README.md: There are four lights.
README.md: There are four lights.

--- a/ARConfiguration.cs
+++ b/ARConfiguration.cs
@@ -16,6 +16,14 @@
 	[KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
 	public class ARConfiguration : MonoBehaviour
 	{
+		private const string WINDOW_POS_KEY = "configWindowPos";
+		private const string REQUIRE_LOS_KEY = "requireLineOfSight";
+		private const string GRACE_RATIO_KEY = "graceRatio";
+		private const string REQUIRE_PROBE_CONNECTION_KEY = "requireConnectionForControl";
+		private const string FIXED_POWER_KEY = "fixedPowerCost";
+		private const string PRETTY_LINES_KEY = "drawPrettyLines";
+		private const string UPDATE_DELAY_KEY = "updateDelay";
+
 		/// <summary>
 		/// Indicates whether connections require line of sight.
 		/// </summary>
@@ -59,6 +67,12 @@
 		public static bool PrettyLines
 		{
 			get;
+			set;
+		}
+
+		public static long UpdateDelay
+		{
+			get;
 			private set;
 		}
 
@@ -66,6 +80,9 @@
 
 		private bool showConfigWindow;
 		private Rect configWindowPos;
+
+		private string updateDelayStr;
+		private long updateDelay;
 
 		private IButton toolbarButton;
 		private ApplicationLauncherButton appLauncherButton;
@@ -96,19 +113,23 @@
 			this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15);
 
 
-			this.configWindowPos = this.LoadConfigValue("configWindowPos", this.configWindowPos);
-
-			ARConfiguration.RequireLineOfSight = this.LoadConfigValue("requireLineOfSight", false);
-
-			ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue("graceRatio", .05d));
+			this.configWindowPos = this.LoadConfigValue(WINDOW_POS_KEY, this.configWindowPos);
+
+			ARConfiguration.RequireLineOfSight = this.LoadConfigValue(REQUIRE_LOS_KEY, false);
+
+			ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue(GRACE_RATIO_KEY, .05d));
 			ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio;
 
 			ARConfiguration.RequireConnectionForControl =
-				this.LoadConfigValue("requireConnectionForControl", false);
-
-			ARConfiguration.FixedPowerCost = this.LoadConfigValue("fixedPowerCost", false);
-
-			ARConfiguration.PrettyLines = this.LoadConfigValue("drawPrettyLines", true);
+				this.LoadConfigValue(REQUIRE_PROBE_CONNECTION_KEY, false);
+
+			ARConfiguration.FixedPowerCost = this.LoadConfigValue(FIXED_POWER_KEY, false);
+
+			ARConfiguration.PrettyLines = this.LoadConfigValue(PRETTY_LINES_KEY, true);
+
+			ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L);
+
+			this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
 
@@ -167,7 +188,7 @@
 				if (configPos != this.configWindowPos)
 				{
 					this.configWindowPos = configPos;
-					this.SaveConfigValue("configWindowPos", this.configWindowPos);
+					this.SaveConfigValue(WINDOW_POS_KEY, this.configWindowPos);
 				}
 			}
 		}
@@ -182,7 +203,7 @@
 			if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
 			{
 				ARConfiguration.RequireLineOfSight = requireLineOfSight;
-				this.SaveConfigValue("requireLineOfSight", requireLineOfSight);
+				this.SaveConfigValue(REQUIRE_LOS_KEY, requireLineOfSight);
 			}
 
 			GUILayout.EndHorizontal();
@@ -197,7 +218,7 @@
 			if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl)
 			{
 				ARConfiguration.RequireConnectionForControl = requireConnectionForControl;
-				this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl);
+				this.SaveConfigValue(REQUIRE_PROBE_CONNECTION_KEY, requireConnectionForControl);
 			}
 
 			GUILayout.EndHorizontal();
@@ -208,7 +229,7 @@
 			if (fixedPowerCost != ARConfiguration.FixedPowerCost)
 			{
 				ARConfiguration.FixedPowerCost = fixedPowerCost;
-				this.SaveConfigValue("fixedPowerCost", fixedPowerCost);
+				this.SaveConfigValue(FIXED_POWER_KEY, fixedPowerCost);
 			}
 
 			GUILayout.EndHorizontal();
@@ -219,10 +240,26 @@
 			if (prettyLines != ARConfiguration.PrettyLines)
 			{
 				ARConfiguration.PrettyLines = prettyLines;
-				this.SaveConfigValue("drawPrettyLines", prettyLines);
-			}
-
-			GUILayout.EndHorizontal();
+				this.SaveConfigValue(PRETTY_LINES_KEY, prettyLines);
+			}
+
+			GUILayout.EndHorizontal();
+
+			GUILayout.BeginHorizontal();
+
+			GUILayout.Label("Update Delay", GUILayout.ExpandWidth(false));
+
+			this.updateDelayStr = GUILayout.TextField(this.updateDelayStr, 4, GUILayout.Width(40f));
+
+			GUILayout.Label("ms", GUILayout.ExpandWidth(false));
+
+			GUILayout.EndHorizontal();
+
+			if (this.updateDelayStr.Length > 1 && long.TryParse(this.updateDelayStr, out this.updateDelay))
+			{
+				ARConfiguration.UpdateDelay = Math.Min(Math.Max(this.updateDelay, 16), 2500);
+				this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
+			}
 
 			if (requireLineOfSight)
 			{
@@ -243,7 +280,7 @@
 				if (newRatio != graceRatio)
 				{
 					ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio);
-					this.SaveConfigValue("graceRatio", newRatio);
+					this.SaveConfigValue(GRACE_RATIO_KEY, newRatio);
 				}
 
 				GUILayout.EndHorizontal();
@@ -281,6 +318,7 @@
 		private void toggleConfigWindow()
 		{
 			this.showConfigWindow = !this.showConfigWindow;
+			this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
 		}
 
 		private T LoadConfigValue<T>(string key, T defaultValue)

