Added an XML comment and fixed up some string formats to use the SIFormatter.
Added an XML comment and fixed up some string formats to use the SIFormatter.

--- a/ARConfiguration.cs
+++ b/ARConfiguration.cs
@@ -16,6 +16,18 @@
 	[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";
+		private const string USE_ADDITIVE_KEY = "useAdditiveRanges";
+
+		private const string TRACKING_STATION_RANGES_KEY = "TRACKING_STATION_RANGES";
+		private const string RANGE_KEY = "range";
+
 		/// <summary>
 		/// Indicates whether connections require line of sight.
 		/// </summary>
@@ -59,7 +71,42 @@
 		public static bool PrettyLines
 		{
 			get;
-			private set;
+			set;
+		}
+
+		/// <summary>
+		/// Gets the update delay.
+		/// </summary>
+		public static long UpdateDelay
+		{
+			get;
+			private set;
+		}
+
+		public static bool UseAdditiveRanges
+		{
+			get;
+			private set;
+		}
+
+		/// <summary>
+		/// Gets Kerbin's relay range based on the current tracking station level.
+		/// </summary>
+		public static double KerbinRelayRange
+		{
+			get;
+			private set;
+		}
+
+		/// <summary>
+		/// Gets Kerbin's nominal relay range based on the current tracking station level.
+		/// </summary>
+		public static double KerbinNominalRange
+		{
+			get
+			{
+				return KerbinRelayRange / 2.8284271247461901d;
+			}
 		}
 
 #pragma warning disable 1591
@@ -67,10 +114,17 @@
 		private bool showConfigWindow;
 		private Rect configWindowPos;
 
+		private string updateDelayStr;
+		private long updateDelay;
+
 		private IButton toolbarButton;
 		private ApplicationLauncherButton appLauncherButton;
 
+		private double[] trackingStationRanges;
+
 		private System.Version runningVersion;
+
+		private bool runOnce;
 
 		private KSP.IO.PluginConfiguration _config;
 		private KSP.IO.PluginConfiguration config
@@ -96,25 +150,79 @@
 			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);
+
+			ARConfiguration.UseAdditiveRanges = this.LoadConfigValue(USE_ADDITIVE_KEY, true);
+
+			this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
+			GameEvents.OnKSCFacilityUpgraded.Add(this.onFacilityUpgraded);
 
 			Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion));
 
+			ConfigNode[] tsRangeNodes = GameDatabase.Instance.GetConfigNodes(TRACKING_STATION_RANGES_KEY);
+
+			if (tsRangeNodes.Length > 0)
+			{
+				string[] rangeValues = tsRangeNodes[0].GetValues(RANGE_KEY);
+
+				this.trackingStationRanges = new double[rangeValues.Length];
+
+				for (int idx = 0; idx < rangeValues.Length; idx++)
+				{
+					if (!double.TryParse(rangeValues[idx], out this.trackingStationRanges[idx]))
+					{
+						this.LogError("Could not parse value '{0}' to double; Tracking Station ranges may not work!");
+						this.trackingStationRanges[idx] = 0d;
+					}
+				}
+
+				this.Log("Loaded Tracking Station ranges from config: [{0}]", this.trackingStationRanges.SPrint());
+			}
+			else
+			{
+				this.trackingStationRanges = new double[]
+				{
+					51696576d,
+					37152180000d,
+					224770770000d
+				};
+
+				this.LogWarning("Failed to load Tracking Station ranges from config, using hard-coded values: [{0}]",
+					this.trackingStationRanges.SPrint());
+			}
+
+			this.runOnce = true;
+
 			Tools.PostDebugMessage(this, "Awake.");
+		}
+
+		public void Update()
+		{
+			if (
+				this.runOnce &&
+				(ScenarioUpgradeableFacilities.Instance != null || HighLogic.CurrentGame.Mode != Game.Modes.CAREER)
+			)
+			{
+				this.runOnce = false;
+
+				this.SetKerbinRelayRange();
+			}
 		}
 
 		public void OnGUI()
@@ -167,7 +275,7 @@
 				if (configPos != this.configWindowPos)
 				{
 					this.configWindowPos = configPos;
-					this.SaveConfigValue("configWindowPos", this.configWindowPos);
+					this.SaveConfigValue(WINDOW_POS_KEY, this.configWindowPos);
 				}
 			}
 		}
@@ -182,7 +290,7 @@
 			if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
 			{
 				ARConfiguration.RequireLineOfSight = requireLineOfSight;
-				this.SaveConfigValue("requireLineOfSight", requireLineOfSight);
+				this.SaveConfigValue(REQUIRE_LOS_KEY, requireLineOfSight);
 			}
 
 			GUILayout.EndHorizontal();
@@ -197,7 +305,7 @@
 			if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl)
 			{
 				ARConfiguration.RequireConnectionForControl = requireConnectionForControl;
-				this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl);
+				this.SaveConfigValue(REQUIRE_PROBE_CONNECTION_KEY, requireConnectionForControl);
 			}
 
 			GUILayout.EndHorizontal();
