Probably actually resolving by cost now.
Probably actually resolving by cost now.

--- a/ARConfiguration.cs
+++ b/ARConfiguration.cs
@@ -4,8 +4,12 @@
 // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
 
 using KSP;
+using KSP.UI.Screens;
 using System;
-using ToadicusTools;
+using ToadicusTools.Extensions;
+using ToadicusTools.Text;
+using ToadicusTools.GUIUtils;
+using ToadicusTools.Wrappers;
 using UnityEngine;
 
 namespace AntennaRange
@@ -28,6 +32,8 @@
 		private const string TRACKING_STATION_RANGES_KEY = "TRACKING_STATION_RANGES";
 		private const string RANGE_KEY = "range";
 
+		private const string USE_TOOLBAR_KEY = "useToolbarIfAvailable";
+
 		/// <summary>
 		/// Indicates whether connections require line of sight.
 		/// </summary>
@@ -83,6 +89,9 @@
 			private set;
 		}
 
+		/// <summary>
+		/// Gets a value indicating whether AntennaRange will use additive ranges.
+		/// </summary>
 		public static bool UseAdditiveRanges
 		{
 			get;
@@ -107,6 +116,12 @@
 			{
 				return KerbinRelayRange / 2.8284271247461901d;
 			}
+		}
+
+		public static bool UseToolbarIfAvailable
+		{
+			get;
+			private set;
 		}
 
 #pragma warning disable 1591
@@ -142,14 +157,13 @@
 
 		public void Awake()
 		{
-			Tools.PostDebugMessage(this, "Waking up.");
+			this.LogDebug("Waking up.");
 
 			this.runningVersion = this.GetType().Assembly.GetName().Version;
 
 			this.showConfigWindow = false;
 			this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15);
 
-
 			this.configWindowPos = this.LoadConfigValue(WINDOW_POS_KEY, this.configWindowPos);
 
 			ARConfiguration.RequireLineOfSight = this.LoadConfigValue(REQUIRE_LOS_KEY, false);
@@ -168,7 +182,12 @@
 
 			ARConfiguration.UseAdditiveRanges = this.LoadConfigValue(USE_ADDITIVE_KEY, true);
 
+			ARConfiguration.PrettyLines = this.LoadConfigValue(PRETTY_LINES_KEY, true);
+
+			ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L);
 			this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
+
+			ARConfiguration.UseToolbarIfAvailable = this.LoadConfigValue(USE_TOOLBAR_KEY, true);
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
 			GameEvents.OnKSCFacilityUpgraded.Add(this.onFacilityUpgraded);
@@ -209,7 +228,7 @@
 
 			this.runOnce = true;
 
-			Tools.PostDebugMessage(this, "Awake.");
+			this.LogDebug("Awake.");
 		}
 
 		public void Update()
@@ -228,11 +247,17 @@
 		public void OnGUI()
 		{
 			// Only runs once, if the Toolbar is available.
-			if (ToolbarManager.ToolbarAvailable)
+			if (ToolbarManager.ToolbarAvailable && ARConfiguration.UseToolbarIfAvailable)
 			{
 				if (this.toolbarButton == null)
 				{
-					Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button.");
+					this.LogDebug("Toolbar available; initializing toolbar button.");
+
+					if (this.appLauncherButton != null)
+					{
+						ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
+						this.appLauncherButton = null;
+					}
 
 					this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
 					this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
@@ -247,7 +272,13 @@
 			}
 			else if (this.appLauncherButton == null && ApplicationLauncher.Ready)
 			{
-				Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button.");
+				if (this.toolbarButton != null)
+				{
+					this.toolbarButton.Destroy();
+					this.toolbarButton = null;
+				}
+
+				this.LogDebug("Toolbar available; initializing AppLauncher button.");
 
 				this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
 					this.toggleConfigWindow,
@@ -270,7 +301,7 @@
 					GUILayout.ExpandWidth(true)
 				);
 
-				configPos = Tools.ClampRectToScreen(configPos, 20);
+				configPos = WindowTools.ClampRectToScreen(configPos, 20);
 
 				if (configPos != this.configWindowPos)
 				{
@@ -286,7 +317,7 @@
 
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 
-			bool requireLineOfSight = GUITools.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight");
+			bool requireLineOfSight = Layout.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight");
 			if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
 			{
 				ARConfiguration.RequireLineOfSight = requireLineOfSight;
@@ -298,7 +329,7 @@
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 
 			bool requireConnectionForControl =
-				GUITools.Toggle(
+				Layout.Toggle(
 					ARConfiguration.RequireConnectionForControl,
 					"Require Connection for Probe Control"
 				);
@@ -312,7 +343,7 @@
 
 			GUILayout.BeginHorizontal();
 
-			bool fixedPowerCost = GUITools.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost");
+			bool fixedPowerCost = Layout.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost");
 			if (fixedPowerCost != ARConfiguration.FixedPowerCost)
 			{
 				ARConfiguration.FixedPowerCost = fixedPowerCost;
@@ -323,7 +354,7 @@
 
 			GUILayout.BeginHorizontal();
 
-			bool useAdditive = GUITools.Toggle(ARConfiguration.UseAdditiveRanges, "Use Additive Ranges");
+			bool useAdditive = Layout.Toggle(ARConfiguration.UseAdditiveRanges, "Use Additive Ranges");
 			if (useAdditive != ARConfiguration.UseAdditiveRanges)
 			{
 				ARConfiguration.UseAdditiveRanges = useAdditive;
@@ -334,11 +365,22 @@
 
 			GUILayout.BeginHorizontal();
 
-			bool prettyLines = GUITools.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines");
+			bool prettyLines = Layout.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines");
 			if (prettyLines != ARConfiguration.PrettyLines)
 			{
 				ARConfiguration.PrettyLines = prettyLines;
 				this.SaveConfigValue(PRETTY_LINES_KEY, prettyLines);
+			}
+
+			GUILayout.EndHorizontal();
+
+			GUILayout.BeginHorizontal();
+
+			bool useToolbar = Layout.Toggle(ARConfiguration.UseToolbarIfAvailable, "Use Blizzy's Toolbar, if Available");
+			if (useToolbar != ARConfiguration.UseToolbarIfAvailable)
+			{
+				ARConfiguration.UseToolbarIfAvailable = useToolbar;
+				this.SaveConfigValue(USE_TOOLBAR_KEY, useToolbar);
 			}
 
 			GUILayout.EndHorizontal();
@@ -433,8 +475,6 @@
 			{
 				tsLevel = ScenarioUpgradeableFacilities.protoUpgradeables["SpaceCenter/TrackingStation"]
 					.facilityRefs[0].FacilityLevel;
-			
-
 			}
 			else
 			{

--- a/ARFlightController.cs
+++ b/ARFlightController.cs
@@ -29,9 +29,13 @@
 #pragma warning disable 1591
 
 using KSP;
+using KSP.UI.Screens;
 using System;
 using System.Collections.Generic;
-using ToadicusTools;
+using ToadicusTools.Extensions;
+using ToadicusTools.Text;
+using ToadicusTools.DebugTools;
+using ToadicusTools.Wrappers;
 using UnityEngine;
 
 namespace AntennaRange
@@ -39,6 +43,11 @@
 	[KSPAddon(KSPAddon.Startup.Flight, false)]
 	public class ARFlightController : MonoBehaviour
 	{
+		#region Static
+		private static List<IAntennaRelay> usefulRelays;
+		public static IList<IAntennaRelay> UsefulRelays;
+		#endregion
+
 		#region Fields
 		private Dictionary<ConnectionStatus, string> toolbarTextures;
 		private Dictionary<ConnectionStatus, Texture> appLauncherTextures;
@@ -48,7 +57,7 @@
 		private IButton toolbarButton;
 
 		private ApplicationLauncherButton appLauncherButton;
-		private Tools.DebugLogger log;
+		private PooledDebugLogger log;
 
 		private System.Diagnostics.Stopwatch updateTimer;
 		#endregion
@@ -122,7 +131,7 @@
 		{
 			this.lockID = "ARConnectionRequired";
 
-			this.log = Tools.DebugLogger.New(this);
+			this.log = PooledDebugLogger.New(this);
 
 			this.updateTimer = new System.Diagnostics.Stopwatch();
 
@@ -141,7 +150,7 @@
 			this.appLauncherTextures[ConnectionStatus.Optimal] =
 				GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false);
 
-			if (ToolbarManager.ToolbarAvailable)
+			if (ToolbarManager.ToolbarAvailable && ARConfiguration.UseToolbarIfAvailable)
 			{
 				this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
 
@@ -153,11 +162,9 @@
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
 			GameEvents.onVesselChange.Add(this.onVesselChange);
-		}
-
-		private void Start()
-		{
-			this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
+
+			usefulRelays = new List<IAntennaRelay>();
+			UsefulRelays = usefulRelays.AsReadOnly();
 		}
 
 		private void FixedUpdate()
@@ -216,6 +223,11 @@
 
 		private void Update()
 		{
+			if (MapView.MapIsEnabled && this.mapRenderer == null)
+			{
+				this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
+			}
+
 			if (this.toolbarButton != null)
 			{
 				this.toolbarButton.Enabled = MapView.MapIsEnabled;
@@ -241,12 +253,17 @@
 
 			this.log.Clear();
 
-			if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ActiveVessel != null)
+			this.log.Append("[ARFlightController]: Update");
+
+			if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ready && FlightGlobals.ActiveVessel != null)
 			{
 				Vessel vessel;
 				IAntennaRelay relay;
+				IAntennaRelay bestActiveRelay = null;
 				IList<IAntennaRelay> activeVesselRelays;
 
+				usefulRelays.Clear();
+
 				for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
 				{
 					vessel = FlightGlobals.Vessels[vIdx];
@@ -256,33 +273,78 @@
 						continue;
 					}
 
-					log.AppendFormat("Fetching best relay for vessel {0}", vessel);
+					switch (vessel.vesselType)
+					{
+						case VesselType.Debris:
+						case VesselType.Flag:
+						case VesselType.Unknown:
+							continue;
+					}
+
+					log.AppendFormat("\nFetching best relay for vessel {0}", vessel);
 
 					relay = vessel.GetBestRelay();
 
 					if (relay != null)
 					{
-						log.AppendFormat("Finding nearest relay for best relay {0}", relay);
-
-						relay.FindNearestRelay();
+						log.AppendFormat("\n\tAdding useful relay {0}", relay);
+
+						usefulRelays.Add(relay);
 					}
 				}
 
 				activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel];
+
+				if (activeVesselRelays.Count > 0)
+				{
+					bestActiveRelay = RelayDatabase.Instance.GetBestVesselRelay(FlightGlobals.ActiveVessel);
+
+					log.AppendFormat("\n\tAdding best active vessel relay {0} to usefulRelays", bestActiveRelay);
+
+					usefulRelays.Add(bestActiveRelay);
+				}
+
+				log.AppendFormat("\n\tDoing target searches for {0} useful relays", usefulRelays.Count);
+
+				for (int uIdx = 0; uIdx < usefulRelays.Count; uIdx++)
+				{
+					relay = usefulRelays[uIdx];
+
+					if (relay == null)
+					{
+						continue;
+					}
+
+					log.AppendFormat("\n\tDoing target search for useful relay {0}", relay);
+
+					relay.RecalculateTransmissionRates();
+					relay.FindNearestRelay();
+				}
+
+				// Very last, find routes for the non-best relays on the active vessel.
 				for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
 				{
 					relay = activeVesselRelays[rIdx];
 
+					// The best active relay will get checked with the other useful relays later.
+					if (relay == null || relay == bestActiveRelay)
+					{
+						continue;
+					}
+
+					log.AppendFormat("\nFinding nearest relay for active vessel relay {0}", relay);
+
+					relay.RecalculateTransmissionRates();
 					relay.FindNearestRelay();
 				}
 
 				if (this.toolbarButton != null || this.appLauncherButton != null)
 				{
-					log.Append("Checking vessel relay status.\n");
+					log.Append("\nChecking active vessel relay status.");
 
 					this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus();
 
-					log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}",
+					log.AppendFormat("\n\tcurrentConnectionStatus: {0}, setting texture to {1}",
 						this.currentConnectionStatus, this.currentConnectionTexture);
 
 					if (this.toolbarButton != null)
@@ -305,7 +367,7 @@
 				}
 			}
 
-			log.Print();
+			log.Print(false);
 		}
 
 		private void OnDestroy()

--- a/ARMapRenderer.cs
+++ b/ARMapRenderer.cs
@@ -31,20 +31,28 @@
 using KSP;
 using System;
 using System.Collections.Generic;
-using ToadicusTools;
+using ToadicusTools.Extensions;
+using ToadicusTools.DebugTools;
 using UnityEngine;
 
 namespace AntennaRange
 {
 	public class ARMapRenderer : MonoBehaviour
 	{
+		#if BENCH
+		private static ulong updateCount = 0u;
+		private static ulong updateTimer = 0u;
+		private readonly static RollingAverage averager = new RollingAverage();
+		private static long twiceAverageTime = long.MaxValue;
+		#endif
+
 		#region Fields
 		private Dictionary<Guid, LineRenderer> vesselLineRenderers;
 
 		// Debug Stuff
 		#pragma warning disable 649
 		private System.Diagnostics.Stopwatch timer;
-		private Tools.DebugLogger log;
+		private PooledDebugLogger log;
 		private long relayStart;
 		private long start;
 		#pragma warning restore 649
@@ -59,32 +67,23 @@
 		{
 			get
 			{
-				if (this.vesselLineRenderers == null)
-				{
-					this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
-				}
-
 				LineRenderer lr;
 
-				if (this.vesselLineRenderers.TryGetValue(idx, out lr))
-				{
-					return lr;
-				}
-				else
+				if (!this.vesselLineRenderers.TryGetValue(idx, out lr))
 				{
 					GameObject obj = new GameObject();
 					obj.layer = 31;
 
 					lr = obj.AddComponent<LineRenderer>();
 
-					// lr.SetColors(Color.green, Color.green);
 					lr.material = MapView.OrbitLinesMaterial;
-					// lr.SetVertexCount(2);
 
 					this.vesselLineRenderers[idx] = lr;
 
 					return lr;
 				}
+
+				return lr;
 			}
 		}
 		#endregion
@@ -97,9 +96,11 @@
 				this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
 			}
 
+			#if DEBUG || BENCH
+			this.timer = new System.Diagnostics.Stopwatch();
+			#endif
 			#if DEBUG
-			this.timer = new System.Diagnostics.Stopwatch();
-			this.log = Tools.DebugLogger.New(this);
+			this.log = PooledDebugLogger.Get();
 			#endif
 		}
 
@@ -107,12 +108,12 @@
 		{
 			if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled || !ARConfiguration.PrettyLines)
 			{
-				this.Cleanup();
+				this.Cleanup(!HighLogic.LoadedSceneIsFlight);
 
 				return;
 			}
 
-			#if DEBUG
+			#if DEBUG || BENCH
 			timer.Restart();
 			#endif
 
@@ -121,7 +122,7 @@
 				log.Clear();
 
 				log.AppendFormat("OnPreCull.\n");
-
+/* @ TODO: Fix
 				log.AppendFormat("\tMapView: Draw3DLines: {0}\n" +
 					"\tMapView.MapCamera.camera.fieldOfView: {1}\n" +
 					"\tMapView.MapCamera.Distance: {2}\n",
@@ -129,69 +130,37 @@
 					MapView.MapCamera.camera.fieldOfView,
 					MapView.MapCamera.Distance
 				);
-
-				if (FlightGlobals.ready && FlightGlobals.Vessels != null)
-				{
-					log.AppendLine("FlightGlobals ready and Vessels list not null.");
-
-					for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
+*/
+				log.AppendLine("FlightGlobals ready and Vessels list not null.");
+
+				IAntennaRelay relay;
+
+				for (int i = 0; i < ARFlightController.UsefulRelays.Count; i++)
+				{
+					relay = ARFlightController.UsefulRelays[i];
+
+					if (relay == null)
 					{
-						Vessel vessel = FlightGlobals.Vessels[i];
-
-						log.AppendFormat("\nStarting check for vessel {0} at {1}ms", vessel, timer.ElapsedMilliseconds);
-
-						if (vessel == null)
-						{
-							log.AppendFormat("\n\tSkipping vessel {0} altogether because it is null.", vessel);
-							continue;
-						}
-
-						switch (vessel.vesselType)
-						{
-							case VesselType.Debris:
-							case VesselType.EVA:
-							case VesselType.Unknown:
-							case VesselType.SpaceObject:
-								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);
-						}
+						log.AppendFormat("\n\tGot null relay, skipping");
+						continue;
 					}
+
+					log.AppendFormat("\n\tDrawing pretty lines for useful relay {0}", relay);
+					
+					#if DEBUG
+					start = timer.ElapsedMilliseconds;
+					#endif
+
+					this.SetRelayVertices(relay);
+
+					log.AppendFormat("\n\tSet relay vertices for {0} in {1}ms",
+						relay, timer.ElapsedMilliseconds - start);
 				}
 			}
 			catch (Exception ex)
 			{
 				this.LogError("Caught {0}: {1}\n{2}\n", ex.GetType().Name, ex.ToString(), ex.StackTrace.ToString());
-				this.Cleanup();
+				this.Cleanup(false);
 			}
 			#if DEBUG
 			finally
@@ -201,11 +170,32 @@
 				log.Print();
 			}
 			#endif
+
+			#if BENCH
+			ARMapRenderer.updateCount++;
+			ARMapRenderer.updateTimer += (ulong)this.timer.ElapsedTicks;
+
+			if (ARMapRenderer.updateCount >= (ulong)(8d / Time.smoothDeltaTime))
+			{
+				ARMapRenderer.averager.AddItem((double)ARMapRenderer.updateTimer / (double)ARMapRenderer.updateCount);
+				ARMapRenderer.updateTimer = 0u;
+				ARMapRenderer.updateCount = 0u;
+				ARMapRenderer.twiceAverageTime = (long)(ARMapRenderer.averager.Average * 2d);
+			}
+
+			if (this.timer.ElapsedTicks > ARMapRenderer.twiceAverageTime)
+			{
+				this.Log("PreCull took significant longer than usual ({0:S3}s vs {1:S3}s)",
+					(double)this.timer.ElapsedTicks / (double)System.Diagnostics.Stopwatch.Frequency,
+					ARMapRenderer.averager.Average / (double)System.Diagnostics.Stopwatch.Frequency
+				);
+			}
+			#endif
 		}
 
 		private void OnDestroy()
 		{
-			this.Cleanup();
+			this.Cleanup(true);
 
 			this.Log("Destroyed");
 		}
@@ -223,20 +213,22 @@
 			}
 
 			LineRenderer renderer = this[relay.vessel.id];