--- a/ARFlightController.cs
+++ b/ARFlightController.cs
@@ -2,7 +2,7 @@
 //
 // ARFlightController.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -40,35 +40,35 @@
 	public class ARFlightController : MonoBehaviour
 	{
 		#region Fields
-		protected Dictionary<ConnectionStatus, string> connectionTextures;
-		protected Dictionary<ConnectionStatus, Texture> appLauncherTextures;
-
-		protected ARMapRenderer mapRenderer;
-
-		protected IButton toolbarButton;
-
-		protected ApplicationLauncherButton appLauncherButton;
-		protected Tools.DebugLogger log;
-
-		protected System.Diagnostics.Stopwatch updateTimer;
+		private Dictionary<ConnectionStatus, string> toolbarTextures;
+		private Dictionary<ConnectionStatus, Texture> appLauncherTextures;
+
+		private ARMapRenderer mapRenderer;
+
+		private IButton toolbarButton;
+
+		private ApplicationLauncherButton appLauncherButton;
+		private Tools.DebugLogger log;
+
+		private System.Diagnostics.Stopwatch updateTimer;
 		#endregion
 
 		#region Properties
 		public ConnectionStatus currentConnectionStatus
 		{
 			get;
-			protected set;
-		}
-
-		protected string currentConnectionTexture
-		{
-			get
-			{
-				return this.connectionTextures[this.currentConnectionStatus];
-			}
-		}
-
-		protected Texture currentAppLauncherTexture
+			private set;
+		}
+
+		private string currentConnectionTexture
+		{
+			get
+			{
+				return this.toolbarTextures[this.currentConnectionStatus];
+			}
+		}
+
+		private Texture currentAppLauncherTexture
 		{
 			get
 			{
@@ -92,7 +92,7 @@
 		public string lockID
 		{
 			get;
-			protected set;
+			private set;
 		}
 
 		public ControlTypes lockSet
@@ -118,7 +118,7 @@
 		#endregion
 
 		#region MonoBehaviour LifeCycle
-		protected void Awake()
+		private void Awake()
 		{
 			this.lockID = "ARConnectionRequired";
 
@@ -126,11 +126,11 @@
 
 			this.updateTimer = new System.Diagnostics.Stopwatch();
 
-			this.connectionTextures = new Dictionary<ConnectionStatus, string>();
-
-			this.connectionTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
-			this.connectionTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
-			this.connectionTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
+			this.toolbarTextures = new Dictionary<ConnectionStatus, string>();
+
+			this.toolbarTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
+			this.toolbarTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
+			this.toolbarTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
 
 			this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>();
 
@@ -145,31 +145,23 @@
 			{
 				this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
 
-				this.toolbarButton.TexturePath = this.connectionTextures[ConnectionStatus.None];
+				this.toolbarButton.TexturePath = this.toolbarTextures[ConnectionStatus.None];
 				this.toolbarButton.Text = "AntennaRange";
 				this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
-				this.toolbarButton.Enabled = false;
+				this.toolbarButton.OnClick += (e) => (this.buttonToggle());
 			}
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
 			GameEvents.onVesselChange.Add(this.onVesselChange);
 		}
 
-		protected void Start()
+		private void Start()
 		{
 			this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
 		}
 
-		protected void FixedUpdate()
-		{
-			if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
-			{
-				this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
-					ApplicationLauncher.AppScenes.FLIGHT,
-					this.appLauncherTextures[ConnectionStatus.None]
-				);
-			}
-
+		private void FixedUpdate()
+		{
 			this.log.Clear();
 
 			VesselCommand availableCommand;
@@ -222,11 +214,25 @@
 			log.Print();
 		}
 
-		protected void Update()
-		{
-			if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > 125L)
-			{
-				this.updateTimer.Reset();
+		private void Update()
+		{
+			if (this.toolbarButton != null)
+			{
+				this.toolbarButton.Enabled = MapView.MapIsEnabled;
+			}
+
+			if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
+			{
+				this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
+					this.buttonToggle, this.buttonToggle,
+					ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW,
+					this.appLauncherTextures[ConnectionStatus.None]
+				);
+			}
+
+			if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > ARConfiguration.UpdateDelay)
+			{
+				this.updateTimer.Restart();
 			}
 			else
 			{
@@ -235,42 +241,74 @@
 
 			this.log.Clear();
 
-			if (
-				(this.toolbarButton != null || this.appLauncherButton != null) &&
-				HighLogic.LoadedSceneIsFlight &&
-				FlightGlobals.ActiveVessel != null
-			)
-			{
-				log.Append("Checking vessel relay status.\n");
-
-				this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus();
-
-				log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}",
-					this.currentConnectionStatus, this.currentConnectionTexture);
-
-				if (this.toolbarButton != null)
-				{
-					this.toolbarButton.TexturePath = this.currentConnectionTexture;
-
-					if (this.currentConnectionStatus == ConnectionStatus.None)
+			if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ActiveVessel != null)
+			{
+				Vessel vessel;
+				IAntennaRelay relay;
+				IList<IAntennaRelay> activeVesselRelays;
+
+				for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
+				{
+					vessel = FlightGlobals.Vessels[vIdx];
+
+					if (vessel == null || vessel == FlightGlobals.ActiveVessel)
 					{
-						this.toolbarButton.Important = true;
+						continue;
 					}
-					else
+
+					log.AppendFormat("Fetching best relay for vessel {0}", vessel);
+
+					relay = vessel.GetBestRelay();
+
+					if (relay != null)
 					{
-						this.toolbarButton.Important = false;
+						log.AppendFormat("Finding nearest relay for best relay {0}", relay);
+
+						relay.FindNearestRelay();
 					}
 				}
-				if (this.appLauncherButton != null)
-				{
-					this.appLauncherButton.SetTexture(this.currentAppLauncherTexture);
+
+				activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel];
+				for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
+				{
+					relay = activeVesselRelays[rIdx];
+
+					relay.FindNearestRelay();
+				}
+
+				if (this.toolbarButton != null || this.appLauncherButton != null)
+				{
+					log.Append("Checking vessel relay status.\n");
+
+					this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus();
+
+					log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}",
+						this.currentConnectionStatus, this.currentConnectionTexture);
+
+					if (this.toolbarButton != null)
+					{
+						this.toolbarButton.TexturePath = this.currentConnectionTexture;
+
+						if (this.currentConnectionStatus == ConnectionStatus.None)
+						{
+							if (!this.toolbarButton.Important) this.toolbarButton.Important = true;
+						}
+						else
+						{
+							if (this.toolbarButton.Important) this.toolbarButton.Important = false;
+						}
+					}
+					if (this.appLauncherButton != null)
+					{
+						this.appLauncherButton.SetTexture(this.currentAppLauncherTexture);
+					}
 				}
 			}
 
 			log.Print();
 		}
 
-		protected void OnDestroy()
+		private void OnDestroy()
 		{
 			InputLockManager.RemoveControlLock(this.lockID);
 
@@ -297,14 +335,22 @@
 		}
 		#endregion
 
+		private void buttonToggle()
+		{
+			if (MapView.MapIsEnabled)
+			{
+				ARConfiguration.PrettyLines = !ARConfiguration.PrettyLines;
+			}
+		}
+
 		#region Event Handlers
-		protected void onSceneChangeRequested(GameScenes scene)
+		private void onSceneChangeRequested(GameScenes scene)
 		{
 			print("ARFlightController: Requesting Destruction.");
 			MonoBehaviour.Destroy(this);
 		}
 
-		protected void onVesselChange(Vessel vessel)
+		private void onVesselChange(Vessel vessel)
 		{
 			InputLockManager.RemoveControlLock(this.lockID);
 		}

--- a/ARMapRenderer.cs
+++ b/ARMapRenderer.cs
@@ -2,7 +2,7 @@
 //
 // ARMapRenderer.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -40,11 +40,16 @@
 	{
 		#region Fields
 		private Dictionary<Guid, LineRenderer> vesselLineRenderers;
-		private Dictionary<Guid, bool> vesselFrameCache;
+
+		// Debug Stuff
+		#pragma warning disable 649
+		private System.Diagnostics.Stopwatch timer;
+		private Tools.DebugLogger log;
+		private long relayStart;
+		private long start;
+		#pragma warning restore 649
 
 		#pragma warning disable 414
-		private bool dumpBool;
-		private Color lastColor;
 		private Color thisColor;
 		#pragma warning restore 414
 		#endregion
@@ -90,8 +95,12 @@
 			if (ARConfiguration.PrettyLines)
 			{
 				this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
-				this.vesselFrameCache = new Dictionary<Guid, bool>();
-			}
+			}
+
+			#if DEBUG
+			this.timer = new System.Diagnostics.Stopwatch();
+			this.log = Tools.DebugLogger.New(this);
+			#endif
 		}
 
 		private void OnPreCull()
@@ -103,10 +112,14 @@
 				return;
 			}
 