@@ -208,7 +316,18 @@
 			if (fixedPowerCost != ARConfiguration.FixedPowerCost)
 			{
 				ARConfiguration.FixedPowerCost = fixedPowerCost;
-				this.SaveConfigValue("fixedPowerCost", fixedPowerCost);
+				this.SaveConfigValue(FIXED_POWER_KEY, fixedPowerCost);
+			}
+
+			GUILayout.EndHorizontal();
+
+			GUILayout.BeginHorizontal();
+
+			bool useAdditive = GUITools.Toggle(ARConfiguration.UseAdditiveRanges, "Use Additive Ranges");
+			if (useAdditive != ARConfiguration.UseAdditiveRanges)
+			{
+				ARConfiguration.UseAdditiveRanges = useAdditive;
+				this.SaveConfigValue(USE_ADDITIVE_KEY, useAdditive);
 			}
 
 			GUILayout.EndHorizontal();
@@ -219,10 +338,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 +378,7 @@
 				if (newRatio != graceRatio)
 				{
 					ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio);
-					this.SaveConfigValue("graceRatio", newRatio);
+					this.SaveConfigValue(GRACE_RATIO_KEY, newRatio);
 				}
 
 				GUILayout.EndHorizontal();
@@ -257,19 +392,22 @@
 		public void OnDestroy()
 		{
 			GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
+			GameEvents.OnKSCFacilityUpgraded.Remove(this.onFacilityUpgraded);
 
 			if (this.toolbarButton != null)
 			{
 				this.toolbarButton.Destroy();
+				this.toolbarButton = null;
 			}
 
 			if (this.appLauncherButton != null)
 			{
 				ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
-			}
-		}
-
-		protected void onSceneChangeRequested(GameScenes scene)
+				this.appLauncherButton = null;
+			}
+		}
+
+		private void onSceneChangeRequested(GameScenes scene)
 		{
 			if (scene != GameScenes.SPACECENTER)
 			{
@@ -278,9 +416,46 @@
 			}
 		}
 
+		private void onFacilityUpgraded(Upgradeables.UpgradeableFacility fac, int lvl)
+		{
+			if (fac.id == "SpaceCenter/TrackingStation")
+			{
+				this.Log("Caught onFacilityUpgraded for {0} at level {1}", fac.id, lvl);
+				this.SetKerbinRelayRange();
+			}
+		}
+
+		private void SetKerbinRelayRange()
+		{
+			int tsLevel;
+
+			if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER)
+			{
+				tsLevel = ScenarioUpgradeableFacilities.protoUpgradeables["SpaceCenter/TrackingStation"]
+					.facilityRefs[0].FacilityLevel;
+			
+
+			}
+			else
+			{
+				tsLevel = this.trackingStationRanges.Length - 1;
+			}
+
+			if (tsLevel < this.trackingStationRanges.Length && tsLevel >= 0)
+			{
+				KerbinRelayRange = this.trackingStationRanges[tsLevel];
+				this.Log("Setting Kerbin's range to {0}", KerbinRelayRange);
+			}
+			else
+			{
+				this.LogError("Could not set Kerbin's range with invalid Tracking Station level {0}", tsLevel);
+			}
+		}
+
 		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,7 +40,7 @@
 	public class ARFlightController : MonoBehaviour
 	{
 		#region Fields
-		private Dictionary<ConnectionStatus, string> connectionTextures;
+		private Dictionary<ConnectionStatus, string> toolbarTextures;
 		private Dictionary<ConnectionStatus, Texture> appLauncherTextures;
 
 		private ARMapRenderer mapRenderer;
@@ -64,7 +64,7 @@
 		{
 			get
 			{
-				return this.connectionTextures[this.currentConnectionStatus];
+				return this.toolbarTextures[this.currentConnectionStatus];
 			}
 		}
 
@@ -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,10 +145,10 @@
 			{
 				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);
@@ -162,14 +162,6 @@
 
 		private void FixedUpdate()
 		{
-			if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
-			{
-				this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
-					ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW,
-					this.appLauncherTextures[ConnectionStatus.None]
-				);
-			}
-
 			this.log.Clear();
 
 			VesselCommand availableCommand;
@@ -224,7 +216,21 @@
 
 		private void Update()
 		{
-			if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > 83L)
+			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();
 			}
@@ -329,6 +335,14 @@
 		}
 		#endregion
 