-			Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
+			Vector3 start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
 
 			float lineWidth;
 			float d = Screen.height / 2f + 0.01f;
 
 			if (MapView.Draw3DLines)
 			{
-				lineWidth = 0.005859375f * MapView.MapCamera.Distance;
+				lineWidth = 0.00833333333f * MapView.MapCamera.Distance;
 			}
 			else
 			{
-				lineWidth = 2f;
-
-				start = MapView.MapCamera.camera.WorldToScreenPoint(start);
+				lineWidth = 3f;
+
+				// TODO: No idea if this substitution is right.
+				// start = MapView.MapCamera.camera.WorldToScreenPoint(start);
+				start = PlanetariumCamera.Camera.WorldToScreenPoint(start);
 
 				start.z = start.z >= 0f ? d : -d;
 			}
@@ -251,7 +243,7 @@
 			relayStart = timer.ElapsedMilliseconds;
 			#endif
 
-			Vector3d nextPoint;
+			Vector3 nextPoint;
 
 			renderer.enabled = true;
 
@@ -288,6 +280,17 @@
 					return;
 				}
 
+				switch (relay.targetRelay.vessel.vesselType)
+				{
+					case VesselType.Debris:
+					case VesselType.Flag:
+					case VesselType.Unknown:
+						renderer.enabled = false;
+						return;
+					default:
+						break;
+				}
+
 				nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
 			}
 
@@ -295,7 +298,9 @@
 
 			if (!MapView.Draw3DLines)
 			{
-				nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
+				// TODO: No idea if this substitution is right.
+				// nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
+				nextPoint = PlanetariumCamera.Camera.WorldToScreenPoint(nextPoint);
 				nextPoint.z = nextPoint.z >= 0f ? d : -d;
 			}
 
@@ -307,7 +312,7 @@
 			log.AppendFormat("\n\t\t\t...finished segment in {0} ms", timer.ElapsedMilliseconds - relayStart);
 		}
 
-		private void Cleanup()
+		private void Cleanup(bool freeObjects)
 		{
 			if (this.vesselLineRenderers != null && this.vesselLineRenderers.Count > 0)
 			{
@@ -318,9 +323,17 @@
 				{
 					lineRenderer = enumerator.Current;
 					lineRenderer.enabled = false;
-					GameObject.Destroy(lineRenderer.gameObject);
-				}
-				this.vesselLineRenderers.Clear();
+
+					if (freeObjects)
+					{
+						GameObject.Destroy(lineRenderer.gameObject);
+					}
+				}
+
+				if (freeObjects)
+				{
+					this.vesselLineRenderers.Clear();
+				}
 			}
 		}
 		#endregion

--- a/AntennaRange.csproj
+++ b/AntennaRange.csproj
@@ -93,7 +93,12 @@
     </Reference>
     <Reference Include="UnityEngine">
       <HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath>
-      <Private>False</Private>
+    </Reference>
+    <Reference Include="KSPUtil">
+      <HintPath>..\_KSPAssemblies\KSPUtil.dll</HintPath>
+    </Reference>
+    <Reference Include="UnityEngine.UI">
+      <HintPath>..\_KSPAssemblies\UnityEngine.UI.dll</HintPath>
     </Reference>
   </ItemGroup>
   <ItemGroup>

--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -28,7 +28,9 @@
 
 using System;
 using System.Collections.Generic;
-using ToadicusTools;
+using ToadicusTools.DebugTools;
+using ToadicusTools.Extensions;
+using UnityEngine;
 
 namespace AntennaRange
 {
@@ -56,8 +58,18 @@
 			}
 		}
 
+		#if BENCH
+		private static ushort relayCount = 0;
+		private static ulong searchCount = 0u;
+		private static ulong searchTimer = 0u;
+		private readonly static RollingAverage averager = new RollingAverage(16);
+		private static long doubleAverageTime = long.MaxValue;
+
+
+		private System.Diagnostics.Stopwatch performanceTimer = new System.Diagnostics.Stopwatch();
+		#endif
+
 		private bool canTransmit;
-		private bool isChecked;
 
 		private IAntennaRelay nearestRelay;
 		private IAntennaRelay bestOccludedRelay;
@@ -102,7 +114,7 @@
 		/// <summary>
 		/// Gets or sets the nominal link distance, in meters.
 		/// </summary>
-		public virtual double NominalLinkDistance
+		public virtual double NominalLinkSqrDistance
 		{
 			get;
 			protected set;
@@ -111,7 +123,7 @@
 		/// <summary>
 		/// Gets or sets the maximum link distance, in meters.
 		/// </summary>
-		public virtual double MaximumLinkDistance
+		public virtual double MaximumLinkSqrDistance
 		{
 			get;
 			protected set;
@@ -130,21 +142,60 @@
 		/// Gets the transmit distance.
 		/// </summary>
 		/// <value>The transmit distance.</value>
-		public double transmitDistance
+		public double CurrentLinkSqrDistance
 		{
 			get
 			{
 				if (this.KerbinDirect || this.targetRelay == null)
 				{
-					return this.DistanceTo(Kerbin);
+					return this.SqrDistanceTo(Kerbin);
 				}
 				else
 				{
-					return this.DistanceTo(this.targetRelay);
-				}
-			}
-		}
-
+					return this.SqrDistanceTo(this.targetRelay);
+				}
+			}
+		}
+
+		/// <summary>
+		/// Gets the current link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current link resource rate in EC/MiT.</value>
+		public virtual double CurrentLinkResourceRate
+		{
+			get
+			{
+				return this.DataResourceCost;
+			}
+		}
+
+		public virtual double CurrentNetworkResourceRate
+		{
+			get
+			{
+				double totalRate = 0;
+
+				IAntennaRelay nextLink = this.moduleRef;
+
+				while (nextLink != null)
+				{
+					totalRate += nextLink.DataResourceCost;
+
+					if (nextLink.KerbinDirect)
+					{
+						break;
+					}
+
+					nextLink = nextLink.targetRelay;
+				}
+
+				return totalRate;
+			}
+		}
+
+		/// <summary>
+		/// Gets or sets the link status.
+		/// </summary>
 		public virtual ConnectionStatus LinkStatus
 		{
 			get;
@@ -169,6 +220,69 @@
 			get;
 			set;
 		}