-			Tools.DebugLogger log = Tools.DebugLogger.New(this);
+			#if DEBUG
+			timer.Restart();
+			#endif
 
 			try
 			{
+				log.Clear();
+
 				log.AppendFormat("OnPreCull.\n");
 
 				log.AppendFormat("\tMapView: Draw3DLines: {0}\n" +
@@ -117,10 +130,6 @@
 					MapView.MapCamera.Distance
 				);
 
-				this.vesselFrameCache.Clear();
-
-				log.AppendLine("vesselFrameCache cleared.");
-
 				if (FlightGlobals.ready && FlightGlobals.Vessels != null)
 				{
 					log.AppendLine("FlightGlobals ready and Vessels list not null.");
@@ -129,19 +138,13 @@
 					{
 						Vessel vessel = FlightGlobals.Vessels[i];
 
+						log.AppendFormat("\nStarting check for vessel {0} at {1}ms", vessel, timer.ElapsedMilliseconds);
+
 						if (vessel == null)
 						{
-							log.AppendFormat("Skipping vessel {0} altogether because it is null.\n");
+							log.AppendFormat("\n\tSkipping vessel {0} altogether because it is null.", vessel);
 							continue;
 						}
-
-						if (this.vesselFrameCache.TryGetValue(vessel.id, out dumpBool))
-						{
-							log.AppendFormat("Skipping vessel {0} because it's already been processed this frame.");
-							continue;
-						}
-
-						log.AppendFormat("Checking vessel {0}.\n", vessel.vesselName);
 
 						switch (vessel.vesselType)
 						{
@@ -149,27 +152,52 @@
 							case VesselType.EVA:
 							case VesselType.Unknown:
 							case VesselType.SpaceObject:
-								log.AppendFormat("\tDiscarded because vessel is of invalid type {0}\n",
+								log.AppendFormat("\n\tDiscarded because vessel is of invalid type {0}",
 									vessel.vesselType);
 								continue;
 						}
 
+						log.AppendFormat("\n\tChecking vessel {0}.", vessel.vesselName);
+
+						#if DEBUG
+						start = timer.ElapsedMilliseconds;
+						#endif
+
 						IAntennaRelay vesselRelay = vessel.GetBestRelay();
+
+						if (vesselRelay == null)
+						{
+							log.AppendFormat("\n\tGot null relay for vessel {0}", vessel.vesselName);
+							continue;
+						}
+
+						log.AppendFormat("\n\tGot best relay {0} ({3}) for vessel {1} in {2} ms",
+							vesselRelay, vessel, timer.ElapsedMilliseconds - start, vesselRelay.GetType().Name);
 
 						if (vesselRelay != null)
 						{
+							#if DEBUG
+							start = timer.ElapsedMilliseconds;
+							#endif
+
 							this.SetRelayVertices(vesselRelay);
+
+							log.AppendFormat("\n\tSet relay vertices for {0} in {1}ms",
+								vessel, timer.ElapsedMilliseconds - start);
 						}
 					}
 				}
 			}
-			catch (Exception)
-			{
+			catch (Exception ex)
+			{
+				this.LogError("Caught {0}: {1}\n{2}\n", ex.GetType().Name, ex.ToString(), ex.StackTrace.ToString());
 				this.Cleanup();
 			}
 			#if DEBUG
 			finally
 			{
+				log.AppendFormat("\n\tOnPreCull finished in {0}ms\n", timer.ElapsedMilliseconds);
+
 				log.Print();
 			}
 			#endif
@@ -179,14 +207,20 @@
 		{
 			this.Cleanup();
 
-			print("ARMapRenderer: Destroyed.");
+			this.Log("Destroyed");
 		}
 		#endregion
 
 		#region Utility
 		private void SetRelayVertices(IAntennaRelay relay)
 		{
-			lastColor = default(Color);
+			log.AppendFormat("\n\t\tDrawing line for relay chain starting at {0}.", relay);
+
+			if (relay.vessel == null)
+			{
+				log.Append("\n\t\tvessel is null, bailing out");
+				return;
+			}
 
 			LineRenderer renderer = this[relay.vessel.id];
 			Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
@@ -213,62 +247,64 @@
 
 			int idx = 0;
 
-			while (relay != null)
-			{
-				Vector3d nextPoint;
-
-				renderer.enabled = true;
-
-				if (!relay.CanTransmit())
-				{
-					thisColor = Color.red;
+			#if DEBUG
+			relayStart = timer.ElapsedMilliseconds;
+			#endif
+
+			Vector3d nextPoint;
+
+			renderer.enabled = true;
+
+			if (!relay.CanTransmit())
+			{
+				thisColor = Color.red;
+			}
+			else
+			{
+				if (relay.transmitDistance < relay.nominalTransmitDistance)
+				{
+					thisColor = Color.green;
 				}
 				else
 				{
-					if (relay.transmitDistance < relay.nominalTransmitDistance)
-					{
-						thisColor = Color.green;
-					}
-					else
-					{
-						thisColor = Color.yellow;
-					}
-				}
-
-				if (lastColor != default(Color) && thisColor != lastColor)
-				{
-					break;
-				}
-
-				lastColor = thisColor;
-				renderer.SetColors(thisColor, thisColor);
-
-				this.vesselFrameCache[relay.vessel.id] = true;
-
-				if (relay.KerbinDirect)
-				{
-					nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
-					relay = null;
-				}
-				else
-				{
-					if (relay.targetRelay == null)
-					{
-						return;
-					}
-
-					nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
-					relay = relay.targetRelay;
-				}
-
-				if (!MapView.Draw3DLines)
-				{
-					nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
-					nextPoint.z = nextPoint.z >= 0f ? d : -d;
-				}
-
-				renderer.SetPosition(++idx, nextPoint);
-			}
+					thisColor = Color.yellow;
+				}
+			}
+
+			if (relay.KerbinDirect)
+			{
+				nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
+			}
+			else
+			{
+				if (relay.targetRelay == null || relay.targetRelay.vessel == null)
+				{
+					this.LogError(
+						"SetRelayVertices: relay {0} has null target relay or vessel when not KerbinDirect, bailing out!",
+						relay
+					);
+
+					renderer.enabled = false;
+					return;
+				}
+
+				nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
+			}
+
+			renderer.SetColors(thisColor, thisColor);
+
+			if (!MapView.Draw3DLines)
+			{
+				nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
+				nextPoint.z = nextPoint.z >= 0f ? d : -d;
+			}
+
+			idx++;
+
+			renderer.SetVertexCount(idx + 1);
+			renderer.SetPosition(idx, nextPoint);
+
+			log.AppendFormat("\n\t\t\t...finished segment in {0} ms", timer.ElapsedMilliseconds - relayStart);
 		}
 
 		private void Cleanup()
@@ -286,11 +322,6 @@
 				}
 				this.vesselLineRenderers.Clear();
 			}
-
-			if (this.vesselFrameCache != null && this.vesselFrameCache.Count > 0)
-			{
-				this.vesselFrameCache.Clear();
-			}
 		}
 		#endregion
 	}

--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -2,7 +2,7 @@
 //
 // AntennaRelay.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -37,9 +37,6 @@
 	/// </summary>
 	public class AntennaRelay
 	{
-		private static readonly System.Diagnostics.Stopwatch searchTimer = new System.Diagnostics.Stopwatch();
-		private const long millisecondsBetweenSearches = 125L;
-
 		// We don't have a Bard, so we'll hide Kerbin here.
 		private static CelestialBody _Kerbin;
 
@@ -59,9 +56,8 @@
 			}
 		}
 
-		private long lastSearch;
-
 		private bool canTransmit;
+		private bool isChecked;
 
 		private IAntennaRelay nearestRelay;
 		private IAntennaRelay bestOccludedRelay;