+		private void buttonToggle()
+		{
+			if (MapView.MapIsEnabled)
+			{
+				ARConfiguration.PrettyLines = !ARConfiguration.PrettyLines;
+			}
+		}
+
 		#region Event Handlers
 		private void onSceneChangeRequested(GameScenes scene)
 		{

--- 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,
@@ -167,6 +167,7 @@
 
 						if (vesselRelay == null)
 						{
+							log.AppendFormat("\n\tGot null relay for vessel {0}", vessel.vesselName);
 							continue;
 						}
 
@@ -206,7 +207,7 @@
 		{
 			this.Cleanup();
 
-			print("ARMapRenderer: Destroyed.");
+			this.Log("Destroyed");
 		}
 		#endregion
 
@@ -260,7 +261,7 @@
 			}
 			else
 			{
-				if (relay.transmitDistance < relay.nominalTransmitDistance)
+				if (relay.LinkStatus == ConnectionStatus.Optimal)
 				{
 					thisColor = Color.green;
 				}
@@ -273,18 +274,21 @@
 			if (relay.KerbinDirect)
 			{
 				nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
-				relay = null;
 			}
 			else
 			{
-				if (relay.targetRelay == null)
-				{
+				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());
-				relay = relay.targetRelay;
 			}
 
 			renderer.SetColors(thisColor, thisColor);

--- 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,
@@ -90,15 +90,43 @@
 		}
 
 		/// <summary>
-		/// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight.
-		/// </summary>
-		public virtual CelestialBody firstOccludingBody
+		/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
+		/// directly with Kerbin.
+		/// </summary>
+		public virtual bool KerbinDirect
 		{
 			get;
 			protected set;
 		}
 
 		/// <summary>
+		/// Gets or sets the nominal link distance, in meters.
+		/// </summary>
+		public virtual double NominalLinkDistance
+		{
+			get;
+			protected set;
+		}
+
+		/// <summary>
+		/// Gets or sets the maximum link distance, in meters.
+		/// </summary>
+		public virtual double MaximumLinkDistance
+		{
+			get;
+			protected set;
+		}
+
+		/// <summary>
+		/// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight.
+		/// </summary>
+		public virtual CelestialBody firstOccludingBody
+		{
+			get;
+			protected set;
+		}
+
+		/// <summary>
 		/// Gets the transmit distance.
 		/// </summary>
 		/// <value>The transmit distance.</value>
@@ -117,6 +145,12 @@
 			}
 		}
 
+		public virtual ConnectionStatus LinkStatus
+		{
+			get;
+			protected set;
+		}
+
 		/// <summary>
 		/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
 		/// </summary>
@@ -134,16 +168,6 @@
 		{
 			get;
 			set;
-		}
-
-		/// <summary>
-		/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
-		/// directly with Kerbin.
-		/// </summary>
-		public virtual bool KerbinDirect
-		{
-			get;
-			protected set;
 		}
 
 		/// <summary>
@@ -198,10 +222,15 @@
 			this.KerbinDirect = true;
 
 			CelestialBody bodyOccludingBestOccludedRelay = null;
-
-			double nearestRelaySqrDistance = double.PositiveInfinity;
-			double bestOccludedSqrDistance = double.PositiveInfinity;
-			double maxTransmitSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
+			IAntennaRelay needle;
+
+			// double nearestRelaySqrDistance = double.PositiveInfinity;
+			// double bestOccludedSqrDistance = double.PositiveInfinity;
+
+			// double maxTransmitSqrDistance = double.NegativeInfinity;
+
+			double nearestRelaySqrQuotient = double.PositiveInfinity;
+			double bestOccludedSqrQuotient = double.PositiveInfinity;
 
 			/*
 			 * Loop through all the vessels and exclude this vessel, vessels of the wrong type, and vessels that are too
@@ -221,6 +250,7 @@
 				
 				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;
@@ -255,20 +285,36 @@
 					continue;
 				}
 
+				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;
+				}
+
 				// Find the distance from here to the vessel...
 				log.Append("\n\tgetting distance to potential vessel");
 				double potentialSqrDistance = this.sqrDistanceTo(potentialVessel);
 				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\tgetting max link distance to potential relay");
+				double maxLinkSqrDistance;
+
+				if (ARConfiguration.UseAdditiveRanges)
+				{
+					maxLinkSqrDistance = this.maxTransmitDistance * potentialBestRelay.maxTransmitDistance;
+				}
+				else
+				{
+					maxLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
+				}
+
+				log.AppendFormat("\n\tmax link distance: {0}", maxLinkSqrDistance);
+
+				double potentialSqrQuotient = potentialSqrDistance / maxLinkSqrDistance;
 
 				log.Append("\n\t\tdoing LOS check");
 				// Skip vessels to which we do not have line of sight.
@@ -283,12 +329,12 @@
 						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);
+					log.AppendFormat("\n\t\t\tbestOccludedSqrQuotient: {0}", bestOccludedSqrQuotient);
+					log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxLinkSqrDistance);
 
 					if (
-						(potentialSqrDistance < bestOccludedSqrDistance) &&
-						(potentialSqrDistance < maxTransmitSqrDistance) &&
+						(potentialSqrQuotient < bestOccludedSqrQuotient) &&
+						(potentialSqrQuotient <= 1d) &&
 						potentialBestRelay.CanTransmit()
 					)
 					{
@@ -297,7 +343,7 @@
 
 						this.bestOccludedRelay = potentialBestRelay;
 						bodyOccludingBestOccludedRelay = fob;
-						bestOccludedSqrDistance = potentialSqrDistance;
+						bestOccludedSqrQuotient = potentialSqrQuotient;
 					}
 					else
 					{
@@ -312,7 +358,7 @@
 				/*
 				 * ...so that we can skip the vessel if it is further away than a vessel we've already checked.
 				 * */