+		/*
+		 * The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except
+		 * in that they both return floats.  Here's some quick justification:
+		 * 
+		 * The stock implementation of GetTransmitterScore (which I cannot override) is:
+		 * 		Score = (1 + DataResourceCost) / DataRate
+		 * 
+		 * The stock DataRate and DataResourceCost are:
+		 * 		DataRate = packetSize / packetInterval
+		 * 		DataResourceCost = packetResourceCost / packetSize
+		 * 
+		 * So, the resulting score is essentially in terms of joules per byte per baud.  Rearranging that a bit, it
+		 * could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte.  Either way,
+		 * that metric is not a very reasonable one.
+		 * 
+		 * Two metrics that might make more sense are joules per byte or joules per byte per second.  The latter case
+		 * would look like:
+		 * 		DataRate = packetSize / packetInterval
+		 * 		DataResourceCost = packetResourceCost
+		 * 
+		 * The former case, which I've chosen to implement below, is:
+		 * 		DataRate = packetSize
+		 * 		DataResourceCost = packetResourceCost
+		 * 
+		 * So... hopefully that doesn't screw with anything else.
+		 * */
+		/// <summary>
+		/// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
+		/// terms of joules/byte
+		/// </summary>
+		public virtual float DataRate
+		{
+			get
+			{
+				if (this.CanTransmit())
+				{
+					return this.moduleRef.PacketSize;
+				}
+				else
+				{
+					return float.Epsilon;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
+		/// to be scored in terms of joules/byte
+		/// </summary>
+		public virtual double DataResourceCost
+		{
+			get
+			{
+				if (this.CanTransmit())
+				{
+					return this.moduleRef.PacketResourceCost;
+				}
+				else
+				{
+					return float.PositiveInfinity;
+				}
+			}
+		}
 
 		/// <summary>
 		/// Determines whether this instance can transmit.
@@ -179,6 +293,106 @@
 			return this.canTransmit;
 		}
 
+		// Before transmission, set packetSize.  Per above, packet size increases with the inverse square of
+		// distance.  packetSize maxes out at _basepacketSize * maxDataFactor.
+		public void RecalculateTransmissionRates()
+		{
+			float rangeFactor = (float)(this.NominalLinkSqrDistance / this.CurrentLinkSqrDistance);
+
+			if (ARConfiguration.FixedPowerCost)
+			{
+				this.moduleRef.PacketResourceCost = this.moduleRef.BasePacketResourceCost;
+
+				this.moduleRef.PacketSize = Mathf.Min(
+					this.moduleRef.BasePacketSize * rangeFactor,
+					this.moduleRef.BasePacketSize * this.moduleRef.MaxDataFactor
+				);
+			}
+			else
+			{
+				if (this.CurrentLinkSqrDistance > this.NominalLinkSqrDistance)
+				{
+					this.moduleRef.PacketSize = this.moduleRef.BasePacketSize;
+					this.moduleRef.PacketResourceCost = this.moduleRef.BasePacketResourceCost / rangeFactor;
+				}
+				else
+				{
+					this.moduleRef.PacketSize = Mathf.Min(
+						this.moduleRef.BasePacketSize * rangeFactor,
+						this.moduleRef.BasePacketSize * this.moduleRef.MaxDataFactor
+					);
+					this.moduleRef.PacketResourceCost = this.moduleRef.BasePacketResourceCost;
+				}
+			}
+
+			this.moduleRef.PacketSize *= this.moduleRef.PacketThrottle / 100f;
+			this.moduleRef.PacketResourceCost *= this.moduleRef.PacketThrottle / 100f;
+		}
+
+		public double GetPotentialLinkCost(double currentSqrDistance, double nominalSqrDistance)
+		{
+			double cost;
+
+			float rangeFactor = (float)(nominalSqrDistance / currentSqrDistance);
+
+			if (ARConfiguration.FixedPowerCost || currentSqrDistance <= NominalLinkSqrDistance)
+			{
+				cost = this.moduleRef.BasePacketResourceCost;
+			}
+			else
+			{
+				cost = this.moduleRef.BasePacketResourceCost / rangeFactor;
+			}
+
+			cost *= this.moduleRef.PacketThrottle / 100f;
+
+			return cost;
+		}
+
+		public double GetPotentialLinkCost(IAntennaRelay potentialTarget)
+		{
+			if (potentialTarget == null)
+			{
+				return double.PositiveInfinity;
+			}
+
+			double nominalSqrDistance;
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				nominalSqrDistance = this.nominalTransmitDistance * potentialTarget.nominalTransmitDistance;
+			}
+			else
+			{
+				nominalSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
+			}
+
+			double currentSqrDistance = this.SqrDistanceTo(potentialTarget);
+
+			return GetPotentialLinkCost(currentSqrDistance, nominalSqrDistance);
+		}
+
+		public double GetPotentialLinkCost(CelestialBody body)
+		{
+			if (body == null || body != Kerbin)
+			{
+				return double.PositiveInfinity;
+			}
+
+			double nominalSqrDistance;
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				nominalSqrDistance = this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange;
+			}
+			else
+			{
+				nominalSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
+			}
+
+			double currentSqrDistance = this.SqrDistanceTo(body);
+
+			return GetPotentialLinkCost(currentSqrDistance, nominalSqrDistance);
+		}
+
 		/// <summary>
 		/// Finds the nearest relay.
 		/// </summary>
@@ -190,27 +404,53 @@
 				return;
 			}
 
-			Tools.DebugLogger log;
+			PooledDebugLogger log;
 			#if DEBUG
-			log = Tools.DebugLogger.New(this);
-			#endif
-
-			// 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 = PooledDebugLogger.New(this);
+			#endif
+
+			#if BENCH
+			this.performanceTimer.Restart();
+
+			long startVesselLoopTicks;
+			long totalVesselLoopTicks;
+
+			string slowestLOSVesselName = string.Empty;
+			long slowestLOSVesselTicks = long.MinValue;
+			long startLOSVesselTicks;
+			long totalLOSVesselTicks;
+
+			string slowestCircularVesselName = string.Empty;
+			long slowestCircularVesselTicks = long.MinValue;
+			long startCircularVesselTicks;
+			long totalCircularVesselTicks;
+
+			long startKerbinLOSTicks;
+			long totalKerbinLOSTicks;
+			long statusResolutionTicks;
+
+			ushort usefulVesselCount = 0;
+			#endif
 
 			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.
-			this.isChecked = true;
+
+			// Declare a bunch of variables we'll be using.
+			CelestialBody bodyOccludingBestOccludedRelay = null;
+			IAntennaRelay needle;
+
+			double cheapestRelayRate = double.PositiveInfinity;
+			double cheapestOccludedRelayRate = double.PositiveInfinity;
+
+			double potentialRelayRate;
+
+			double kerbinRelayRate = this.GetPotentialLinkCost(Kerbin);
+
+			bool isCircular;
+			int iterCount;
 
 			// Blank everything we're trying to find before the search.
 			this.firstOccludingBody = null;
@@ -221,129 +461,91 @@
 			// Default to KerbinDirect = true in case something in here doesn't work right.
 			this.KerbinDirect = true;
 
-			CelestialBody bodyOccludingBestOccludedRelay = null;
-			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
-			 * far away.  When we find a candidate, get through its antennae for relays which have not been checked yet
-			 * and that can transmit.  Once we find a suitable candidate, assign it to nearestRelay for comparison
-			 * against future finds.
+			 * Loop through the useful relays as determined by ARFlightController and check each for line of sight and
+			 * distance, searching for the relay with the best distance/maxRange ratio that is in sight, in range, and
+			 * can transmit, also stashing the "best" relay outside of line of sight for failure report.
 			 * */
-			Vessel potentialVessel;
 			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);
-				}
+			#if BENCH
+			startVesselLoopTicks = performanceTimer.ElapsedTicks;
+			#endif
+			
+			for (int rIdx = 0; rIdx < ARFlightController.UsefulRelays.Count; rIdx++)
+			{
+				potentialBestRelay = ARFlightController.UsefulRelays[rIdx];
+				log.AppendFormat("\n\tgot useful relay {0}",
+					potentialBestRelay == null ? "null" : potentialBestRelay.ToString());
+
+				if (potentialBestRelay == null)
+				{
+					log.Append("\n\t...skipping null relay");
+					continue;
+				}
+
+				if (potentialBestRelay == this || potentialBestRelay.vessel == this.vessel)
+				{
+					log.AppendFormat(
+						"\n\t...skipping relay {0} because it or its vessel ({1}) is the same as ours" +
+						"\n\t\t(our vessel is {2})",
+						potentialBestRelay,
+						potentialBestRelay.vessel == null ? "null" : potentialBestRelay.vessel.vesselName,
+						this.vessel == null ? "null" : this.vessel.vesselName
+					);
+					continue;
+				}
+
+				#if BENCH
+				usefulVesselCount++;
 				#endif
 
-				// Skip vessels of the wrong type.
-				log.Append("\n\tchecking vessel type");
-				switch (potentialVessel.vesselType)
-				{
-					case VesselType.Debris:
-					case VesselType.Flag:
-					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;
-				}
-
-				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");
-
-				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\tgetting cost to potential vessel");
+				potentialRelayRate = potentialBestRelay.CurrentNetworkResourceRate + this.GetPotentialLinkCost(potentialBestRelay);
+
+				#if BENCH
+				startLOSVesselTicks = performanceTimer.ElapsedTicks;
+				#endif
 
 				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.vessel.hasLineOfSightTo(potentialBestRelay.vessel, out fob, ARConfiguration.RadiusRatio)
 				)
 				{
+					#if BENCH
+					totalLOSVesselTicks = performanceTimer.ElapsedTicks - startLOSVesselTicks;
+
+					if (totalLOSVesselTicks > slowestLOSVesselTicks)
+					{
+						slowestLOSVesselTicks = totalLOSVesselTicks;
+						slowestLOSVesselName = vessel.vesselName;
+					}
+					#endif
+
 					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\t{0}: Relay {1} not in line of sight.",
+						this.ToString(), potentialBestRelay);
 					
-					log.AppendFormat("\n\t\t\tpotentialSqrDistance: {0}", potentialSqrDistance);
-					log.AppendFormat("\n\t\t\tbestOccludedSqrQuotient: {0}", bestOccludedSqrQuotient);
-					log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxLinkSqrDistance);
+					log.AppendFormat("\n\t\t\tpotentialRelayRate: {0}", potentialRelayRate);
+					log.AppendFormat("\n\t\t\tcheapestOccludedRelayRate: {0}", cheapestOccludedRelayRate);
 
 					if (
-						(potentialSqrQuotient < bestOccludedSqrQuotient) &&
-						(potentialSqrQuotient <= 1d) &&
+						(potentialRelayRate < cheapestRelayRate) &&
+						this.IsInRangeOf(potentialBestRelay) &&
 						potentialBestRelay.CanTransmit()
 					)
 					{
-						log.Append("\n\t\t...vessel is close enough to and potentialBestRelay can transmit");
+						log.Append("\n\t\t...vessel is cheapest and close enough and potentialBestRelay can transmit");
 						log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay);
 
 						this.bestOccludedRelay = potentialBestRelay;
 						bodyOccludingBestOccludedRelay = fob;
-						bestOccludedSqrQuotient = potentialSqrQuotient;
+						cheapestOccludedRelayRate = potentialRelayRate;
 					}
 					else
 					{
@@ -352,18 +554,30 @@
 					
 					continue;
 				}
+				#if BENCH
+				else
+				{
+					totalLOSVesselTicks = performanceTimer.ElapsedTicks - startLOSVesselTicks;
+				}
+
+				if (totalLOSVesselTicks > slowestLOSVesselTicks)
+				{
+					slowestLOSVesselTicks = totalLOSVesselTicks;
+					slowestLOSVesselName = vessel.vesselName;
+				}
+				#endif
 
 				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.
 				 * */