@@ -110,8 +106,6 @@
 		{
 			get
 			{
-				this.FindNearestRelay();
-
 				if (this.KerbinDirect || this.targetRelay == null)
 				{
 					return this.DistanceTo(Kerbin);
@@ -158,7 +152,6 @@
 		/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
 		public virtual bool CanTransmit()
 		{
-			this.FindNearestRelay();
 			return this.canTransmit;
 		}
 
@@ -166,25 +159,9 @@
 		/// Finds the nearest relay.
 		/// </summary>
 		/// <returns>The nearest relay or null, if no relays in range.</returns>
-		private void FindNearestRelay()
-		{
-			if (!searchTimer.IsRunning)
-			{
-				searchTimer.Start();
-			}
-
-			long searchTime = searchTimer.ElapsedMilliseconds;
-			long timeSinceLast = searchTime - this.lastSearch;
-
-			if (timeSinceLast < Math.Max(millisecondsBetweenSearches, UnityEngine.Time.smoothDeltaTime))
-			{
-				return;
-			}
-
-			this.lastSearch = searchTime;
-
-			// Skip vessels that have already been checked for a nearest relay this pass.
-			if (RelayDatabase.Instance.CheckedVesselsTable.ContainsKey(this.vessel.id))
+		public void FindNearestRelay()
+		{
+			if (!FlightGlobals.ready)
 			{
 				return;
 			}
@@ -194,11 +171,22 @@
 			log = Tools.DebugLogger.New(this);
 			#endif
 
-			log.AppendFormat("{0}: Target search started at {1} ms ({2} ms since last search).",
-				this.ToString(), searchTime, timeSinceLast);
-			
+			// Skip vessels that have already been checked for a nearest relay this pass.
+			if (this.isChecked)
+			{
+				log.AppendFormat("{0}: Target search skipped because our vessel has been checked already this search.",
+					this);
+				log.Print();
+				return;
+			}
+
+			log.AppendFormat("{0}: Target search started).", this.ToString());
+
+			#if DEBUG
+			try {
+			#endif
 			// Set this vessel as checked, so that we don't check it again.
-			RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true;
+			this.isChecked = true;
 
 			// Blank everything we're trying to find before the search.
 			this.firstOccludingBody = null;
@@ -210,6 +198,7 @@
 			this.KerbinDirect = true;
 
 			CelestialBody bodyOccludingBestOccludedRelay = null;
+			IAntennaRelay needle;
 
 			double nearestRelaySqrDistance = double.PositiveInfinity;
 			double bestOccludedSqrDistance = double.PositiveInfinity;
@@ -222,11 +211,31 @@
 			 * against future finds.
 			 * */
 			Vessel potentialVessel;
-			IList<IAntennaRelay> vesselRelays;
+			IAntennaRelay potentialBestRelay;
+			CelestialBody fob;
+
+			// IList<IAntennaRelay> vesselRelays;
 			for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
 			{
+				log.AppendFormat("\nFetching vessel at index {0}", vIdx);
 				potentialVessel = FlightGlobals.Vessels[vIdx];
+				
+				if (potentialVessel == null)
+				{
+					Tools.PostErrorMessage("{0}: Skipping vessel at index {1} because it is null.", this, vIdx);
+					log.AppendFormat("\n\tSkipping vessel at index {0} because it is null.", vIdx);
+					log.Print();
+					return;
+				}
+				#if DEBUG
+				else
+				{
+					log.AppendFormat("\n\tGot vessel {0}", potentialVessel);
+				}
+				#endif
+
 				// Skip vessels of the wrong type.
+				log.Append("\n\tchecking vessel type");
 				switch (potentialVessel.vesselType)
 				{
 					case VesselType.Debris:
@@ -234,77 +243,73 @@
 					case VesselType.EVA:
 					case VesselType.SpaceObject:
 					case VesselType.Unknown:
+							log.Append("\n\tSkipping because vessel is the wrong type.");
 						continue;
 					default:
 						break;
 				}
-
+				
+				log.Append("\n\tchecking if vessel is this vessel");
 				// Skip vessels with the wrong ID
 				if (potentialVessel.id == vessel.id)
 				{
+					log.Append("\n\tSkipping because vessel is this vessel.");
 					continue;
 				}
 
 				// Find the distance from here to the vessel...
+				log.Append("\n\tgetting distance to potential vessel");
 				double potentialSqrDistance = this.sqrDistanceTo(potentialVessel);
-				vesselRelays = potentialVessel.GetAntennaRelays();
-
-				CelestialBody fob = null;
-
+				log.Append("\n\tgetting best vessel relay");
+
+				potentialBestRelay = potentialVessel.GetBestRelay();
+				log.AppendFormat("\n\t\tgot best vessel relay {0}",
+					potentialBestRelay == null ? "null" : potentialBestRelay.ToString());
+
+				if (potentialBestRelay == null)
+				{
+					log.Append("\n\t\t...skipping null relay");
+					continue;
+				}
+
+				log.Append("\n\t\tdoing LOS check");
 				// Skip vessels to which we do not have line of sight.
 				if (
 					ARConfiguration.RequireLineOfSight &&
 					!this.vessel.hasLineOfSightTo(potentialVessel, out fob, ARConfiguration.RadiusRatio)
 				)
 				{
-					this.firstOccludingBody = fob;
-
-					log.AppendFormat("\n\t{0}: Vessel {1} not in line of sight.",
+					log.Append("\n\t\t...failed LOS check");
+
+					log.AppendFormat("\n\t\t\t{0}: Vessel {1} not in line of sight.",
 						this.ToString(), potentialVessel.vesselName);
+					
+					log.AppendFormat("\n\t\t\tpotentialSqrDistance: {0}", potentialSqrDistance);
+					log.AppendFormat("\n\t\t\tbestOccludedSqrDistance: {0}", bestOccludedSqrDistance);
+					log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxTransmitSqrDistance);
 
 					if (
 						(potentialSqrDistance < bestOccludedSqrDistance) &&
-						(potentialSqrDistance < maxTransmitSqrDistance)
+						(potentialSqrDistance < maxTransmitSqrDistance) &&
+						potentialBestRelay.CanTransmit()
 					)
 					{
-						
-						log.AppendFormat("\n\t\t{0}: Checking {1} relays on occluded vessel {2}.",
-							this.ToString(),
-							potentialVessel.GetAntennaRelays().Count,
-							potentialVessel
-						);
-
-						IAntennaRelay occludedRelay;
-						for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
-						{
-							occludedRelay = vesselRelays[rIdx];
-
-							log.AppendFormat(
-								"\n\t\t{0}: Checking candidate for bestOccludedRelay: {1}" +
-								"\n\t\tCanTransmit: {2}",
-								this.ToString(), occludedRelay, occludedRelay.CanTransmit()
-							);
-							
-							if (occludedRelay.CanTransmit())
-							{
-								this.bestOccludedRelay = occludedRelay;
-								bodyOccludingBestOccludedRelay = fob;
-								bestOccludedSqrDistance = potentialSqrDistance;
-
-								log.AppendFormat("\n\t{0}: Found new bestOccludedRelay: {1}" +
-									" (blocked by {2}; distance: {3} m)",
-									this.ToString(),
-									occludedRelay.ToString(),
-									fob,
-									potentialSqrDistance
-								);
-								break;
-							}
-						}
-					}
-
+						log.Append("\n\t\t...vessel is close enough to and potentialBestRelay can transmit");
+						log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay);
+
+						this.bestOccludedRelay = potentialBestRelay;
+						bodyOccludingBestOccludedRelay = fob;
+						bestOccludedSqrDistance = potentialSqrDistance;
+					}
+					else
+					{
+						log.Append("\n\t\t...vessel is not close enough to check for occluded relays, carrying on");
+					}
+					
 					continue;
 				}
+
+				log.Append("\n\t\t...passed LOS check");
 
 				/*
 				 * ...so that we can skip the vessel if it is further away than a vessel we've already checked.
@@ -319,26 +324,76 @@
 					continue;
 				}
 
-				IAntennaRelay potentialRelay;
-				for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
-				{
-					potentialRelay = vesselRelays[rIdx];
-
-					if (potentialRelay.CanTransmit() && potentialRelay.targetRelay != this)
-					{
-						// @TODO: Moved this here from outside the loop; why was it there?
+				log.Append("\n\t\t...passed distance check");
+
+				if (potentialBestRelay.CanTransmit())
+				{
+					needle = potentialBestRelay;
+					bool isCircular = false;
+
+					int iterCount = 0;
+					while (needle != null)
+					{
+						iterCount++;
+
+						if (needle.KerbinDirect)
+						{
+							break;
+						}
+
+						if (needle.targetRelay == null)
+						{
+							break;
+						}
+
+						if (needle.targetRelay.vessel == this.vessel || needle == this.moduleRef)
+						{
+							isCircular = true;
+							break;
+						}
+
+						// Avoid infinite loops when we're not catching things right.
+						if (iterCount > FlightGlobals.Vessels.Count)
+						{
+							Tools.PostErrorMessage(
+								"[{0}] iterCount exceeded while checking for circular network; assuming it is circular" +
+								"\n\tneedle={1}" +
+								"\n\tthis.moduleRef={2}",
+								this,
+								needle == null ? "null" : string.Format(
+									"{0}, needle.KerbinDirect={1}, needle.targetRelay={2}",
+									needle, needle.KerbinDirect, needle.targetRelay == null ? "null" : string.Format(
+										"{0}\n\tneedle.targetRelay.vessel={1}",
+										needle.targetRelay,
+										needle.targetRelay.vessel == null ?
+											"null" : needle.targetRelay.vessel.vesselName
+									)
+								),
+								this.moduleRef == null ? "null" : this.moduleRef.ToString()
+							);
+							isCircular = true;
+							break;
+						}
+
+						needle = needle.targetRelay;
+					}
+
+					if (!isCircular)
+					{
 						nearestRelaySqrDistance = potentialSqrDistance;
-						this.nearestRelay = potentialRelay;
-
-						if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)
-						{
-							log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m)",
-								this.ToString(),
-								this.nearestRelay.ToString(),
-								Math.Sqrt(nearestRelaySqrDistance)
-							);
-						}
-						break;
+						this.nearestRelay = potentialBestRelay;
+
+						log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m)",
+							this.ToString(),
+							this.nearestRelay.ToString(),
+							Math.Sqrt(nearestRelaySqrDistance)
+						);
+					}
+					else
+					{
+						log.AppendFormat("\n\t\t...connection to {0} would result in a circular network, skipping",
+							potentialBestRelay
+						);
 					}
 				}
 			}
@@ -349,9 +404,21 @@
 			kerbinSqrDistance *= kerbinSqrDistance;
 
 			log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
+			log.AppendFormat(
+				"\n{0}: nearestRelay={1} ({2}m²)), bestOccludedRelay={3} ({4}m²), kerbinSqrDistance={5}m²)",
+				this,
+				this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
+				nearestRelaySqrDistance,
+				this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
+				bestOccludedSqrDistance,
+				kerbinSqrDistance
+			);
 
 			// If we don't have LOS to Kerbin, focus on relays
-			if (!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio))
+			if (
+				ARConfiguration.RequireLineOfSight &&
+				!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)
+			)
 			{
 				log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
 
@@ -392,7 +459,7 @@
 							log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
 								nearestRelaySqrDistance, bestOccludedSqrDistance);
 							
-							this.targetRelay = nearestRelay;
+							this.targetRelay = this.nearestRelay;
 							this.firstOccludingBody = null;
 						}
 						// Otherwise, target the best occluded relay.
@@ -422,7 +489,7 @@
 								nearestRelaySqrDistance, kerbinSqrDistance);
 							
 							this.KerbinDirect = false;
-							this.targetRelay = nearestRelay;
+							this.targetRelay = this.nearestRelay;
 						}
 						// Otherwise, pick Kerbin.
 						else
@@ -513,7 +580,7 @@
 								log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
 									nearestRelaySqrDistance, bestOccludedSqrDistance);
 								
-								this.targetRelay = nearestRelay;
+								this.targetRelay = this.nearestRelay;
 								this.firstOccludingBody = null;
 							}
 							// Otherwise, target the best occluded relay.
@@ -543,7 +610,7 @@
 									nearestRelaySqrDistance, kerbinSqrDistance);
 								
 								this.KerbinDirect = false;
-								this.targetRelay = nearestRelay;
+								this.targetRelay = this.nearestRelay;
 							}
 							// Otherwise, pick Kerbin.
 							else
@@ -559,16 +626,23 @@
 				}
 			}
 
-			log.AppendFormat("{0}: Target search completed at {1} ms ({2} ms elapsed).",
-				this.ToString(), searchTimer.ElapsedMilliseconds, searchTimer.ElapsedMilliseconds - searchTime);;
-
-			log.AppendFormat("\n{0}: Status determination complete.", this.ToString());
-
+			log.AppendFormat("\n{0}: Target search and status determination complete.", this.ToString());
+			
+			#if DEBUG
+			} catch (Exception ex) {
+				log.AppendFormat("\nCaught {0}: {1}\n{2}", ex.GetType().FullName, ex.ToString(), ex.StackTrace);
+			#if QUIT_ON_EXCEPTION
+				UnityEngine.Application.Quit();
+			#endif
+			} finally {
+			#endif
 			log.Print(false);
-
+			#if DEBUG
+			}
+			#endif
 			// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
 			// used next time.
-			RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id);
+			this.isChecked = false;
 		}
 
 		/// <summary>
@@ -592,6 +666,9 @@
 		public AntennaRelay(IAntennaRelay module)
 		{
 			this.moduleRef = module;
+			this.isChecked = false;
+
+			Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
 		}
 	}
 }

--- a/GameData/AntennaRange/AntennaRange.cfg
+++ b/GameData/AntennaRange/AntennaRange.cfg
@@ -2,7 +2,7 @@
 //
 // AntennaRange.cfg
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,

--- a/IAntennaRelay.cs
+++ b/IAntennaRelay.cs
@@ -2,7 +2,7 @@
 //
 // IAntennaRelay.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -84,6 +84,11 @@
 		bool CanTransmit();
 
 		/// <summary>
+		/// Finds the nearest relay.
+		/// </summary>
+		void FindNearestRelay();
+
+		/// <summary>
 		/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.IAntennaRelay"/>.
 		/// </summary>
 		string ToString();

--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -2,7 +2,7 @@
 //
 // ModuleLimitedDataTransmitter.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -145,13 +145,21 @@
 				{
 					return base.vessel;
 				}
-				else if (this.part != null)
+				else if (this.part != null && this.part.vessel != null)
 				{
 					return this.part.vessel;
 				}
-
+				else if (
+					this.part.protoPartSnapshot != null &&
+					this.part.protoPartSnapshot.pVesselRef != null &&
+					this.part.protoPartSnapshot.pVesselRef.vesselRef != null
+				)
+				{
+					return this.part.protoPartSnapshot.pVesselRef.vesselRef;
+				}
 				else
 				{
+					this.LogError("Vessel and/or part reference are null, returning null vessel.");
 					return null;
 				}
 			}
@@ -205,11 +213,8 @@
 		/// </summary>
 		public double maxTransmitDistance
 		{
-			get
-			{
-				// TODO: Cache this in a way that doesn't break everything.
-				return Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
-			}
+			get;
+			protected set;
 		}
 
 		/// <summary>
@@ -370,6 +375,8 @@
 
 			if (state >= StartState.PreLaunch)
 			{
+				this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
+
 				this.relay = new AntennaRelay(this);
 				this.relay.maxTransmitDistance = this.maxTransmitDistance;
 				this.relay.nominalTransmitDistance = this.nominalRange;
@@ -393,6 +400,8 @@
 			base.Fields.Load(node);
 
 			base.OnLoad (node);
+
+			this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
 		}
 
 		/// <summary>
@@ -437,6 +446,17 @@
 		}
 
 		/// <summary>
+		/// Finds the nearest relay.
+		/// </summary>
+		public void FindNearestRelay()
+		{
+			if (this.relay != null)
+			{
+				this.relay.FindNearestRelay();
+			}
+		}
+
+		/// <summary>
 		/// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
 		/// returns false.
 		/// </summary>
@@ -444,6 +464,13 @@
 		/// <param name="callback">Callback function</param>
 		public new void TransmitData(List<ScienceData> dataQueue, Callback callback)
 		{
+			this.LogDebug(
+				"TransmitData(List<ScienceData> dataQueue, Callback callback) called.  dataQueue.Count={0}",
+				dataQueue.Count
+			);
+
+			this.FindNearestRelay();
+
 			this.PreTransmit_SetPacketSize();
 			this.PreTransmit_SetPacketResourceCost();
 
@@ -451,7 +478,20 @@
 			{
 				ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
 
-				base.TransmitData(dataQueue, callback);
+				this.LogDebug(
+					"CanTransmit in TransmitData, calling base.TransmitData with dataQueue=[{0}] and callback={1}",
+					dataQueue.SPrint(),
+					callback == null ? "null" : callback.ToString()
+				);
+
+				if (callback == null)
+				{
+					base.TransmitData(dataQueue);
+				}
+				else
+				{
+					base.TransmitData(dataQueue, callback);
+				}
 			}
 			else
 			{
@@ -513,23 +553,25 @@
 
 				if (dataQueue.Count > 0)
 				{
-					StringBuilder msg = new StringBuilder();
-
-					msg.Append('[');
-					msg.Append(this.part.partInfo.title);
-					msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
-					msg.Append("Data to be discarded:\n");
+					StringBuilder sb = Tools.GetStringBuilder();
+
+					sb.Append('[');
+					sb.Append(this.part.partInfo.title);
+					sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
+					sb.Append("Data to be discarded:\n");
 
 					ScienceData data;
 					for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
 					{
 						data = dataQueue[dIdx];
-						msg.AppendFormat("\t{0}\n", data.title);
+						sb.AppendFormat("\t{0}\n", data.title);
 					}
 
-					ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
-
-					Tools.PostDebugMessage(msg.ToString());
+					ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+					Tools.PostDebugMessage(sb.ToString());
+
+					Tools.PutStringBuilder(sb);
 				}
 
 				this.PostCannotTransmitError();
@@ -549,6 +591,11 @@
 		/// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
 		public new void TransmitData(List<ScienceData> dataQueue)
 		{
+			this.LogDebug(
+				"TransmitData(List<ScienceData> dataQueue) called, dataQueue.Count={0}",
+				dataQueue.Count
+			);
+
 			this.TransmitData(dataQueue, null);
 		}
 
@@ -558,6 +605,8 @@
 		/// </summary>
 		public new void StartTransmission()
 		{
+			this.FindNearestRelay();
+
 			PreTransmit_SetPacketSize ();
 			PreTransmit_SetPacketResourceCost ();
 
@@ -603,7 +652,6 @@
 					{
 						this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
 					}
-					this.UImaxTransmitDistance = "N/A";
 					this.UIpacketSize = "N/A";
 					this.UIpacketCost = "N/A";
 				}
@@ -625,14 +673,15 @@
 		/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns>
 		public override string ToString()
 		{
-			StringBuilder msg = new StringBuilder();
-
-			msg.Append(this.part.partInfo.title);
+			StringBuilder sb = Tools.GetStringBuilder();
+			string msg;
+
+			sb.Append(this.part.partInfo.title);
 
 			if (vessel != null)
 			{
-				msg.Append(" on ");
-				msg.Append(vessel.vesselName);
+				sb.Append(" on ");
+				sb.Append(vessel.vesselName);
 			}
 			else if (
 				this.part != null &&
@@ -641,11 +690,15 @@
 				this.part.protoPartSnapshot.pVesselRef != null
 			)
 			{
-				msg.Append(" on ");
-				msg.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
-			}
-
-			return msg.ToString();
+				sb.Append(" on ");
+				sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
+			}
+
+			msg = sb.ToString();
+
+			Tools.PutStringBuilder(sb);
+
+			return msg;
 		}
 
 		// When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty.
@@ -702,12 +755,6 @@
 
 				base.packetResourceCost = this._basepacketResourceCost
 					* rangeFactor;
-
-				Tools.PostDebugMessage(
-					this,
-					"Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).",
-					base.packetResourceCost,
-					rangeFactor);
 			}
 
 			base.packetResourceCost *= this.packetThrottle / 100f;
@@ -729,12 +776,6 @@
 				base.packetSize = Mathf.Min(
 					this._basepacketSize * rangeFactor,
 					this._basepacketSize * this.maxDataFactor);
-
-				Tools.PostDebugMessage(
-					this,
-					"Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).",
-					base.packetSize,
-					rangeFactor);
 			}
 
 			base.packetSize *= this.packetThrottle / 100f;
@@ -742,25 +783,30 @@
 
 		private string buildTransmitMessage()
 		{
-			StringBuilder message = new StringBuilder();
-
-			message.Append("[");
-			message.Append(base.part.partInfo.title);
-			message.Append("]: ");
-
-			message.Append("Beginning transmission ");
+			StringBuilder sb = Tools.GetStringBuilder();
+			string msg;
+
+			sb.Append("[");
+			sb.Append(base.part.partInfo.title);
+			sb.Append("]: ");
+
+			sb.Append("Beginning transmission ");
 
 			if (this.KerbinDirect)
 			{
-				message.Append("directly to Kerbin.");
+				sb.Append("directly to Kerbin.");
 			}
 			else
 			{
-				message.Append("via ");
-				message.Append(this.relay.targetRelay);
-			}
-
-			return message.ToString();
+				sb.Append("via ");
+				sb.Append(this.relay.targetRelay);
+			}
+
+			msg = sb.ToString();
+
+			Tools.PutStringBuilder(sb);
+
+			return msg;
 		}
 
 		#if DEBUG
@@ -771,47 +817,14 @@
 			PreTransmit_SetPacketSize ();
 			PreTransmit_SetPacketResourceCost ();
 
-			string msg = string.Format(
-				"'{0}'\n" + 
-				"_basepacketSize: {1}\n" +
-				"packetSize: {2}\n" +
-				"_basepacketResourceCost: {3}\n" +
-				"packetResourceCost: {4}\n" +
-				"maxTransmitDistance: {5}\n" +
-				"transmitDistance: {6}\n" +
-				"nominalRange: {7}\n" +
-				"CanTransmit: {8}\n" +
-				"DataRate: {9}\n" +
-				"DataResourceCost: {10}\n" +
-				"TransmitterScore: {11}\n" +
-				"targetRelay: {12}\n" +
-				"KerbinDirect: {13}\n" +
-				"Vessel ID: {14}",
-				this.name,
-				this._basepacketSize,
-				base.packetSize,
-				this._basepacketResourceCost,
-				base.packetResourceCost,
-				this.maxTransmitDistance,
-				this.transmitDistance,
-				this.nominalRange,
-				this.CanTransmit(),
-				this.DataRate,
-				this.DataResourceCost,
-				ScienceUtil.GetTransmitterScore(this),
-				this.relay.targetRelay == null ? "null" : this.relay.targetRelay.ToString(),
-				this.KerbinDirect,
-				this.vessel.id
-				);
-
-			Tools.PostLogMessage(msg);
+			DebugPartModule.DumpClassObject(this);
 		}
 
 		[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
 		public void PrintAllVessels()
 		{
-			StringBuilder sb = new StringBuilder();
-
+			StringBuilder sb = Tools.GetStringBuilder();
+			
 			sb.Append("Dumping FlightGlobals.Vessels:");
 
 			Vessel vessel;
@@ -820,8 +833,10 @@
 				vessel = FlightGlobals.Vessels[i];
 				sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
 			}
-
+		    
 			Tools.PostDebugMessage(sb.ToString());
+
+			Tools.PutStringBuilder(sb);
 		}
 		 
 		[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]

--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -2,7 +2,7 @@
 //
 // AssemblyInfo.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -39,7 +39,7 @@
 // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
 // The form "{Major}.{Minor}.*" will automatically update the build and revision,
 // and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion("1.8.*")]
+[assembly: AssemblyVersion("1.9.1.*")]
 // The following attributes are used to specify the signing key for the assembly,
 // if desired. See the Mono documentation for more information about signing.
 //[assembly: AssemblyDelaySign(false)]

--- a/ProtoAntennaRelay.cs
+++ b/ProtoAntennaRelay.cs
@@ -2,7 +2,7 @@
 //
 // ProtoAntennaRelay.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -48,12 +48,25 @@
 		{
 			get
 			{
-				if (this.protoPart != null && this.protoPart.pVesselRef != null)
+				if (
+					this.protoPart != null &&
+					this.protoPart.pVesselRef != null &&
+					this.protoPart.pVesselRef.vesselRef != null
+				)
 				{
 					return this.protoPart.pVesselRef.vesselRef;
 				}
 				else
 				{
+					Tools.PostErrorMessage("{0}: Could not fetch vessel!  {1}{2}{3}",
+						this.ToString(),
+						this.protoPart == null ? "\n\tprotoPart=null" : string.Empty,
+						this.protoPart != null && this.protoPart.pVesselRef == null ?
+							"\n\tthis.protoPart.pVesselRef=null" : string.Empty,
+						this.protoPart != null && this.protoPart.pVesselRef != null &&
+							this.protoPart.pVesselRef.vesselRef == null ?
+							"\n\tthis.protoPart.pVesselRef.vesselRef=null" : string.Empty
+					);
 					return null;
 				}
 			}
@@ -125,7 +138,7 @@
 		/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.</returns>
 		public override string ToString()
 		{
-			System.Text.StringBuilder sb = new System.Text.StringBuilder();
+			System.Text.StringBuilder sb = Tools.GetStringBuilder();
 
 			sb.Append(this.Title);
 
@@ -133,6 +146,8 @@
 			{
 				sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
 			}
+
+			Tools.PutStringBuilder(sb);
 
 			return sb.ToString();
 		}
@@ -146,6 +161,8 @@
 		public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(prefabRelay)
 		{
 			this.protoPart = pps;
+
+			Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
 		}
 	}
 }

file:b/README.md (new)
--- /dev/null
+++ b/README.md
@@ -1,1 +1,212 @@
-
+# AntennaRange
+A KSP mod that enforces and encourages the use of the bigger antennas.
+
+# For Part Developers
+## The Fields
+AntennaRange extends and augments the functionality of the stock ModuleDataTransmitter through the new `ModuleLimitedDataTransmitter` class. This class uses four additional configurable fields to define the part's behavior.
+
+`nominalRange` is the range, in meters, at which the part should function identically to the stock module, i.e. without any modification to the power cost or packet size. This is used along with maxPowerFactor to calculate the maximum range of the part.  
+`simpleRange` is the same as nominalRange, but is used when the mod is in "simple" mode. In general it will probably need to be a much larger number than nominalRange.  
+`maxPowerFactor` effectively sets the maximum range of the antenna by essentially capping how much the power may be "turned up" to get better range. I originally used 8 for this number, because it felt right. I've since used 4 (for my DTS) and 16 (for my Comm. 88-88). You don't want this number to be too high, or small probes will go uncontrollable a lot when transmitting.  
+`maxDataFactor` defines the maximum "speed up" bonus that comes from using antennas at less their nominal range. I originally used 4 for this number for all parts; the DTS has a higher bonus now.
+
+Note that all of the fields needed for Squad's `ModuleDataTransmitter` still need to be filled out. Depending on how you're defining your parts, they might need to go in your AntennaRange patch, or they might already be defined on the base part.
+
+## The Mechanic
+In general, the scaling functions assume the relation `D² α P/R,` where D is the total transmission distance, P is the transmission power, and R is the data rate.  Data rate increases as range decreases below nominalRange: `R α nominalRange² / D²`.  By default, power use increases as range increases above `nominalRange`: `P α D² / nominalRange²`.  Optionally, power use may remain fixed, and data rate instead decreases as range increases above `nominalRange`: `R α nominalRange² / D²`.
+
+## Patch Conventions
+To maximize cross-compatibility, please consider the following conventions for ModuleManager patches regarding AntennaRange:
+
+When providing new definitions for your own parts, always specify a `:FOR[YourModHere]` pass name.
+Whenever changing default AntennaRange definitions (e.g. if you were going to rebalance my antennas to suit your mod), please do so in the `:AFTER[AntennaRange]` pass.
+I recommend providing all optional functionality (e.g. enabling RemoteTech vs. AntennaRange modules) in separate patches using `:NEEDS[]` blocks.
+
+A sample AntennaRange configuration for an all-new mod part might look like this:
+```
+@PART[modPartName]:FOR[YourModName]:NEEDS[AntennaRange,!RemoteTech]
+{
+	MODULE
+	{
+		// ### Module Definition ###
+		name = ModuleLimitedDataTransmitter
+		
+		// ### Squad Definitions ###
+		// Delay between transmission packets, in seconds
+		packetInterval = 0.10
+		
+		// Data capacity of nominal transmission packets, in MiT
+		packetSize = 2
+		
+		// Resource cost of nominal transmission packets, in units
+		packetResourceCost = 20.0
+		
+		// Resource name to be consumed by transmission
+		requiredResource = ElectricCharge
+		
+		// Animation module index, 0-based, of the antenna extend/retract animation
+		DeployFxModules = 0
+		
+		// ### AntennaRange Defintions ###
+		// Range, in meters, at which the antenna behaves per the "nominal" characteristics above
+		// Used with "additive" ranges.
+		nominalRange = 10000000000
+		
+		// Range, in meters, at which the antenna behaves per the "nominal" characteristics above
+		// Used with "simple" ranges.
+		simpleRange = 56250000000
+		
+		// The maxmimum multiplier on packetResourceCost, essentially defining the maximum power output of the
+		// transmitter.  Maximum range is defined as: maxTransmitDistance = nominalRange * sqrt(maxPowerFactor)
+		maxPowerFactor = 16
+		
+		// The maximum multiplier on packetSize, essentially defining the maximum data throughput of the
+		// transmitter.
+		maxDataFactor = 2
+	}
+	
+	// We add this ModuleScienceContainer so that when transmission fails the antennas can try to stash the data instead of dumping it to the void.
+	MODULE
+	{
+		name = ModuleScienceContainer
+
+		dataIsCollectable = true
+		dataIsStorable = false
+
+		storageRange = 2
+	}
+}
+```
+
+This example assumes that the base part definition does not include a `ModuleDataTransmitter` module, or any RT modules. If the base part definition includes a `ModuleDataTransmitter` module, a sample AntennaRange patch could look like this:
+```
+@PART[modPartName]:FOR[YourModName]:NEEDS[AntennaRange,!RemoteTech]
+{
+	@MODULE[ModuleDataTransmitter]
+	{
+		@name = ModuleLimitedDataTransmitter
+		nominalRange = 10000000000
+		simpleRange = 56250000000
+		maxPowerFactor = 16
+		maxDataFactor = 2
+	}
+	
+	// We add this ModuleScienceContainer so that when transmission fails the antennas can try to stash the data instead of dumping it to the void.
+	MODULE
+	{
+		name = ModuleScienceContainer
+
+		dataIsCollectable = true
+		dataIsStorable = false
+
+		storageRange = 2
+	}
+}
+```
+
+IIRC, RemoteTech parts should not have `ModuleDataTransmitter` definitions. In that case, to facilitate RT, AR, and Stock compatibility, a suite of patches like this might be appropriate:
+
+```
+// If we don't have RemoteTech, add a stock ModuleDataTransmitter first.
+@PART[modPartName]:NEEDS[!RemoteTech]:BEFORE[YourModName]
+{
+	MODULE
+	{
+		// ### Module Definition ###
+		name = ModuleDataTransmitter
+		
+		// ### Squad Definitions ###
+		// Delay between transmission packets, in seconds
+		packetInterval = 0.10
+		
+		// Data capacity of nominal transmission packets, in MiT
+		packetSize = 2
+		
+		// Resource cost of nominal transmission packets, in units
+		packetResourceCost = 20.0
+		
+		// Resource name to be consumed by transmission
+		requiredResource = ElectricCharge
+		
+		// Animation module index, 0-based, of the antenna extend/retract animation
+		DeployFxModules = 0
+	}
+}
+
+// If AntennaRange is installed, convert that to a ModuleLimitedDataTransmitter
+@PART[modPartName]:NEEDS[AntennaRange,!RemoteTech]:FOR[YourModName]
+{
+	@MODULE[ModuleDataTransmitter]
+	{
+		// ### Module Redefinition ###
+		@name = ModuleLimitedDataTransmitter
+		
+		// ### AntennaRange Defintions ###
+		// Range, in meters, at which the antenna behaves per the "nominal" characteristics above
+		// Used with "additive" ranges.
+		nominalRange = 10000000000
+		
+		// Range, in meters, at which the antenna behaves per the "nominal" characteristics above
+		// Used with "simple" ranges.
+		simpleRange = 56250000000
+		
+		// The maxmimum multiplier on packetResourceCost, essentially defining the maximum power output of the
+		// transmitter.  Maximum range is defined as: maxTransmitDistance = nominalRange * sqrt(maxPowerFactor)
+		maxPowerFactor = 16
+		
+		// The maximum multiplier on packetSize, essentially defining the maximum data throughput of the
+		// transmitter.
+		maxDataFactor = 2
+	}
+
+	// We add this ModuleScienceContainer so that when transmission fails the antennas can try to stash the data instead of dumping it to the void.
+	MODULE
+	{
+		name = ModuleScienceContainer
+
+		dataIsCollectable = true
+		dataIsStorable = false
+
+		storageRange = 2
+	}
+}
+
+// If RemoteTech is installed, do their module(s) instead
+@PART[modPartName]:NEEDS[RemoteTech]:FOR[YourModName]
+{
+	// RemoteTech module(s) here
+}
+```
+
+## Useful Formulas
+
+### Per Antenna
+`nominalRange` is a given, and is never calculated
+`maxPowerFactor` is a given, and is never calculated
+`maxTransmitDistance = nominalRange * sqrt(maxPowerFactor)`
+
+### Per Link
+A "link" is any connected pair of antennas.  
+`NominalLinkDistance = sqrt(nominalRange1 * nominalRange2)`  
+`MaxLinkDistance = sqrt(maxTransmitDistance1 * maxTransmitDistance2)`
+
+Therefore, to find the `MaxLinkDistance` from two sets of `nominalRange` and `maxPowerFactor`:  
+`MaxLinkDistance = sqrt(nominalRange1 * sqrt(maxPowerFactor1) * nominalRange2 * sqrt(maxPowerFactor2))`
+
+To find a single antenna's `nominalRange` from a desired `maxTransmitDistance` given its `maxPowerFactor`:  
+`nominalRange = maxTransmitDistance / sqrt(maxPowerFactor)`
+
+To find a single antenna's desired maximum range given the desired maximum link distance and another set `maxTransmitDistance`:  
+`maxTransmitDistance1 = MaxLinkDistance * MaxLinkDistance / maxTransmitDistance2`
+
+Remember that `maxPowerFactor` may differ between antennas (and does, in my lastest configs: longAntenna is 8, mediumDish is 4, commDish is 16).
+
+Currently Kerbin's `maxPowerFactor` is hard-coded as 8.
+
+Feel free to use this spreadsheet for balancing antennas if it's useful to you: https://goo.gl/ChsbfL
+
+## On Balance
+In my configs I've balanced the three stock antennas to cover all of the stock solar system. Since you're introducing five more antennas and working with OPM, you will probably want to change the behavior of the stock parts and diversify the range to gradually cover the whole OPM system. Since you have some parts specifically designed for use in planetary subsystems, their balance when transmitting to other parts is probably more important than their balance when transmitting to Kerbin. For longer range parts designed to make the whole interplanetary leap, the inverse is probably true.
+
+Feel free to ask questions! If anything's unclear or you just want to bounce balance ideas off of me, don't be shy. I'm always happy to help.
+

--- a/RelayDatabase.cs
+++ b/RelayDatabase.cs
@@ -2,7 +2,7 @@
 //
 // RelayDatabase.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -37,27 +37,8 @@
 
 namespace AntennaRange
 {
-	public class RelayDatabase
+	public class RelayDatabase : Singleton<RelayDatabase>
 	{
-		/*
-		 * Static members
-		 * */
-		// Singleton storage
-		protected static RelayDatabase _instance;
-		// Gets the singleton
-		public static RelayDatabase Instance
-		{
-			get
-			{	
-				if (_instance == null)
-				{
-					_instance = new RelayDatabase();
-				}
-
-				return _instance;
-			}
-		}
-
 		/*
 		 * Instance members
 		 * */
@@ -66,16 +47,14 @@
 		 * Fields
 		 * */
 		// Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
-		protected Dictionary<Guid, List<IAntennaRelay>> relayDatabase;
+		private Dictionary<Guid, List<IAntennaRelay>> relayDatabase;
+		private Dictionary<Guid, IAntennaRelay> bestRelayTable;
 
 		// Vessel.id-keyed hash table of part counts, used for caching
-		protected Dictionary<Guid, int> vesselPartCountTable;
-
-		// Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time.
-		public Dictionary<Guid, bool> CheckedVesselsTable;
-
-		protected int cacheHits;
-		protected int cacheMisses;
+		private Dictionary<Guid, int> vesselPartCountTable;
+
+		private int cacheHits;
+		private int cacheMisses;
 
 		/*
 		 * Properties
@@ -113,9 +92,59 @@
 		/* 
 		 * Methods
 		 * */
+		// Remove a vessel from the cache, if it exists.
+		public void DirtyVessel(Vessel vessel)
+		{
+			#if DEBUG
+			Tools.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}",
+				vessel, new System.Diagnostics.StackTrace().ToString());
+			#else
+			Tools.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName);
+			#endif
+
+			this.relayDatabase.Remove(vessel.id);
+			this.vesselPartCountTable.Remove(vessel.id);
+			this.bestRelayTable.Remove(vessel.id);
+		}
+
+		public void ClearCache()
+		{
+			Tools.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache.");
+
+			this.relayDatabase.Clear();
+			this.bestRelayTable.Clear();
+			this.vesselPartCountTable.Clear();
+		}
+
+		// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
+		public bool ContainsKey(Guid key)
+		{
+			return this.relayDatabase.ContainsKey(key);
+		}
+
+		// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
+		public bool ContainsKey(Vessel vessel)
+		{
+			return this.ContainsKey(vessel.id);
+		}
+
+		public IAntennaRelay GetBestVesselRelay(Vessel vessel)
+		{
+			IAntennaRelay relay;
+			if (this.bestRelayTable.TryGetValue(vessel.id, out relay))
+			{
+				return relay;
+			}
+			else
+			{
+				var dump = this[vessel];
+				return null;
+			}
+		}
+
 		// Adds a vessel to the database
 		// The return for this function isn't used yet, but seems useful for potential future API-uses
-		public bool AddVessel(Vessel vessel)
+		private bool AddVessel(Vessel vessel)
 		{
 			// If this vessel is already here...
 			if (this.ContainsKey(vessel))
@@ -146,7 +175,7 @@
 		}
 
 		// Update the vessel's entry in the table
-		public void UpdateVessel(Vessel vessel)
+		private void UpdateVessel(Vessel vessel)
 		{
 			// Squak if the database doesn't have the vessel
 			if (!this.ContainsKey(vessel))
@@ -167,31 +196,6 @@
 			this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
 		}
 
-		// Remove a vessel from the cache, if it exists.
-		public void DirtyVessel(Vessel vessel)
-		{
-			if (this.relayDatabase.ContainsKey(vessel.id))
-			{
-				this.relayDatabase.Remove(vessel.id);
-			}
-			if (this.vesselPartCountTable.ContainsKey(vessel.id))
-			{
-				this.vesselPartCountTable.Remove(vessel.id);
-			}
-		}
-
-		// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
-		public bool ContainsKey(Guid key)
-		{
-			return this.relayDatabase.ContainsKey(key);
-		}
-
-		// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
-		public bool ContainsKey(Vessel vessel)
-		{
-			return this.ContainsKey(vessel.id);
-		}
-
 		// Runs when a vessel is modified (or when we switch to one, to catch docking events)
 		public void onVesselEvent(Vessel vessel)
 		{
@@ -216,18 +220,41 @@
 		}
 
 		// Runs when the player requests a scene change, such as when changing vessels or leaving flight.
-		public void onSceneChange(GameScenes scene)
-		{
-			// If the active vessel is a real thing...
-			if (FlightGlobals.ActiveVessel != null)
-			{
-				// ... dirty its cache
-				this.onVesselEvent(FlightGlobals.ActiveVessel);
-			}
+		private void onSceneChange(GameScenes scene)
+		{
+			Tools.PostDebugMessage(
+				"RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}.  ActiveVessel is {2}",
+				HighLogic.LoadedScene,
+				scene,
+				FlightGlobals.ActiveVessel == null ? "null" : FlightGlobals.ActiveVessel.vesselName
+			);
+
+			if (scene == GameScenes.FLIGHT)
+			{
+				if (scene == HighLogic.LoadedScene)
+				{
+					if (FlightGlobals.ActiveVessel != null)
+					{
+						Tools.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.",
+							FlightGlobals.ActiveVessel.vesselName);
+
+						this.onVesselEvent(FlightGlobals.ActiveVessel);
+					}
+				}
+				else
+				{
+					this.ClearCache();
+				}
+			}
+		}
+
+		private void onGameLoaded(object data)
+		{
+			this.ClearCache();
 		}
 
 		// Runs when parts are undocked
-		public void onPartEvent(Part part)
+		private void onPartEvent(Part part)
 		{
 			if (part != null && part.vessel != null)
 			{
@@ -236,14 +263,14 @@
 		}
 
 		// Runs when parts are coupled, as in docking
-		public void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
+		private void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
 		{
 			this.onPartEvent(data.from);
 			this.onPartEvent(data.to);
 		}
 
 		// Produce a Part-hashed table of relays for the given vessel
-		protected void getVesselRelays(Vessel vessel, ref List<IAntennaRelay> relays)
+		private void getVesselRelays(Vessel vessel, ref List<IAntennaRelay> relays)
 		{
 			// We're going to completely regen this table, so dump the current contents.
 			relays.Clear();
@@ -253,6 +280,10 @@
 				"IAntennaRelay",
 				vessel.vesselName
 			));
+
+			double bestRelayRange = double.NegativeInfinity;
+			IAntennaRelay bestRelay = null;
+			IAntennaRelay relay;
 
 			// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
 			if (vessel.loaded) {
@@ -277,8 +308,16 @@
 						// ...if the module is a relay...
 						if (module is IAntennaRelay)
 						{
+							relay = (module as IAntennaRelay);
+
+							if (relay.maxTransmitDistance > bestRelayRange)
+							{
+								bestRelayRange = relay.maxTransmitDistance;
+								bestRelay = relay;
+							}
+
 							// ...add the module to the table
-							relays.Add(module as IAntennaRelay);
+							relays.Add(relay);
 							// ...neglect relay objects after the first in each part.
 							break;
 						}
@@ -337,14 +376,24 @@
 								module
 							));
 
+							relay = new ProtoAntennaRelay(module as IAntennaRelay, pps);
+
+							if (relay.maxTransmitDistance > bestRelayRange)
+							{
+								bestRelayRange = relay.maxTransmitDistance;
+								bestRelay = relay;
+							}
+
 							// ...build a new ProtoAntennaRelay and add it to the table
-							relays.Add(new ProtoAntennaRelay(module as IAntennaRelay, pps));
+							relays.Add(relay);
 							// ...neglect relay objects after the first in each part.
 							break;
 						}
 					}
 				}
 			}