-				if (potentialSqrDistance > nearestRelaySqrDistance)
+				if (potentialSqrQuotient > nearestRelaySqrQuotient)
 				{
 					
 					log.AppendFormat("\n\t{0}: Vessel {1} discarded because it is farther than another the nearest relay.",
@@ -324,19 +370,75 @@
 
 				log.Append("\n\t\t...passed distance check");
 
-				if (
-					potentialBestRelay.CanTransmit() &&
-					(potentialBestRelay.targetRelay == null || potentialBestRelay.targetRelay.vessel != this.vessel))
-				{
-					// @TODO: Moved this here from outside the loop; why was it there?
-					nearestRelaySqrDistance = potentialSqrDistance;
-					this.nearestRelay = potentialBestRelay;
-
-					log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m)",
-						this.ToString(),
-						this.nearestRelay.ToString(),
-						Math.Sqrt(nearestRelaySqrDistance)
-					);
+				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)
+					{
+						nearestRelaySqrQuotient = potentialSqrQuotient;
+						this.nearestRelay = potentialBestRelay;
+
+						log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m²)",
+							this.ToString(),
+							this.nearestRelay.ToString(),
+							Math.Sqrt(nearestRelaySqrQuotient)
+						);
+					}
+					else
+					{
+						log.AppendFormat("\n\t\t...connection to {0} would result in a circular network, skipping",
+							potentialBestRelay
+						);
+					}
 				}
 			}
 
@@ -345,20 +447,45 @@
 			double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
 			kerbinSqrDistance *= kerbinSqrDistance;
 
+			double kerbinSqrQuotient;
+
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				kerbinSqrQuotient = kerbinSqrDistance /
+					(this.maxTransmitDistance * ARConfiguration.KerbinRelayRange);
+			}
+			else
+			{
+				kerbinSqrQuotient = kerbinSqrDistance /
+					(this.maxTransmitDistance * this.maxTransmitDistance);
+			}
+
 			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(),
+				nearestRelaySqrQuotient,
+				this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
+				bestOccludedSqrQuotient,
+				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);
 
 				// nearestRelaySqrDistance will be infinity if all relays are occluded or none exist.
 				// Therefore, this will only be true if a valid relay is in range.
-				if (nearestRelaySqrDistance <= maxTransmitSqrDistance)
+				if (nearestRelaySqrQuotient <= 1d)
 				{
 					log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
 						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrDistance, maxTransmitSqrDistance);
+						nearestRelaySqrQuotient, 1d);
 
 					this.KerbinDirect = false;
 					this.canTransmit = true;
@@ -369,13 +496,13 @@
 				{
 					log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
 						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrDistance, maxTransmitSqrDistance);
+						nearestRelaySqrQuotient, 1d);
 
 					this.canTransmit = false;
 
 					// If the best occluded relay is closer than Kerbin, check it against the nearest relay.
 					// Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
-					if (bestOccludedSqrDistance < kerbinSqrDistance)
+					if (bestOccludedSqrQuotient < kerbinSqrQuotient)
 					{
 						log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
 							bestOccludedRelay, kerbinSqrDistance);
@@ -384,19 +511,19 @@
 
 						// If the nearest relay is closer than the best occluded relay, pick it.
 						// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
-						if (nearestRelaySqrDistance < bestOccludedSqrDistance)
+						if (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
 						{
 							log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-								nearestRelaySqrDistance, bestOccludedSqrDistance);
+								nearestRelaySqrQuotient, bestOccludedSqrQuotient);
 							
-							this.targetRelay = nearestRelay;
+							this.targetRelay = this.nearestRelay;
 							this.firstOccludingBody = null;
 						}
 						// Otherwise, target the best occluded relay.
 						else
 						{
 							log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
-								nearestRelaySqrDistance, bestOccludedSqrDistance);
+								nearestRelaySqrQuotient, bestOccludedSqrQuotient);
 							
 							this.targetRelay = bestOccludedRelay;
 							this.firstOccludingBody = bodyOccludingBestOccludedRelay;
@@ -409,25 +536,25 @@
 						log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
 							bestOccludedRelay, kerbinSqrDistance);
 						
-						this.firstOccludingBody = null;
-
 						// If the nearest relay is closer than Kerbin, pick it.
 						// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
-						if (nearestRelaySqrDistance < kerbinSqrDistance)
+						if (nearestRelaySqrQuotient < kerbinSqrQuotient)
 						{
 							log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-								nearestRelaySqrDistance, kerbinSqrDistance);
+								nearestRelaySqrQuotient, kerbinSqrQuotient);
 							
 							this.KerbinDirect = false;