-				if (potentialSqrQuotient > nearestRelaySqrQuotient)
+				if (potentialRelayRate > cheapestRelayRate)
 				{
 					
-					log.AppendFormat("\n\t{0}: Vessel {1} discarded because it is farther than another the nearest relay.",
+					log.AppendFormat("\n\t{0}: Relay {1} discarded because it is more expensive than another the nearest relay.",
 						this.ToString(),
-						potentialVessel.vesselName
+						potentialBestRelay
 					);
 					continue;
 				}
@@ -372,10 +586,14 @@
 
 				if (potentialBestRelay.CanTransmit())
 				{
+					#if BENCH
+					startCircularVesselTicks = performanceTimer.ElapsedTicks;
+					#endif
+
 					needle = potentialBestRelay;
-					bool isCircular = false;
-
-					int iterCount = 0;
+					isCircular = false;
+
+					iterCount = 0;
 					while (needle != null)
 					{
 						iterCount++;
@@ -399,11 +617,10 @@
 						// 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,
+							this.LogError(
+								"iterCount exceeded while checking for circular network; assuming it is circular" +
+								"\n\tneedle={0}" +
+								"\n\tthis.moduleRef={1}",
 								needle == null ? "null" : string.Format(
 									"{0}, needle.KerbinDirect={1}, needle.targetRelay={2}",
 									needle, needle.KerbinDirect, needle.targetRelay == null ? "null" : string.Format(
@@ -424,13 +641,13 @@
 
 					if (!isCircular)
 					{
-						nearestRelaySqrQuotient = potentialSqrQuotient;
+						cheapestRelayRate = potentialRelayRate;
 						this.nearestRelay = potentialBestRelay;
 
-						log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m²)",
+						log.AppendFormat("\n\t{0}: found new cheapest relay {1} ({2} EC/MiT)",
 							this.ToString(),
 							this.nearestRelay.ToString(),
-							Math.Sqrt(nearestRelaySqrQuotient)
+							Math.Sqrt(cheapestRelayRate)
 						);
 					}
 					else
@@ -439,37 +656,40 @@
 							potentialBestRelay
 						);
 					}
-				}
-			}
+
+					#if BENCH
+					totalCircularVesselTicks = performanceTimer.ElapsedTicks - startCircularVesselTicks;
+
+					if (totalCircularVesselTicks > slowestCircularVesselTicks)
+					{
+						slowestCircularVesselName = vessel.vesselName;
+						slowestCircularVesselTicks = totalCircularVesselTicks;
+					}
+
+					#endif
+				}
+			}
+
+			#if BENCH
+			totalVesselLoopTicks = performanceTimer.ElapsedTicks - startVesselLoopTicks;
+			#endif
 
 			CelestialBody bodyOccludingKerbin = null;
-
-			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²)",
+				"\n{0}: nearestRelay={1} ({2})), bestOccludedRelay={3} ({4}), kerbinSqrDistance={5}m²)",
 				this,
 				this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-				nearestRelaySqrQuotient,
+				cheapestRelayRate,
 				this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
-				bestOccludedSqrQuotient,
-				kerbinSqrDistance
+				cheapestOccludedRelayRate,
+				kerbinRelayRate
 			);
+			
+			#if BENCH
+			startKerbinLOSTicks = this.performanceTimer.ElapsedTicks;
+			#endif
 
 			// If we don't have LOS to Kerbin, focus on relays
 			if (
@@ -477,15 +697,17 @@
 				!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)
 			)
 			{
+				#if BENCH
+				totalKerbinLOSTicks = this.performanceTimer.ElapsedTicks - startKerbinLOSTicks;
+				#endif
 				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 (nearestRelaySqrQuotient <= 1d)
-				{
-					log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
-						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-						nearestRelaySqrQuotient, 1d);
+				// If we're in range of the "nearest" (actually cheapest) relay, use it.
+				if (this.IsInRangeOf(this.nearestRelay))
+				{
+					log.AppendFormat("\n\t\tCan transmit to nearby relay {0}).",
+						this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
+					);
 
 					this.KerbinDirect = false;
 					this.canTransmit = true;
@@ -494,27 +716,27 @@
 				// If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway
 				else
 				{
-					log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
-						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-						nearestRelaySqrQuotient, 1d);
+					log.AppendFormat("\n\t\tCan't transmit to nearby relay {0}.",
+						this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
+					);
 
 					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 (bestOccludedSqrQuotient < kerbinSqrQuotient)
-					{
-						log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
-							bestOccludedRelay, kerbinSqrDistance);
+					// If the best occluded relay is cheaper than Kerbin, check it against the nearest relay.
+					// Since cheapestOccludedRelayRate is infinity if there are no occluded relays, this is safe
+					if (cheapestOccludedRelayRate < kerbinRelayRate)
+					{
+						log.AppendFormat("\n\t\t\tBest occluded relay is cheaper than Kerbin ({0} < {1})",
+							cheapestOccludedRelayRate, kerbinRelayRate);
 						
 						this.KerbinDirect = false;
 
-						// 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 (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
+						// If the nearest relay is cheaper than the best occluded relay, pick it.
+						// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
+						if (cheapestRelayRate < cheapestOccludedRelayRate)
 						{
-							log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-								nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+							log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.",
+								cheapestRelayRate, cheapestOccludedRelayRate);
 							
 							this.targetRelay = this.nearestRelay;
 							this.firstOccludingBody = null;
@@ -522,36 +744,37 @@
 						// 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.",
-								nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+							log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
+								cheapestRelayRate, cheapestOccludedRelayRate);
 							
 							this.targetRelay = bestOccludedRelay;
 							this.firstOccludingBody = bodyOccludingBestOccludedRelay;
 						}
 					}
-					// Otherwise, check Kerbin against the nearest relay.
-					// Since we have LOS, blank the first occluding body.
+					// Otherwise, check Kerbin against the "nearest" (cheapest) relay.
 					else
 					{
-						log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
-							bestOccludedRelay, kerbinSqrDistance);
+						log.AppendFormat("\n\t\t\tKerbin is cheaper than the best occluded relay ({0} >= {1})",
+							cheapestOccludedRelayRate, kerbinRelayRate);
 						
-						// If the nearest relay is closer than Kerbin, pick it.
-						// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
-						if (nearestRelaySqrQuotient < kerbinSqrQuotient)
+						// If the "nearest" (cheapest) relay is cheaper than Kerbin, pick it.
+						// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
+						if (cheapestRelayRate < kerbinRelayRate)
 						{
-							log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-								nearestRelaySqrQuotient, kerbinSqrQuotient);
-							
+							log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.",
+								cheapestRelayRate, kerbinRelayRate);
+
+							// Since we have LOS, blank the first occluding body.
+							this.firstOccludingBody = null;
+
 							this.KerbinDirect = false;
-							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.",
-								nearestRelaySqrQuotient, kerbinSqrQuotient);
+							log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
+								cheapestRelayRate, kerbinRelayRate);
 							
 							this.KerbinDirect = true;
 							this.firstOccludingBody = bodyOccludingKerbin;
@@ -563,23 +786,27 @@
 			// If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin
 			else
 			{
+				#if BENCH
+				totalKerbinLOSTicks = this.performanceTimer.ElapsedTicks - startKerbinLOSTicks;
+				#endif
+
 				log.AppendFormat("\n\tKerbin is in LOS.");
 
-				// If the nearest relay is closer than Kerbin and in range, transmit to it.
-				if (nearestRelaySqrQuotient <= 1d)
-				{
-					log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
-						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-						nearestRelaySqrQuotient, 1d);
+				// If the nearest relay is in range, we can transmit.
+				if (this.IsInRangeOf(this.nearestRelay))
+				{
+					log.AppendFormat("\n\t\tCan transmit to nearby relay {0} (in range).",
+						this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
+					);
 
 					this.canTransmit = true;
 
 					// If the nearestRelay is closer than Kerbin, use it.
-					if (nearestRelaySqrQuotient < kerbinSqrQuotient)
+					if (cheapestRelayRate < kerbinRelayRate)
 					{
 						log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).",
 							this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrQuotient, kerbinSqrQuotient);
+							cheapestRelayRate, kerbinRelayRate);
 
 						this.KerbinDirect = false;
 						this.targetRelay = this.nearestRelay;
@@ -589,7 +816,7 @@
 					{
 						log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",
 							this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrQuotient, kerbinSqrQuotient);
+							cheapestRelayRate, kerbinRelayRate);
 
 						this.KerbinDirect = true;
 						this.targetRelay = null;
@@ -598,15 +825,14 @@
 				// If the nearest relay is out of range, we still need to check on Kerbin.
 				else
 				{
-					log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
-						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrQuotient, 1d);
+					log.AppendFormat("\n\t\tCheapest relay {0} is out of range.",
+						this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
+					);
 
 					// If Kerbin is in range, use it.
-					if (kerbinSqrQuotient <= 1d)
-					{
-						log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).",
-							kerbinSqrQuotient, 1d);
+					if (this.IsInRangeOf(Kerbin))
+					{
+						log.AppendFormat("\n\t\t\tCan transmit to Kerbin (in range).");
 
 						this.canTransmit = true;
 						this.KerbinDirect = true;
@@ -616,26 +842,25 @@
 					// Kerbin and bestOccludedRelay
 					else
 					{
-						log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).",
-								kerbinSqrQuotient, 1d);
+						log.AppendFormat("\n\t\t\tCan't transmit to Kerbin (out of range).");
 
 						this.canTransmit = false;
 
-						// If the best occluded relay is closer than Kerbin, check it against the nearest relay.
+						// If the best occluded relay is cheaper than Kerbin, check it against the nearest relay.
 						// Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
-						if (bestOccludedSqrQuotient < kerbinSqrQuotient)
+						if (cheapestOccludedRelayRate < kerbinRelayRate)
 						{
 							log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
-								bestOccludedRelay, kerbinSqrDistance);
+								cheapestOccludedRelayRate, kerbinRelayRate);
 							
 							this.KerbinDirect = false;
 
 							// 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 (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
+							// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
+							if (cheapestRelayRate < cheapestOccludedRelayRate)
 							{
-								log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-									nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+								log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.",
+									cheapestRelayRate, cheapestOccludedRelayRate);
 								
 								this.targetRelay = this.nearestRelay;
 								this.firstOccludingBody = null;
@@ -643,8 +868,8 @@
 							// 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.",
-									nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+								log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
+									cheapestRelayRate, cheapestOccludedRelayRate);
 								
 								this.targetRelay = bestOccludedRelay;
 								this.firstOccludingBody = bodyOccludingBestOccludedRelay;
@@ -654,17 +879,17 @@
 						// Since we have LOS, blank the first occluding body.
 						else
 						{
-							log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
-								bestOccludedRelay, kerbinSqrDistance);
+							log.AppendFormat("\n\t\t\tKerbin is cheaper than the best occluded relay ({0} >= {1})",
+								cheapestOccludedRelayRate, kerbinRelayRate);
 							
 							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 (nearestRelaySqrQuotient < kerbinSqrQuotient)
+							// If the nearest relay is cheaper than Kerbin, pick it.
+							// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
+							if (cheapestRelayRate < kerbinRelayRate)
 							{
-								log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-									nearestRelaySqrQuotient, kerbinSqrQuotient);
+								log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.",
+									cheapestRelayRate, kerbinRelayRate);
 								
 								this.KerbinDirect = false;
 								this.targetRelay = this.nearestRelay;
@@ -672,8 +897,8 @@
 							// Otherwise, pick Kerbin.
 							else
 							{
-								log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
-									nearestRelaySqrQuotient, kerbinSqrQuotient);
+								log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
+									cheapestRelayRate, kerbinRelayRate);
 								
 								this.KerbinDirect = true;
 								this.targetRelay = null;