+
+			this.bestRelayTable[vessel.id] = bestRelay;
 
 			Tools.PostDebugMessage(string.Format(
 				"{0}: vessel '{1}' ({2}) has {3} transmitters.",
@@ -356,12 +405,12 @@
 		}
 
 		// Construct the singleton
-		protected RelayDatabase()
+		private RelayDatabase()
 		{
 			// Initialize the databases
 			this.relayDatabase = new Dictionary<Guid, List<IAntennaRelay>>();
+			this.bestRelayTable = new Dictionary<Guid, IAntennaRelay>();
 			this.vesselPartCountTable = new Dictionary<Guid, int>();
-			this.CheckedVesselsTable = new Dictionary<Guid, bool>();
 
 			this.cacheHits = 0;
 			this.cacheMisses = 0;
@@ -373,6 +422,7 @@
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
 			GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
 			GameEvents.onPartUndock.Add(this.onPartEvent);
+			GameEvents.onGameStateLoad.Add(this.onGameLoaded);
 		}
 
 		~RelayDatabase()
@@ -384,6 +434,7 @@
 			GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
 			GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
 			GameEvents.onPartUndock.Remove(this.onPartEvent);
+			GameEvents.onGameStateLoad.Remove(this.onGameLoaded);
 
 			Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
 
@@ -399,7 +450,7 @@
 		#if DEBUG
 		public void Dump()
 		{
-			StringBuilder sb = new StringBuilder();
+			StringBuilder sb = Tools.GetStringBuilder();
 
 			sb.Append("Dumping RelayDatabase:");
 
@@ -419,6 +470,8 @@
 			}
 
 			Tools.PostDebugMessage(sb.ToString());
+
+			Tools.PutStringBuilder(sb);
 		}
 		#endif
 	}

--- a/RelayExtensions.cs
+++ b/RelayExtensions.cs
@@ -2,7 +2,7 @@
 //
 // Extensions.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -166,25 +166,7 @@
 		/// <param name="vessel">This <see cref="Vessel"/></param>
 		public static IAntennaRelay GetBestRelay(this Vessel vessel)
 		{
-			IAntennaRelay bestRelay = null;
-			double bestScore = double.PositiveInfinity;
-			double relayScore = double.NaN;
-
-			IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel];
-			IAntennaRelay relay;
-			for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
-			{
-				relay = vesselRelays[rIdx];
-				relayScore = relay.transmitDistance / relay.maxTransmitDistance;
-
-				if (relayScore < bestScore)
-				{
-					bestScore = relayScore;
-					bestRelay = relay;
-				}
-			}
-
-			return bestRelay;
+			return RelayDatabase.Instance.GetBestVesselRelay(vessel);
 		}
 	}