-							this.targetRelay = nearestRelay;
+							this.firstOccludingBody = null;
+							this.targetRelay = this.nearestRelay;
 						}
 						// Otherwise, pick Kerbin.
 						else
 						{
 							log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
-								nearestRelaySqrDistance, kerbinSqrDistance);
+								nearestRelaySqrQuotient, kerbinSqrQuotient);
 							
 							this.KerbinDirect = true;
+							this.firstOccludingBody = bodyOccludingKerbin;
 							this.targetRelay = null;
 						}
 					}
@@ -439,20 +566,20 @@
 				log.AppendFormat("\n\tKerbin is in LOS.");
 
 				// If the nearest relay is closer than Kerbin and in range, transmit to it.
-				if (nearestRelaySqrDistance <= maxTransmitSqrDistance)
+				if (nearestRelaySqrQuotient <= 1d)
 				{
 					log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
 						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrDistance, maxTransmitSqrDistance);
+						nearestRelaySqrQuotient, 1d);
 
 					this.canTransmit = true;
 
 					// If the nearestRelay is closer than Kerbin, use it.
-					if (nearestRelaySqrDistance < kerbinSqrDistance)
+					if (nearestRelaySqrQuotient < kerbinSqrQuotient)
 					{
 						log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).",
 							this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrDistance, kerbinSqrDistance);
+							nearestRelaySqrQuotient, kerbinSqrQuotient);
 
 						this.KerbinDirect = false;
 						this.targetRelay = this.nearestRelay;
@@ -462,7 +589,7 @@
 					{
 						log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",
 							this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-								nearestRelaySqrDistance, kerbinSqrDistance);
+							nearestRelaySqrQuotient, kerbinSqrQuotient);
 
 						this.KerbinDirect = true;
 						this.targetRelay = null;
@@ -473,13 +600,13 @@
 				{
 					log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
 						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrDistance, maxTransmitSqrDistance);
+							nearestRelaySqrQuotient, 1d);
 
 					// If Kerbin is in range, use it.
-					if (kerbinSqrDistance <= maxTransmitSqrDistance)
+					if (kerbinSqrQuotient <= 1d)
 					{
 						log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).",
-							kerbinSqrDistance, maxTransmitSqrDistance);
+							kerbinSqrQuotient, 1d);
 
 						this.canTransmit = true;
 						this.KerbinDirect = true;
@@ -490,13 +617,13 @@
 					else
 					{
 						log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).",
-							kerbinSqrDistance, maxTransmitSqrDistance);
+								kerbinSqrQuotient, 1d);
 
 						this.canTransmit = false;
 
 						// If the best occluded relay is closer than Kerbin, check it against the nearest relay.
 						// Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
-						if (bestOccludedSqrDistance < kerbinSqrDistance)
+						if (bestOccludedSqrQuotient < kerbinSqrQuotient)
 						{
 							log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
 								bestOccludedRelay, kerbinSqrDistance);
@@ -505,19 +632,19 @@
 
 							// If the nearest relay is closer than the best occluded relay, pick it.
 							// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
-							if (nearestRelaySqrDistance < bestOccludedSqrDistance)
+							if (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
 							{
 								log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-									nearestRelaySqrDistance, bestOccludedSqrDistance);
+									nearestRelaySqrQuotient, bestOccludedSqrQuotient);
 								
-								this.targetRelay = nearestRelay;
+								this.targetRelay = this.nearestRelay;
 								this.firstOccludingBody = null;
 							}
 							// Otherwise, target the best occluded relay.
 							else
 							{
 								log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
-									nearestRelaySqrDistance, bestOccludedSqrDistance);
+									nearestRelaySqrQuotient, bestOccludedSqrQuotient);
 								
 								this.targetRelay = bestOccludedRelay;
 								this.firstOccludingBody = bodyOccludingBestOccludedRelay;
@@ -534,19 +661,19 @@
 
 							// If the nearest relay is closer than Kerbin, pick it.
 							// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
-							if (nearestRelaySqrDistance < kerbinSqrDistance)
+							if (nearestRelaySqrQuotient < kerbinSqrQuotient)
 							{
 								log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-									nearestRelaySqrDistance, kerbinSqrDistance);
+									nearestRelaySqrQuotient, kerbinSqrQuotient);
 								
 								this.KerbinDirect = false;
-								this.targetRelay = nearestRelay;
+								this.targetRelay = this.nearestRelay;
 							}
 							// Otherwise, pick Kerbin.
 							else
 							{
 								log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
-									nearestRelaySqrDistance, kerbinSqrDistance);
+									nearestRelaySqrQuotient, kerbinSqrQuotient);
 								
 								this.KerbinDirect = true;
 								this.targetRelay = null;
@@ -554,6 +681,41 @@
 						}
 					}
 				}