@@ -687,24 +912,24 @@
 			{
 				if (this.KerbinDirect)
 				{
-					this.NominalLinkDistance = Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange);
-					this.MaximumLinkDistance = Math.Sqrt(this.maxTransmitDistance * ARConfiguration.KerbinRelayRange);
+					this.NominalLinkSqrDistance = this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange;
+					this.MaximumLinkSqrDistance = this.maxTransmitDistance * ARConfiguration.KerbinRelayRange;
 				}
 				else
 				{
-					this.NominalLinkDistance = Math.Sqrt(this.nominalTransmitDistance * this.targetRelay.nominalTransmitDistance);
-					this.MaximumLinkDistance = Math.Sqrt(this.maxTransmitDistance * this.targetRelay.maxTransmitDistance);
+					this.NominalLinkSqrDistance = this.nominalTransmitDistance * this.targetRelay.nominalTransmitDistance;
+					this.MaximumLinkSqrDistance = this.maxTransmitDistance * this.targetRelay.maxTransmitDistance;
 				}
 			}
 			else
 			{
-				this.NominalLinkDistance = this.nominalTransmitDistance;
-				this.MaximumLinkDistance = this.maxTransmitDistance;
+				this.NominalLinkSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
+				this.MaximumLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
 			}
 
 			if (this.canTransmit)
 			{
-				if (this.transmitDistance < this.NominalLinkDistance)
+				if (this.CurrentLinkSqrDistance < this.NominalLinkSqrDistance)
 				{
 					this.LinkStatus = ConnectionStatus.Optimal;
 				}
@@ -717,6 +942,10 @@
 			{
 				this.LinkStatus = ConnectionStatus.None;
 			}
+
+			#if BENCH
+			statusResolutionTicks = performanceTimer.ElapsedTicks - startKerbinLOSTicks - totalKerbinLOSTicks;
+			#endif
 
 			log.AppendFormat("\n{0}: Target search and status determination complete.", this.ToString());
 			
@@ -732,9 +961,69 @@
 			#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.
-			this.isChecked = false;
+
+			#if BENCH
+			AntennaRelay.searchTimer += (ulong)this.performanceTimer.ElapsedTicks;
+			AntennaRelay.searchCount++;
+			this.performanceTimer.Stop();
+
+			double averageSearchTime = (double)AntennaRelay.searchTimer / (double)AntennaRelay.searchCount;
+
+			if (AntennaRelay.searchCount >= 8000u / (ulong)ARConfiguration.UpdateDelay)
+			{
+				AntennaRelay.searchCount = 0u;
+				AntennaRelay.searchTimer = 0u;
+
+				AntennaRelay.averager.AddItem(averageSearchTime);
+				AntennaRelay.doubleAverageTime = (long)(AntennaRelay.averager.Average * 2d);
+			}
+
+			if (this.performanceTimer.ElapsedTicks > AntennaRelay.doubleAverageTime)
+			{
+				System.Text.StringBuilder sb = Tools.GetStringBuilder();
+
+				sb.AppendFormat(Tools.SIFormatter, "[AntennaRelay] FindNearestRelay search for {0}" +
+					" took significantly longer than average ({1:S3}s vs {2:S3}s)",
+					this.ToString(),
+					(double)this.performanceTimer.ElapsedTicks / (double)System.Diagnostics.Stopwatch.Frequency,
+					(double)AntennaRelay.averager.Average / (double)System.Diagnostics.Stopwatch.Frequency
+				);
+
+				sb.AppendFormat(Tools.SIFormatter, "\n\tVessel loop time: {0:S3}s",
+					(double)totalVesselLoopTicks / (double)System.Diagnostics.Stopwatch.Frequency
+				);
+
+				sb.AppendFormat(Tools.SIFormatter, "\n\t\tAverage vessel time for each of {1} vessels: {0:S3}s",
+					(double)totalVesselLoopTicks / (double)System.Diagnostics.Stopwatch.Frequency /
+					(double)usefulVesselCount,
+					usefulVesselCount
+				);
+
+				sb.AppendFormat(Tools.SIFormatter, "\n\t\tSlowest vessel LOS check: {0:S3}s to {1}",
+					(double)slowestLOSVesselTicks / (double)System.Diagnostics.Stopwatch.Frequency,
+					slowestLOSVesselName
+				);
+
+				sb.AppendFormat(Tools.SIFormatter, "\n\t\tSlowest circular relay check: {0:S3}s for {1}",
+					(double)slowestCircularVesselTicks / (double)System.Diagnostics.Stopwatch.Frequency,
+					slowestCircularVesselName
+				);
+
+				sb.AppendFormat(Tools.SIFormatter, "\n\tKerbin LOS check: {0:S3}s",
+					(double)totalKerbinLOSTicks / (double)System.Diagnostics.Stopwatch.Frequency
+				);
+
+				sb.AppendFormat(Tools.SIFormatter, "\n\tStatus resolution check: {0:S3}s",
+					(double)statusResolutionTicks / (double)System.Diagnostics.Stopwatch.Frequency
+				);
+
+				// sb.AppendFormat(Tools.SIFormatter, "", start)
+
+				this.LogWarning(sb.ToString());
+
+				Tools.PutStringBuilder(sb);
+			}
+			#endif
 		}
 
 		/// <summary>
@@ -758,9 +1047,12 @@
 		public AntennaRelay(IAntennaRelay module)
 		{
 			this.moduleRef = module;
-			this.isChecked = false;
-
-			Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
+
+			#if BENCH
+			AntennaRelay.relayCount++;
+			#endif
+
+			this.LogDebug("{0}: constructed {1}", this.GetType().Name, this.ToString());
 		}
 	}
 }

--- a/GameData/AntennaRange/AntennaRange.cfg
+++ b/GameData/AntennaRange/AntennaRange.cfg
@@ -37,8 +37,10 @@
 // maxDataFactor:	The multipler on packetSize that defines the maximum data bandwidth of the antenna.
 // 
 
-@PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
+@PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech]
 {
+	@TechRequired = start
+
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
@@ -59,12 +61,12 @@
 	}
 }
 
-@PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
+@PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech]
 {
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
-		nominalRange = 3500000000
+		nominalRange = 3150000000
 		simpleRange = 18000000000
 		maxPowerFactor = 4
 		maxDataFactor = 8
@@ -81,16 +83,41 @@
 	}
 }
 