+			}
+
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				if (this.KerbinDirect)
+				{
+					this.NominalLinkDistance = Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange);
+					this.MaximumLinkDistance = Math.Sqrt(this.maxTransmitDistance * ARConfiguration.KerbinRelayRange);
+				}
+				else
+				{
+					this.NominalLinkDistance = Math.Sqrt(this.nominalTransmitDistance * this.targetRelay.nominalTransmitDistance);
+					this.MaximumLinkDistance = Math.Sqrt(this.maxTransmitDistance * this.targetRelay.maxTransmitDistance);
+				}
+			}
+			else
+			{
+				this.NominalLinkDistance = this.nominalTransmitDistance;
+				this.MaximumLinkDistance = this.maxTransmitDistance;
+			}
+
+			if (this.canTransmit)
+			{
+				if (this.transmitDistance < this.NominalLinkDistance)
+				{
+					this.LinkStatus = ConnectionStatus.Optimal;
+				}
+				else
+				{
+					this.LinkStatus = ConnectionStatus.Suboptimal;
+				}
+			}
+			else
+			{
+				this.LinkStatus = ConnectionStatus.None;
 			}
 
 			log.AppendFormat("\n{0}: Target search and status determination complete.", 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,
@@ -42,7 +42,8 @@
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
-		nominalRange = 1500000
+		nominalRange = 6364
+		simpleRange = 20500000
 		maxPowerFactor = 8
 		maxDataFactor = 4
 	}
@@ -63,9 +64,10 @@
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
-		nominalRange = 30000000
-		maxPowerFactor = 8
-		maxDataFactor = 4
+		nominalRange = 3500000000
+		simpleRange = 18000000000
+		maxPowerFactor = 4
+		maxDataFactor = 8
 	}
 
 	MODULE
@@ -84,9 +86,11 @@
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
-		nominalRange = 80000000000
-		maxPowerFactor = 8
-		maxDataFactor = 4
+		@packetResourceCost /= 1.414213
+		nominalRange = 10000000000
+		simpleRange = 56250000000
+		maxPowerFactor = 16
+		maxDataFactor = 2
 	}
 
 	MODULE
@@ -100,11 +104,18 @@
 	}
 }
 
+TRACKING_STATION_RANGES
+{
+	range = 800000
+	range = 200000000000
+	range = 2250000000000
+}
+
 EVA_MODULE
 {
 	name = ModuleLimitedDataTransmitter
 
-	nominalRange = 5000
+	nominalRange = 1389
 	maxPowerFactor = 1
 	maxDataFactor = 1
 

--- 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,
@@ -47,9 +47,20 @@
 		IAntennaRelay targetRelay { get; }
 
 		/// <summary>
+		/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
+		/// directly with Kerbin.
+		/// </summary>
+		bool KerbinDirect { get; }
+
+		double NominalLinkDistance { get; }
+		double MaximumLinkDistance { get; }
+
+		/// <summary>
 		/// Gets the distance to the nearest relay or Kerbin, whichever is closer.
 		/// </summary>
 		double transmitDistance { get; }
+
+		ConnectionStatus LinkStatus { get; }
 
 		/// <summary>
 		/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
@@ -65,12 +76,6 @@
 		/// The first CelestialBody blocking line of sight to a 
 		/// </summary>
 		CelestialBody firstOccludingBody { get; }
-
-		/// <summary>
-		/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
-		/// directly with Kerbin.
-		/// </summary>
-		bool KerbinDirect { get; }
 
 		/// <summary>
 		/// Gets the Part title.

--- 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,
@@ -61,13 +61,20 @@
 		private ScreenMessage ErrorMsg;
 
 		/// <summary>
-		/// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
-		/// and packetSize.
+		/// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as
+		/// prescribed by packetResourceCost and packetSize.
 		/// </summary>
 		[KSPField(isPersistant = false)]
 		public double nominalRange;
 
 		/// <summary>
+		/// When additive ranges are disabled, the distance from Kerbin at which the antenna will perform exactly as
+		/// prescribed by packetResourceCost and packetSize.
+		/// </summary>
+		[KSPField(isPersistant = false)]
+		public double simpleRange;
+
+		/// <summary>
 		/// Relay status string for use in action menus.
 		/// </summary>
 		[KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
@@ -86,9 +93,15 @@
 		public string UItransmitDistance;
 
 		/// <summary>
+		/// The nominal range string for use in action menus.
+		/// </summary>
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Nominal Range")]
+		public string UInominalLinkDistance;
+
+		/// <summary>
 		/// Maximum distance string for use in action menus.
 		/// </summary>
-		[KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")]
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Range")]
 		public string UImaxTransmitDistance;
 
 		/// <summary>
@@ -149,6 +162,14 @@
 				{
 					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.");
@@ -174,6 +195,55 @@
 		}
 
 		/// <summary>
+		/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
+		/// directly with Kerbin.
+		/// </summary>
+		public bool KerbinDirect
+		{
+			get
+			{
+				if (this.relay != null)
+				{
+					return this.relay.KerbinDirect;
+				}
+
+				return false;
+			}
+		}
+
+		/// <summary>
+		/// Gets or sets the nominal link distance, in meters.
+		/// </summary>
+		public double NominalLinkDistance
+		{
+			get
+			{
+				if (this.relay != null)
+				{
+					return this.relay.NominalLinkDistance;
+				}
+
+				return 0d;
+			}
+		}
+
+		/// <summary>
+		/// Gets or sets the maximum link distance, in meters.
+		/// </summary>
+		public double MaximumLinkDistance
+		{
+			get
+			{
+				if (this.relay != null)
+				{
+					return this.relay.MaximumLinkDistance;
+				}
+
+				return 0d;
+			}
+		}
+
+		/// <summary>
 		/// Gets the distance to the nearest relay or Kerbin, whichever is closer.
 		/// </summary>
 		public double transmitDistance
@@ -190,13 +260,36 @@
 		}
 
 		/// <summary>
+		/// Gets the link status.
+		/// </summary>
+		public ConnectionStatus LinkStatus
+		{
+			get
+			{
+				if (this.relay == null)
+				{
+					return ConnectionStatus.None;
+				}
+
+				return this.relay.LinkStatus;
+			}
+		}
+
+		/// <summary>
 		/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
 		/// </summary>
 		public double nominalTransmitDistance
 		{
 			get
 			{
-				return this.nominalRange;
+				if (ARConfiguration.UseAdditiveRanges)
+				{
+					return this.nominalRange;
+				}
+				else
+				{
+					return this.simpleRange;
+				}
 			}
 		}
 
@@ -205,11 +298,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>
@@ -292,23 +382,6 @@
 		}
 
 		/// <summary>
-		/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
-		/// directly with Kerbin.
-		/// </summary>
-		public bool KerbinDirect
-		{
-			get
-			{
-				if (this.relay != null)
-				{
-					return this.relay.KerbinDirect;
-				}
-
-				return false;
-			}
-		}
-
-		/// <summary>
 		/// Gets the Part title.
 		/// </summary>
 		public string Title
@@ -348,13 +421,13 @@
 				"{0} loaded:\n" +
 				"packetSize: {1}\n" +
 				"packetResourceCost: {2}\n" +
-				"nominalRange: {3}\n" +
+				"nominalTransmitDistance: {3}\n" +
 				"maxPowerFactor: {4}\n" +
 				"maxDataFactor: {5}\n",
 				this.name,
 				base.packetSize,
 				this._basepacketResourceCost,
-				this.nominalRange,
+				this.nominalTransmitDistance,
 				this.maxPowerFactor,
 				this.maxDataFactor
 			));
@@ -370,11 +443,13 @@
 
 			if (state >= StartState.PreLaunch)
 			{
+				this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
+
 				this.relay = new AntennaRelay(this);
+				this.relay.nominalTransmitDistance = this.nominalTransmitDistance;
 				this.relay.maxTransmitDistance = this.maxTransmitDistance;
-				this.relay.nominalTransmitDistance = this.nominalRange;
-
-				this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
+
+				this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.maxTransmitDistance);
 
 				GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
 				GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
@@ -393,6 +468,8 @@
 			base.Fields.Load(node);
 
 			base.OnLoad (node);
+
+			this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
 		}
 
 		/// <summary>
@@ -400,9 +477,17 @@
 		/// </summary>
 		public override string GetInfo()
 		{
-			string text = base.GetInfo();
-			text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";
-			text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n";
+			StringBuilder sb = Tools.GetStringBuilder();
+			string text;
+
+			sb.Append(base.GetInfo());
+			sb.AppendFormat(Tools.SIFormatter, "Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
+			sb.AppendFormat(Tools.SIFormatter, "Maximum Range: {0:S3}m\n", this.maxTransmitDistance);
+
+			text = sb.ToString();
+
+			Tools.PutStringBuilder(sb);
+
 			return text;
 		}
 
@@ -626,24 +711,28 @@
 		{
 			if (this.actionUIUpdate)
 			{
+				this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.MaximumLinkDistance);
+				this.UInominalLinkDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.NominalLinkDistance);
+				
 				if (this.CanTransmit())
 				{
-					this.UIrelayStatus = "Connected";
-					this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
-					this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT";
-					this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E";
+					this.UIrelayStatus = this.LinkStatus.ToString();
+					this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.transmitDistance);
+					this.UIpacketSize = string.Format(Tools.SIFormatter, "{0:S3}MiT", this.DataRate);
+					this.UIpacketCost = string.Format(Tools.SIFormatter, "{0:S3}EC", this.DataResourceCost);
 				}
 				else
 				{
 					if (this.relay.firstOccludingBody == null)
 					{
+						this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.transmitDistance);
 						this.UIrelayStatus = "Out of range";
 					}
 					else
 					{
+						this.UItransmitDistance = "N/A";
 						this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
 					}
-					this.UImaxTransmitDistance = "N/A";
 					this.UIpacketSize = "N/A";
 					this.UIpacketCost = "N/A";
 				}