-@PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
+@PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech]
 {
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
 		@packetResourceCost /= 1.414213
-		nominalRange = 10000000000
+		nominalRange = 9250000000
 		simpleRange = 56250000000
 		maxPowerFactor = 16
 		maxDataFactor = 2
+	}
+
+	MODULE
+	{
+		name = ModuleScienceContainer
+
+		dataIsCollectable = true
+		dataIsStorable = false
+
+		storageRange = 2
+	}
+}
+
+@PART[HighGainAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech]
+{
+	@TechRequired = electronics
+	@description = Repurposed for medium range probes, the HG-55 provdes high speed directional data transmission.
+
+	@MODULE[ModuleDataTransmitter]
+	{
+		@name = ModuleLimitedDataTransmitter
+		nominalRange = 7774867578
+		simpleRange = 25030376544
+		maxPowerFactor = 2.6180339887498948
+		maxDataFactor = 9
 	}
 
 	MODULE
@@ -108,7 +135,7 @@
 {
 	range = 800000
 	range = 200000000000
-	range = 2250000000000
+	range = 2000000000000
 }
 
 EVA_MODULE
@@ -116,6 +143,7 @@
 	name = ModuleLimitedDataTransmitter
 
 	nominalRange = 1389
+	simpleRange = 5000
 	maxPowerFactor = 1
 	maxDataFactor = 1
 
@@ -129,7 +157,7 @@
 EVA_RESOURCE
 {
 	name = ElectricCharge
-	amount = 100
+	amount = 0
 	maxAmount = 100
 }
 

 Binary files a/GameData/AntennaRange/Textures/appLauncherIcon.png and b/GameData/AntennaRange/Textures/appLauncherIcon.png differ
 Binary files a/GameData/AntennaRange/Textures/appLauncherIconNoConnection.png and b/GameData/AntennaRange/Textures/appLauncherIconNoConnection.png differ
 Binary files a/GameData/AntennaRange/Textures/appLauncherIconSubOptimal.png and b/GameData/AntennaRange/Textures/appLauncherIconSubOptimal.png differ
 Binary files a/GameData/AntennaRange/Textures/toolbarIcon.png and b/GameData/AntennaRange/Textures/toolbarIcon.png differ
 Binary files a/GameData/AntennaRange/Textures/toolbarIconNoConnection.png and b/GameData/AntennaRange/Textures/toolbarIconNoConnection.png differ
 Binary files a/GameData/AntennaRange/Textures/toolbarIconSubOptimal.png and b/GameData/AntennaRange/Textures/toolbarIconSubOptimal.png differ
--- a/IAntennaRelay.cs
+++ b/IAntennaRelay.cs
@@ -46,20 +46,54 @@
 		/// </summary>
 		IAntennaRelay targetRelay { get; }
 
+		float PacketSize { get; set; }
+
+		float BasePacketSize { get; }
+
+		float PacketResourceCost { get; set; }
+
+		float BasePacketResourceCost { get; }
+
+		float PacketThrottle { get; }
+
+		float MaxDataFactor { get; }
+
+		/// <summary>
+		/// Gets the data resource cost in EC/MiT.
+		/// </summary>
+		/// <value>The data resource cost in EC/MiT.</value>
+		double DataResourceCost { get; }
+
+		/// <summary>
+		/// Gets the current network resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current network resource rate in EC/MiT.</value>
+		double CurrentNetworkResourceRate { 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>
+		/// The link distance, in meters, at which this relay behaves nominally.
+		/// </summary>
+		double NominalLinkSqrDistance { get; }
+
+		/// <summary>
+		/// The link distance, in meters, beyond which this relay cannot operate.
+		/// </summary>
+		double MaximumLinkSqrDistance { get; }
 
 		/// <summary>
 		/// Gets the distance to the nearest relay or Kerbin, whichever is closer.
 		/// </summary>
-		double transmitDistance { get; }
+		double CurrentLinkSqrDistance { get; }
 
+		/// <summary>
+		/// Gets the link status.
+		/// </summary>
 		ConnectionStatus LinkStatus { get; }
 
 		/// <summary>
@@ -89,9 +123,19 @@
 		bool CanTransmit();
 
 		/// <summary>
+		/// Recalculates the transmission rates.
+		/// </summary>
+		void RecalculateTransmissionRates();
+
+		/// <summary>
 		/// Finds the nearest relay.
 		/// </summary>
 		void FindNearestRelay();
+
+		/// <summary>
+		/// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
+		/// </summary>
+		void RecalculateMaxRange();
 
 		/// <summary>
 		/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.IAntennaRelay"/>.

--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -30,7 +30,9 @@
 using System;
 using System.Collections.Generic;
 using System.Text;
-using ToadicusTools;
+using ToadicusTools.DebugTools;
+using ToadicusTools.Extensions;
+using ToadicusTools.Text;
 using UnityEngine;
 
 namespace AntennaRange
@@ -46,13 +48,13 @@
 	/// 
 	/// <para>where D is the total transmission distance, P is the transmission power, and R is the data rate.</para>
 	/// </summary>
-	public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
+	public class ModuleLimitedDataTransmitter
+		: ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay, IModuleInfo
 	{
-		// Stores the packetResourceCost as defined in the .cfg file.
-		private float _basepacketResourceCost;
-
-		// Stores the packetSize as defined in the .cfg file.
-		private float _basepacketSize;
+		private const string tooltipSkinName = "PartTooltipSkin";
+		private static GUISkin partTooltipSkin;
+		private static GUIStyle partTooltipBodyStyle;
+		private static GUIStyle partTooltipHeaderStyle;
 
 		// Every antenna is a relay.
 		private AntennaRelay relay;
@@ -60,6 +62,9 @@
 		// Sometimes we will need to communicate errors; this is how we do it.
 		private ScreenMessage ErrorMsg;
 
+		// Used in module info panes for part tooltips in the editor and R&D
+		private GUIContent moduleInfoContent;
+
 		/// <summary>
 		/// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as
 		/// prescribed by packetResourceCost and packetSize.
@@ -93,9 +98,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>
@@ -167,8 +178,63 @@
 				else
 				{
 					this.LogError("Vessel and/or part reference are null, returning null vessel.");
+					#if DEBUG && VERBOSE
+					this.LogError(new System.Diagnostics.StackTrace().ToString());
+					#endif
 					return null;
 				}
+			}
+		}
+
+		public float PacketSize
+		{
+			get
+			{
+				return this.packetSize;
+			}
+			set
+			{
+				this.packetSize = value;
+			}
+		}
+
+		public float BasePacketSize
+		{
+			get;
+			private set;
+		}
+
+		public float PacketResourceCost
+		{
+			get
+			{
+				return this.packetResourceCost;
+			}
+			set
+			{
+				this.packetResourceCost = value;
+			}
+		}
+
+		public float BasePacketResourceCost
+		{
+			get;
+			private set;
+		}
+
+		public float PacketThrottle
+		{
+			get
+			{
+				return this.packetThrottle;
+			}
+		}
+
+		public float MaxDataFactor
+		{
+			get
+			{
+				return this.maxDataFactor;
 			}
 		}
 
@@ -208,13 +274,13 @@
 		/// <summary>
 		/// Gets or sets the nominal link distance, in meters.
 		/// </summary>
-		public double NominalLinkDistance
+		public double NominalLinkSqrDistance
 		{
 			get
 			{
 				if (this.relay != null)
 				{
-					return this.relay.NominalLinkDistance;
+					return this.relay.NominalLinkSqrDistance;
 				}
 
 				return 0d;
@@ -224,13 +290,13 @@
 		/// <summary>
 		/// Gets or sets the maximum link distance, in meters.
 		/// </summary>
-		public double MaximumLinkDistance
+		public double MaximumLinkSqrDistance
 		{
 			get
 			{
 				if (this.relay != null)
 				{
-					return this.relay.MaximumLinkDistance;
+					return this.relay.MaximumLinkSqrDistance;
 				}
 
 				return 0d;
@@ -240,7 +306,7 @@
 		/// <summary>
 		/// Gets the distance to the nearest relay or Kerbin, whichever is closer.
 		/// </summary>
-		public double transmitDistance
+		public double CurrentLinkSqrDistance
 		{
 			get
 			{
@@ -249,10 +315,30 @@
 					return double.PositiveInfinity;
 				}
 
-				return this.relay.transmitDistance;
-			}
-		}
-
+				return this.relay.CurrentLinkSqrDistance;
+			}
+		}
+
+		/// <summary>
+		/// Gets the current link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current link resource rate in EC/MiT.</value>
+		public double CurrentLinkResourceRate
+		{
+			get
+			{
+				if (this.relay == null)
+				{
+					return double.PositiveInfinity;
+				}
+
+				return this.relay.CurrentLinkResourceRate;
+			}
+		}
+
+		/// <summary>
+		/// Gets the link status.
+		/// </summary>
 		public ConnectionStatus LinkStatus
 		{
 			get
@@ -338,16 +424,12 @@
 		{
 			get
 			{
-				this.PreTransmit_SetPacketSize();
-
-				if (this.CanTransmit())
-				{
-					return this.packetSize;
-				}
-				else
-				{
-					return float.Epsilon;
-				}
+				if (this.relay == null)
+				{
+					return float.PositiveInfinity;
+				}
+
+				return this.relay.DataRate;
 			}
 		}
 
@@ -359,16 +441,25 @@
 		{
 			get
 			{
-				this.PreTransmit_SetPacketResourceCost();
-
-				if (this.CanTransmit())
-				{
-					return this.packetResourceCost;
-				}
-				else
-				{
-					return float.PositiveInfinity;
-				}
+				if (this.relay == null)
+				{
+					return double.PositiveInfinity;
+				}
+
+				return this.relay.DataResourceCost;
+			}
+		}
+
+		public double CurrentNetworkResourceRate
+		{
+			get
+			{
+				if (this.relay == null)
+				{
+					return double.PositiveInfinity;
+				}
+
+				return this.relay.CurrentNetworkResourceRate;
 			}
 		}
 
@@ -394,7 +485,7 @@
 		// Build ALL the objects.
 		public ModuleLimitedDataTransmitter () : base()
 		{
-			this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
+			this.ErrorMsg = new ScreenMessage("", 4f, ScreenMessageStyle.UPPER_LEFT);
 			this.packetThrottle = 100f;
 		}
 
@@ -405,23 +496,23 @@
 		{
 			base.OnAwake();
 
-			this._basepacketSize = base.packetSize;
-			this._basepacketResourceCost = base.packetResourceCost;
-
-			Tools.PostDebugMessage(string.Format(
-				"{0} loaded:\n" +
+			this.BasePacketSize = base.packetSize;
+			this.BasePacketResourceCost = base.packetResourceCost;
+			this.moduleInfoContent = new GUIContent();
+
+			this.LogDebug("{0} loaded:\n" +
 				"packetSize: {1}\n" +
 				"packetResourceCost: {2}\n" +
-				"nominalRange: {3}\n" +
+				"nominalTransmitDistance: {3}\n" +
 				"maxPowerFactor: {4}\n" +
 				"maxDataFactor: {5}\n",
-				this.name,
+				this,
 				base.packetSize,
-				this._basepacketResourceCost,
-				this.nominalRange,
+				this.BasePacketResourceCost,
+				this.nominalTransmitDistance,
 				this.maxPowerFactor,
 				this.maxDataFactor
-			));
+			);
 		}
 
 		/// <summary>
@@ -432,15 +523,15 @@
 		{
 			base.OnStart (state);
 
+			this.RecalculateMaxRange();
+
 			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.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.maxTransmitDistance);
+				this.UImaxTransmitDistance = TextTools.Format("{0:S3}m", this.maxTransmitDistance);
 
 				GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
 				GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
@@ -460,7 +551,79 @@
 
 			base.OnLoad (node);
 
-			this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
+			this.RecalculateMaxRange();
+		}
+
+		/// <summary>
+		/// Gets the human-friendly module title.
+		/// </summary>
+		public string GetModuleTitle()
+		{
+			return "Comms Transceiver";
+		}
+
+		/// <summary>
+		/// Returns drawTooltipWidget as a callback for part tooltips.
+		/// </summary>
+		public Callback<Rect> GetDrawModulePanelCallback()
+		{
+			return this.drawTooltipWidget;
+		}
+
+		// Called by Squad's part tooltip system when drawing tooltips.
+		// HACK: Currently hacks around Squad's extraneous layout box, see KSPModders issue #5118
+		private void drawTooltipWidget(Rect rect)
+		{
+			this.moduleInfoContent.text = this.GetInfo();
+
+			if (partTooltipSkin == null)
+			{
+				UnityEngine.Object[] skins = Resources.FindObjectsOfTypeAll(typeof(GUISkin));
+				GUISkin skin;
+				for (int sIdx = 0; sIdx < skins.Length; sIdx++)
+				{
+					skin = (GUISkin)skins[sIdx];
+
+					if (skin.name == tooltipSkinName)
+					{
+						partTooltipSkin = skin;
+						partTooltipBodyStyle = partTooltipSkin.customStyles[0];
+						partTooltipHeaderStyle = partTooltipSkin.customStyles[1];
+					}
+				}
+
+				if (partTooltipSkin == null)
+				{
+					this.LogError("Could not find GUISkin {0}?  Please report this!", tooltipSkinName);
+					return;
+				}
+				else
+				{
+					this.Log("Loaded GUISkin {0}", tooltipSkinName);
+				}
+			}
+
+			float width = rect.width;
+			float orgHeight = rect.height;
+			float height = partTooltipBodyStyle.CalcHeight(this.moduleInfoContent, width);
+
+			rect.height = height;
+
+			GUI.Box(rect, this.moduleInfoContent, partTooltipBodyStyle);
+			GUI.Label(rect, this.GetModuleTitle(), partTooltipHeaderStyle);
+
+			GUILayout.Space(height - orgHeight
+				- partTooltipBodyStyle.padding.bottom - partTooltipBodyStyle.padding.top
+				- 2f * (partTooltipBodyStyle.margin.bottom + partTooltipBodyStyle.margin.top)
+			);
+		}
+
+		/// <summary>
+		/// Returns an empty string, because we don't really have a "primary field" like some modules do.
+		/// </summary>
+		public string GetPrimaryField()
+		{
+			return string.Empty;
 		}
 
 		/// <summary>
@@ -468,18 +631,34 @@
 		/// </summary>
 		public override string GetInfo()
 		{
-			StringBuilder sb = Tools.GetStringBuilder();
-			string text;
-
-			sb.Append(base.GetInfo());
-			sb.AppendFormat(Tools.SIFormatter, "Nominal Range: {0:S3}m\n", this.nominalRange);
-			sb.AppendFormat(Tools.SIFormatter, "Maximum Range: {0:S3}m\n", this.maxTransmitDistance);
-
-			text = sb.ToString();
-
-			Tools.PutStringBuilder(sb);
-
-			return text;
+			using (PooledStringBuilder sb = PooledStringBuilder.Get())
+			{
+				string text;
+
+				sb.Append(base.GetInfo());
+
+				if (ARConfiguration.UseAdditiveRanges)
+				{
+					sb.AppendFormat("Nominal Range to Kerbin: {0:S3}m\n",
+						Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange)
+					);
+					sb.AppendFormat("Maximum Range to Kerbin: {0:S3}m",
+						Math.Sqrt(
+							this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) *
+							ARConfiguration.KerbinRelayRange
+						)
+					);
+				}
+				else
+				{
+					sb.AppendFormat("Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
+					sb.AppendFormat("Maximum Range: {0:S3}m", this.maxTransmitDistance);
+				}
+
+				text = sb.ToString();
+
+				return text;
+			}
 		}
 
 		/// <summary>
@@ -497,13 +676,13 @@
 			{
 				case PartStates.DEAD:
 				case PartStates.DEACTIVATED:
-					Tools.PostDebugMessage(string.Format(
+					this.LogDebug(
 						"{0}: {1} on {2} cannot transmit: {3}",
 						this.GetType().Name,
 						this.part.partInfo.title,
 						this.vessel.vesselName,
 						Enum.GetName(typeof(PartStates), this.part.State)
-					));
+					);
 					return false;
 				default:
 					break;
@@ -512,6 +691,14 @@
 			return this.relay.CanTransmit();
 		}
 
+		public void RecalculateTransmissionRates()
+		{
+			if (this.relay != null)
+			{
+				this.relay.RecalculateTransmissionRates();
+			}
+		}
+
 		/// <summary>
 		/// Finds the nearest relay.
 		/// </summary>
@@ -528,43 +715,29 @@
 		/// returns false.
 		/// </summary>
 		/// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
-		/// <param name="callback">Callback function</param>
-		public new void TransmitData(List<ScienceData> dataQueue, Callback callback)
+		public new void TransmitData(List<ScienceData> dataQueue)
 		{
 			this.LogDebug(
 				"TransmitData(List<ScienceData> dataQueue, Callback callback) called.  dataQueue.Count={0}",
 				dataQueue.Count
 			);
 
-			this.FindNearestRelay();
-
-			this.PreTransmit_SetPacketSize();
-			this.PreTransmit_SetPacketResourceCost();
-
 			if (this.CanTransmit())
 			{
 				ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
 
 				this.LogDebug(
 					"CanTransmit in TransmitData, calling base.TransmitData with dataQueue=[{0}] and callback={1}",
-					dataQueue.SPrint(),
-					callback == null ? "null" : callback.ToString()
+					dataQueue.SPrint()
 				);
 
-				if (callback == null)
-				{
-					base.TransmitData(dataQueue);
-				}
-				else
-				{
-					base.TransmitData(dataQueue, callback);
-				}
+				base.TransmitData(dataQueue);
 			}
 			else
 			{
-				Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
-
-				var logger = Tools.DebugLogger.New(this);
+				this.LogDebug("{0} unable to transmit during TransmitData.", this.part.partInfo.title);
+
+				var logger = PooledDebugLogger.New(this);
 
 				IList<ModuleScienceContainer> vesselContainers = this.vessel.getModulesOfType<ModuleScienceContainer>();
 				ModuleScienceContainer scienceContainer;
@@ -620,65 +793,44 @@
 
 				if (dataQueue.Count > 0)
 				{
-					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++)
+					using (PooledStringBuilder sb = PooledStringBuilder.Get())
 					{
-						data = dataQueue[dIdx];
-						sb.AppendFormat("\t{0}\n", data.title);
+						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];
+							sb.AppendFormat("\t{0}\n", data.title);
+						}
+
+						ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+						this.LogDebug(sb.ToString());
 					}
-
-					ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
-
-					Tools.PostDebugMessage(sb.ToString());
-
-					Tools.PutStringBuilder(sb);
 				}
 
 				this.PostCannotTransmitError();
 			}
 