@@ -736,13 +825,13 @@
 		// transmission fails (see CanTransmit).
 		private void PreTransmit_SetPacketResourceCost()
 		{
-			if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.nominalRange)
+			if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.NominalLinkDistance)
 			{
 				base.packetResourceCost = this._basepacketResourceCost;
 			}
 			else
 			{
-				float rangeFactor = (float)(this.transmitDistance / this.nominalRange);
+				float rangeFactor = (float)(this.transmitDistance / this.NominalLinkDistance);
 				rangeFactor *= rangeFactor;
 
 				base.packetResourceCost = this._basepacketResourceCost
@@ -756,13 +845,13 @@
 		// distance.  packetSize maxes out at _basepacketSize * maxDataFactor.
 		private void PreTransmit_SetPacketSize()
 		{
-			if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.nominalRange)
+			if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.NominalLinkDistance)
 			{
 				base.packetSize = this._basepacketSize;
 			}
 			else
 			{
-				float rangeFactor = (float)(this.nominalRange / this.transmitDistance);
+				float rangeFactor = (float)(this.NominalLinkDistance / this.transmitDistance);
 				rangeFactor *= rangeFactor;
 
 				base.packetSize = Mathf.Min(

--- 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.*")]
 // 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,17 +48,24 @@
 		{
 			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.PostLogMessage("{0}: Could not fetch vessel!  {1}{2}{3}",
+					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
+						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;
 				}

--- 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
-		private static RelayDatabase _instance;
-		// Gets the singleton
-		public static RelayDatabase Instance
-		{
-			get
-			{	
-				if (_instance == null)
-				{
-					_instance = new RelayDatabase();
-				}
-
-				return _instance;
-			}
-		}
-
 		/*
 		 * Instance members
 		 * */
@@ -72,9 +53,6 @@
 		// Vessel.id-keyed hash table of part counts, used for caching
 		private 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;
-
 		private int cacheHits;
 		private int cacheMisses;
 
@@ -117,9 +95,25 @@
 		// 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.relayDatabase.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.
@@ -228,12 +222,35 @@
 		// Runs when the player requests a scene change, such as when changing vessels or leaving flight.
 		private void onSceneChange(GameScenes scene)
 		{
-			// If the active vessel is a real thing...
-			if (FlightGlobals.ActiveVessel != null)
-			{
-				// ... dirty its cache
-				this.onVesselEvent(FlightGlobals.ActiveVessel);
-			}
+			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
@@ -394,7 +411,6 @@
 			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;
@@ -406,6 +422,7 @@
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
 			GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
 			GameEvents.onPartUndock.Add(this.onPartEvent);
+			GameEvents.onGameStateLoad.Add(this.onGameLoaded);
 		}
 
 		~RelayDatabase()
@@ -417,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.");
 

--- 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,
@@ -38,6 +38,50 @@
 	public static class RelayExtensions
 	{
 		/// <summary>
+		/// Returns the distance between two IAntennaRelays.
+		/// </summary>
+		/// <param name="relayOne">Relay one.</param>
+		/// <param name="relayTwo">Relay two.</param>
+		public static double DistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo)
+		{
+			return relayOne.vessel.DistanceTo(relayTwo.vessel);
+		}
+
+		/// <summary>
+		/// Returns the distance from this IAntennaRelay to the given CelestialBody
+		/// </summary>
+		/// <param name="relay">Relay.</param>
+		/// <param name="body">Body.</param>
+		public static double SqrDistanceTo(this IAntennaRelay relay, CelestialBody body)
+		{
+			double range = relay.vessel.DistanceTo(body) - body.Radius;
+
+			return range * range;
+		}
+
+		/// <summary>
+		/// Returns the distance between two IAntennaRelays.
+		/// </summary>
+		/// <param name="relayOne">Relay one.</param>
+		/// <param name="relayTwo">Relay two.</param>
+		public static double SqrDistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo)
+		{
+			return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
+		}
+
+		/// <summary>
+		/// Returns the distance from this IAntennaRelay to the given CelestialBody
+		/// </summary>
+		/// <param name="relay">Relay.</param>
+		/// <param name="body">Body.</param>
+		public static double DistanceTo(this IAntennaRelay relay, CelestialBody body)
+		{
+			double range = relay.vessel.DistanceTo(body) - body.Radius;
+
+			return range;
+		}
+
+		/// <summary>
 		/// Returns the distance between this IAntennaRelay and a Vessel
 		/// </summary>
 		/// <param name="relay">This <see cref="IAntennaRelay"/></param>
@@ -140,10 +184,11 @@
 			for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
 			{
 				relay = vesselRelays[rIdx];
-				if (relay.CanTransmit())
+				if (relay.LinkStatus > ConnectionStatus.None)
 				{
 					canTransmit = true;
-					if (relay.transmitDistance <= relay.nominalTransmitDistance)
+
+					if (relay.LinkStatus == ConnectionStatus.Optimal)
 					{
 						return ConnectionStatus.Optimal;
 					}