-			Tools.PostDebugMessage (
-				"distance: " + this.transmitDistance
+			this.LogDebug(
+				"distance: " + this.CurrentLinkSqrDistance
 				+ " packetSize: " + this.packetSize
 				+ " packetResourceCost: " + this.packetResourceCost
 			);
 		}
 
 		/// <summary>
-		/// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
-		/// returns false.
-		/// </summary>
-		/// <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);
-		}
-
-		/// <summary>
 		/// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
 		/// returns false.
 		/// </summary>
 		public new void StartTransmission()
 		{
-			this.FindNearestRelay();
-
-			PreTransmit_SetPacketSize ();
-			PreTransmit_SetPacketResourceCost ();
-
-			Tools.PostDebugMessage (
-				"distance: " + this.transmitDistance
+			this.LogDebug(
+				"distance: " + this.CurrentLinkSqrDistance
 				+ " packetSize: " + this.packetSize
 				+ " packetResourceCost: " + this.packetResourceCost
 				);
@@ -702,27 +854,31 @@
 		{
 			if (this.actionUIUpdate)
 			{
-				this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", 
-					this.MaximumLinkDistance);
+				this.UImaxTransmitDistance = TextTools.Format("{0:S3}m",
+					Math.Sqrt(this.MaximumLinkSqrDistance));
+				this.UInominalLinkDistance = TextTools.Format("{0:S3}m",
+					Math.Sqrt(this.NominalLinkSqrDistance));
 				
 				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 = TextTools.Format("{0:S3}m",
+						Math.Sqrt(this.CurrentLinkSqrDistance));
+					this.UIpacketSize = TextTools.Format("{0:S3}MiT", this.DataRate);
+					this.UIpacketCost = TextTools.Format("{0:S3}EC", this.DataResourceCost);
 				}
 				else
 				{
 					if (this.relay.firstOccludingBody == null)
 					{
-						this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
+						this.UItransmitDistance = TextTools.Format("{0:S3}m",
+							Math.Sqrt(this.CurrentLinkSqrDistance));
 						this.UIrelayStatus = "Out of range";
 					}
 					else
 					{
 						this.UItransmitDistance = "N/A";
-						this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
+						this.UIrelayStatus = TextTools.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
 					}
 					this.UIpacketSize = "N/A";
 					this.UIpacketCost = "N/A";
@@ -734,9 +890,26 @@
 				}
 				else
 				{
-					this.UIrelayTarget = this.targetRelay.ToString();
-				}
-			}
+					if (this.targetRelay != null)
+					{
+						this.UIrelayTarget = this.targetRelay.ToString();
+					}
+					else
+					{
+						this.UIrelayTarget = "A mysterious null entity";
+					}
+				}
+			}
+		}
+
+		public void RecalculateMaxRange()
+		{
+			this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
+
+			#if DEBUG
+			this.Log("Recalculated max range: sqrt({0}) * {1} = {2}",
+				this.maxPowerFactor, this.nominalTransmitDistance, this.maxTransmitDistance);
+			#endif
 		}
 
 		/// <summary>
@@ -745,32 +918,38 @@
 		/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns>
 		public override string ToString()
 		{
-			StringBuilder sb = Tools.GetStringBuilder();
-			string msg;
-
-			sb.Append(this.part.partInfo.title);
-
-			if (vessel != null)
-			{
-				sb.Append(" on ");
-				sb.Append(vessel.vesselName);
-			}
-			else if (
-				this.part != null &&
-				this.part.protoPartSnapshot != null &&
-				this.part.protoPartSnapshot != null &&
-				this.part.protoPartSnapshot.pVesselRef != null
-			)
-			{
-				sb.Append(" on ");
-				sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
-			}
-
-			msg = sb.ToString();
-
-			Tools.PutStringBuilder(sb);
-
-			return msg;
+			using (PooledStringBuilder sb = PooledStringBuilder.Get())
+			{
+				string msg;
+
+				if (this.part != null && this.part.partInfo != null)
+				{
+					sb.Append(this.part.partInfo.title);
+				}
+				else
+				{
+					sb.Append(this.GetType().Name);
+				}
+
+				if (vessel != null)
+				{
+					sb.Append(" on ");
+					sb.Append(vessel.vesselName);
+				}
+				else if (
+					this.part != null &&
+					this.part.protoPartSnapshot != null &&
+					this.part.protoPartSnapshot != null &&
+					this.part.protoPartSnapshot.pVesselRef != null)
+				{
+					sb.Append(" on ");
+					sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
+				}
+
+				msg = sb.ToString();
+
+				return msg;
+			}
 		}
 
 		// When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty.
@@ -806,79 +985,37 @@
 				ErrorText
 			);
 
-			Tools.PostDebugMessage(this.GetType().Name + ": " + this.ErrorMsg.message);
-
-			ScreenMessages.PostScreenMessage(this.ErrorMsg, false);
-		}
-
-		// Before transmission, set packetResourceCost.  Per above, packet cost increases with the square of
-		// distance.  packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point
-		// transmission fails (see CanTransmit).
-		private void PreTransmit_SetPacketResourceCost()
-		{
-			if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.NominalLinkDistance)
-			{
-				base.packetResourceCost = this._basepacketResourceCost;
-			}
-			else
-			{
-				float rangeFactor = (float)(this.transmitDistance / this.NominalLinkDistance);
-				rangeFactor *= rangeFactor;
-
-				base.packetResourceCost = this._basepacketResourceCost
-					* rangeFactor;
-			}
-
-			base.packetResourceCost *= this.packetThrottle / 100f;
-		}
-
-		// Before transmission, set packetSize.  Per above, packet size increases with the inverse square of
-		// distance.  packetSize maxes out at _basepacketSize * maxDataFactor.
-		private void PreTransmit_SetPacketSize()
-		{
-			if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.NominalLinkDistance)
-			{
-				base.packetSize = this._basepacketSize;
-			}
-			else
-			{
-				float rangeFactor = (float)(this.NominalLinkDistance / this.transmitDistance);
-				rangeFactor *= rangeFactor;
-
-				base.packetSize = Mathf.Min(
-					this._basepacketSize * rangeFactor,
-					this._basepacketSize * this.maxDataFactor);
-			}
-
-			base.packetSize *= this.packetThrottle / 100f;
+			this.LogDebug(this.ErrorMsg.message);
+
+			ScreenMessages.PostScreenMessage(this.ErrorMsg);
 		}
 
 		private string buildTransmitMessage()
 		{
-			StringBuilder sb = Tools.GetStringBuilder();
-			string msg;
-
-			sb.Append("[");
-			sb.Append(base.part.partInfo.title);
-			sb.Append("]: ");
-
-			sb.Append("Beginning transmission ");
-
-			if (this.KerbinDirect)
-			{
-				sb.Append("directly to Kerbin.");
-			}
-			else
-			{
-				sb.Append("via ");
-				sb.Append(this.relay.targetRelay);
-			}
-
-			msg = sb.ToString();
-
-			Tools.PutStringBuilder(sb);
-
-			return msg;
+			using (PooledStringBuilder sb = PooledStringBuilder.Get())
+			{
+				string msg;
+
+				sb.Append("[");
+				sb.Append(base.part.partInfo.title);
+				sb.Append("]: ");
+
+				sb.Append("Beginning transmission ");
+
+				if (this.KerbinDirect)
+				{
+					sb.Append("directly to Kerbin.");
+				}
+				else
+				{
+					sb.Append("via ");
+					sb.Append(this.relay.targetRelay);
+				}
+
+				msg = sb.ToString();
+
+				return msg;
+			}
 		}
 
 		#if DEBUG
@@ -886,8 +1023,8 @@
 		[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
 		public void DebugInfo()
 		{
-			PreTransmit_SetPacketSize ();
-			PreTransmit_SetPacketResourceCost ();
+			if (this.relay != null)
+				this.relay.RecalculateTransmissionRates();
 
 			DebugPartModule.DumpClassObject(this);
 		}
@@ -895,27 +1032,26 @@
 		[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
 		public void PrintAllVessels()
 		{
-			StringBuilder sb = Tools.GetStringBuilder();
-			
-			sb.Append("Dumping FlightGlobals.Vessels:");
-
-			Vessel vessel;
-			for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
-			{
-				vessel = FlightGlobals.Vessels[i];
-				sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
-			}
+			using (PooledStringBuilder sb = PooledStringBuilder.Get())
+			{
+				sb.Append("Dumping FlightGlobals.Vessels:");
+
+				Vessel vessel;
+				for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
+				{
+					vessel = FlightGlobals.Vessels[i];
+					sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
+				}
 		    
-			Tools.PostDebugMessage(sb.ToString());
-
-			Tools.PutStringBuilder(sb);
+				ToadicusTools.Logging.PostDebugMessage(sb.ToString());
+			}
 		}
 		 
-		[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
+		/*[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
 		public void DumpRelayDB()
 		{
 			RelayDatabase.Instance.Dump();
-		}
+		}*/
 		#endif
 	}
 }

--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -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.9.*")]
+[assembly: AssemblyVersion("1.10.3.*")]
 // 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
@@ -29,6 +29,7 @@
 using KSP;
 using System;
 using ToadicusTools;
+using ToadicusTools.Text;
 
 namespace AntennaRange
 {
@@ -58,8 +59,7 @@
 				}
 				else
 				{
-					Tools.PostErrorMessage("{0}: Could not fetch vessel!  {1}{2}{3}",
-						this.ToString(),
+					this.LogError("Could not fetch vessel!  {0}{1}{2}",
 						this.protoPart == null ? "\n\tprotoPart=null" : string.Empty,
 						this.protoPart != null && this.protoPart.pVesselRef == null ?
 							"\n\tthis.protoPart.pVesselRef=null" : string.Empty,
@@ -72,6 +72,102 @@
 			}
 		}
 
+		public float PacketSize
+		{
+			get
+			{
+				if (this.moduleRef == null)
+				{
+					return float.NaN;
+				}
+
+				return this.moduleRef.PacketSize;
+			}
+			set
+			{
+				if (this.moduleRef == null)
+				{
+					return;
+				}
+
+				this.moduleRef.PacketSize = value;
+			}
+		}
+
+		public float BasePacketSize
+		{
+			get
+			{
+				if (this.moduleRef == null)
+				{
+					return float.NaN;
+				}
+
+				return this.moduleRef.BasePacketSize;
+			}
+		}
+
+		public float PacketResourceCost
+		{
+			get
+			{
+				if (this.moduleRef == null)
+				{
+					return float.NaN;
+				}
+
+				return this.moduleRef.PacketResourceCost;
+			}
+			set
+			{
+				if (this.moduleRef == null)
+				{
+					return;
+				}
+
+				this.moduleRef.PacketResourceCost = value;
+			}
+		}
+
+		public float BasePacketResourceCost
+		{
+			get
+			{
+				if (this.moduleRef == null)
+				{
+					return float.NaN;
+				}
+
+				return this.moduleRef.BasePacketResourceCost;
+			}
+		}
+
+		public float PacketThrottle
+		{
+			get
+			{
+				if (this.moduleRef == null)
+				{
+					return float.NaN;
+				}
+
+				return this.moduleRef.PacketThrottle;
+			}
+		}
+
+		public float MaxDataFactor
+		{
+			get
+			{
+				if (this.moduleRef == null)
+				{
+					return float.NaN;
+				}
+
+				return this.moduleRef.MaxDataFactor;
+			}
+		}
+
 		/// <summary>
 		/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
 		/// </summary>
@@ -120,7 +216,7 @@
 			PartStates partState = (PartStates)this.protoPart.state;
 			if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
 			{
-				Tools.PostDebugMessage(string.Format(
+				Logging.PostDebugMessage(string.Format(
 					"{0}: {1} on {2} cannot transmit: {3}",
 					this.GetType().Name,
 					this.Title,
@@ -132,24 +228,31 @@
 			return base.CanTransmit();
 		}
 
+		public void RecalculateMaxRange()
+		{
+			if (this.moduleRef != null)
+			{
+				this.moduleRef.RecalculateMaxRange();
+			}
+		}
+
 		/// <summary>
 		/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.
 		/// </summary>
 		/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.</returns>
 		public override string ToString()
 		{
-			System.Text.StringBuilder sb = Tools.GetStringBuilder();
-
-			sb.Append(this.Title);
-
-			if (this.protoPart != null && this.protoPart.pVesselRef != null)
-			{
-				sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
-			}
-
-			Tools.PutStringBuilder(sb);
-
-			return sb.ToString();
+			using (PooledStringBuilder sb = PooledStringBuilder.Get())
+			{
+				sb.Append(this.Title);
+
+				if (this.protoPart != null && this.protoPart.pVesselRef != null)
+				{
+					sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
+				}
+
+				return sb.ToString();
+			}
 		}
 
 		/// <summary>
@@ -162,7 +265,9 @@
 		{
 			this.protoPart = pps;
 
-			Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
+			this.Log("constructed ({0})", this.GetType().Name);
+
+			this.RecalculateMaxRange();
 		}
 	}
 }

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
@@ -96,10 +96,10 @@
 		public void DirtyVessel(Vessel vessel)
 		{
 			#if DEBUG
-			Tools.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}",
+			Logging.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);
+			Logging.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName);
 			#endif
 
 			this.relayDatabase.Remove(vessel.id);
@@ -109,7 +109,7 @@
 
 		public void ClearCache()
 		{
-			Tools.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache.");
+			Logging.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache.");
 
 			this.relayDatabase.Clear();
 			this.bestRelayTable.Clear();
@@ -206,7 +206,7 @@
 				// or if we've just docked or undocked)...
 				if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
 				{
-					Tools.PostDebugMessage(string.Format(
+					Logging.PostDebugMessage(string.Format(
 						"{0}: dirtying cache for vessel '{1}' ({2}).",
 						this.GetType().Name,
 						vessel.vesselName,
@@ -222,7 +222,7 @@
 		// Runs when the player requests a scene change, such as when changing vessels or leaving flight.
 		private void onSceneChange(GameScenes scene)
 		{
-			Tools.PostDebugMessage(
+			Logging.PostDebugMessage(
 				"RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}.  ActiveVessel is {2}",
 				HighLogic.LoadedScene,
 				scene,
@@ -235,7 +235,7 @@
 				{
 					if (FlightGlobals.ActiveVessel != null)
 					{
-						Tools.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.",
+						Logging.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.",
 							FlightGlobals.ActiveVessel.vesselName);
 
 						this.onVesselEvent(FlightGlobals.ActiveVessel);
@@ -275,7 +275,7 @@
 			// We're going to completely regen this table, so dump the current contents.
 			relays.Clear();
 
-			Tools.PostDebugMessage(string.Format(
+			Logging.PostDebugMessage(string.Format(
 				"{0}: Getting antenna relays from vessel {1}.",
 				"IAntennaRelay",
 				vessel.vesselName
@@ -287,7 +287,7 @@
 
 			// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
 			if (vessel.loaded) {
-				Tools.PostDebugMessage(string.Format(
+				Logging.PostDebugMessage(string.Format(
 					"{0}: vessel {1} is loaded, searching for modules in loaded parts.",
 					"IAntennaRelay",
 					vessel.vesselName
@@ -327,7 +327,7 @@
 			// If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
 			else
 			{
-				Tools.PostDebugMessage(string.Format(
+				Logging.PostDebugMessage(string.Format(
 					"{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
 					this.GetType().Name,
 					vessel.vesselName
@@ -339,7 +339,7 @@
 				{
 					pps = vessel.protoVessel.protoPartSnapshots[ppsIdx];
 
-					Tools.PostDebugMessage(string.Format(
+					Logging.PostDebugMessage(string.Format(
 						"{0}: Searching in protopartsnapshot {1}",
 						this.GetType().Name,
 						pps
@@ -348,7 +348,7 @@
 					// ...Fetch the prefab, because it's more useful for what we're doing.
 					Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
 
-					Tools.PostDebugMessage(string.Format(
+					Logging.PostDebugMessage(string.Format(
 						"{0}: Got partPrefab {1} in protopartsnapshot {2}",
 						this.GetType().Name,
 						partPrefab,
@@ -361,7 +361,7 @@
 					{
 						module = partPrefab.Modules[modIdx];
 
-						Tools.PostDebugMessage(string.Format(
+						Logging.PostDebugMessage(string.Format(
 							"{0}: Searching in partmodule {1}",
 							this.GetType().Name,
 							module
@@ -370,7 +370,7 @@
 						// ...if the module is a relay...
 						if (module is IAntennaRelay)
 						{
-							Tools.PostDebugMessage(string.Format(
+							Logging.PostDebugMessage(string.Format(
 								"{0}: partmodule {1} is antennarelay",
 								this.GetType().Name,
 								module
@@ -395,7 +395,7 @@
 
 			this.bestRelayTable[vessel.id] = bestRelay;
 
-			Tools.PostDebugMessage(string.Format(
+			Logging.PostDebugMessage(string.Format(
 				"{0}: vessel '{1}' ({2}) has {3} transmitters.",
 				"IAntennaRelay",
 				vessel.vesselName,
@@ -436,7 +436,7 @@
 			GameEvents.onPartUndock.Remove(this.onPartEvent);
 			GameEvents.onGameStateLoad.Remove(this.onGameLoaded);
 
-			Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
+			Logging.PostDebugMessage(this.GetType().Name + " destroyed.");
 
 			KSPLog.print(string.Format(
 				"{0} destructed.  Cache hits: {1}, misses: {2}, hit rate: {3:P1}",
@@ -450,28 +450,27 @@
 		#if DEBUG
 		public void Dump()
 		{
-			StringBuilder sb = Tools.GetStringBuilder();
-
-			sb.Append("Dumping RelayDatabase:");
-
-			var dbEnum = this.relayDatabase.GetEnumerator();
-			IList<IAntennaRelay> vesselRelays;
-			while (dbEnum.MoveNext())
-			{
-				sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key);
-
-				vesselRelays = dbEnum.Current.Value;
-				IAntennaRelay relay;
-				for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
-				{
-					relay = vesselRelays[rIdx];
-					sb.AppendFormat("\n\t{0}", relay.ToString());
-				}
-			}
-
-			Tools.PostDebugMessage(sb.ToString());
-
-			Tools.PutStringBuilder(sb);
+			using (ToadicusTools.Text.PooledStringBuilder sb = ToadicusTools.Text.PooledStringBuilder.Get())
+			{
+				sb.Append("Dumping RelayDatabase:");
+
+				var dbEnum = this.relayDatabase.GetEnumerator();
+				IList<IAntennaRelay> vesselRelays;
+				while (dbEnum.MoveNext())
+				{
+					sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key);
+
+					vesselRelays = dbEnum.Current.Value;
+					IAntennaRelay relay;
+					for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
+					{
+						relay = vesselRelays[rIdx];
+						sb.AppendFormat("\n\t{0}", relay.ToString());
+					}
+				}
+
+				Logging.PostDebugMessage(sb.ToString());
+			}
 		}
 		#endif
 	}

--- a/RelayExtensions.cs
+++ b/RelayExtensions.cs
@@ -28,7 +28,7 @@
 
 using System;
 using System.Collections.Generic;
-using ToadicusTools;
+using ToadicusTools.Extensions;
 
 namespace AntennaRange
 {
@@ -116,7 +116,7 @@
 		/// </summary>
 		/// <param name="relay">This <see cref="IAntennaRelay"/></param>
 		/// <param name="vessel">A <see cref="Vessel"/></param>
-		public static double sqrDistanceTo(this AntennaRelay relay, Vessel vessel)
+		public static double SqrDistanceTo(this AntennaRelay relay, Vessel vessel)
 		{
 			return relay.vessel.sqrDistanceTo(vessel);
 		}
@@ -126,9 +126,11 @@
 		/// </summary>
 		/// <param name="relay">This <see cref="IAntennaRelay"/></param>
 		/// <param name="body">A <see cref="CelestialBody"/></param>
-		public static double sqrDistanceTo(this AntennaRelay relay, CelestialBody body)
-		{
-			return relay.vessel.sqrDistanceTo(body);
+		public static double SqrDistanceTo(this AntennaRelay relay, CelestialBody body)
+		{
+			double dist = (relay.vessel.GetWorldPos3D() - body.position).magnitude - body.Radius;
+
+			return dist * dist;
 		}
 
 		/// <summary>
@@ -136,9 +138,59 @@
 		/// </summary>
 		/// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
 		/// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
-		public static double sqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
+		public static double SqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
 		{
 			return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
+		}
+
+		public static double MaxLinkSqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
+		{
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				return relayOne.maxTransmitDistance * relayTwo.maxTransmitDistance;
+			}
+			else
+			{
+				return relayOne.maxTransmitDistance * relayOne.maxTransmitDistance;
+			}
+		}
+
+		public static double MaxLinkSqrDistanceTo(this AntennaRelay relayOne, CelestialBody body)
+		{
+			if (body != AntennaRelay.Kerbin)
+			{
+				return 0d;
+			}
+
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				return relayOne.maxTransmitDistance * ARConfiguration.KerbinRelayRange;
+			}
+			else
+			{
+				return relayOne.maxTransmitDistance * relayOne.maxTransmitDistance;
+			}
+		}
+
+		public static bool IsInRangeOf(this AntennaRelay relayOne, IAntennaRelay relayTwo)
+		{
+			if (relayOne == null || relayTwo == null)
+			{
+				return false;
+			}
+
+			return relayOne.SqrDistanceTo(relayTwo) <= relayOne.MaxLinkSqrDistanceTo(relayTwo);
+		}
+
+
+		public static bool IsInRangeOf(this AntennaRelay relayOne, CelestialBody body)
+		{
+			if (relayOne == null || body == null)
+			{
+				return false;
+			}
+
+			return relayOne.SqrDistanceTo(body) <= relayOne.MaxLinkSqrDistanceTo(body);
 		}
 
 		/// <summary>
@@ -212,6 +264,72 @@
 		public static IAntennaRelay GetBestRelay(this Vessel vessel)
 		{
 			return RelayDatabase.Instance.GetBestVesselRelay(vessel);
+		}
+
+		/// <summary>
+		/// Logs a message on behalf of this relay
+		/// </summary>
+		public static void Log(this AntennaRelay relay, string format, params object[] args)
+		{
+			ToadicusTools.Logging.PostLogMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
+		}
+
+		/// <summary>
+		/// Logs a message on behalf of this relay
+		/// </summary>
+		public static void Log(this AntennaRelay relay, string msg)
+		{
+			ToadicusTools.Logging.PostLogMessage("[{0}] {1}", relay.ToString(), msg);
+		}
+
+		/// <summary>
+		/// Logs a warning message on behalf of this relay
+		/// </summary>
+		public static void LogWarning(this AntennaRelay relay, string format, params object[] args)
+		{
+			ToadicusTools.Logging.PostWarningMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
+		}
+
+		/// <summary>
+		/// Logs a warning message on behalf of this relay
+		/// </summary>
+		public static void LogWarning(this AntennaRelay relay, string msg)
+		{
+			ToadicusTools.Logging.PostWarningMessage("[{0}] {1}", relay.ToString(), msg);
+		}
+
+		/// <summary>
+		/// Logs an error message on behalf of this relay
+		/// </summary>
+		public static void LogError(this AntennaRelay relay, string format, params object[] args)
+		{
+			ToadicusTools.Logging.PostErrorMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
+		}
+
+		/// <summary>
+		/// Logs an error message on behalf of this relay
+		/// </summary>
+		public static void LogError(this AntennaRelay relay, string msg)
+		{
+			ToadicusTools.Logging.PostErrorMessage("[{0}] {1}", relay.ToString(), msg);
+		}
+
+		/// <summary>
+		/// Logs a debug-only message on behalf of this relay
+		/// </summary>
+		[System.Diagnostics.Conditional("DEBUG")]
+		public static void LogDebug(this AntennaRelay relay, string format, params object[] args)
+		{
+			ToadicusTools.Logging.PostDebugMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
+		}
+
+		/// <summary>
+		/// Logs a debug-only message on behalf of this relay
+		/// </summary>
+		[System.Diagnostics.Conditional("DEBUG")]
+		public static void LogDebug(this AntennaRelay relay, string msg)
+		{
+			ToadicusTools.Logging.PostDebugMessage("[{0}] {1}", relay.ToString(), msg);
 		}
 	}