Fixed RelayDataCost addition. Fixed persistent loads of base packet costs. Fixed some debug logging.
Fixed RelayDataCost addition. Fixed persistent loads of base packet costs. Fixed some debug logging.

--- a/ARConfiguration.cs
+++ b/ARConfiguration.cs
@@ -6,6 +6,8 @@
 using KSP;
 using KSP.UI.Screens;
 using System;
+using System.Collections.Generic;
+using System.Reflection;
 using ToadicusTools.Extensions;
 using ToadicusTools.Text;
 using ToadicusTools.GUIUtils;
@@ -118,6 +120,10 @@
 			}
 		}
 
+		/// <summary>
+		/// Gets a value indicating whether we should use Toolbar if available.
+		/// </summary>
+		/// <value><c>true</c> if we should use Toolbar if available; otherwise, <c>false</c>.</value>
 		public static bool UseToolbarIfAvailable
 		{
 			get;
@@ -125,6 +131,8 @@
 		}
 
 #pragma warning disable 1591
+
+		private static MethodInfo partLoader_CompilePartInfo;
 
 		private bool showConfigWindow;
 		private Rect configWindowPos;
@@ -226,6 +234,16 @@
 					this.trackingStationRanges.SPrint());
 			}
 
+			if (partLoader_CompilePartInfo == null)
+			{
+				partLoader_CompilePartInfo = typeof(PartLoader).GetMethod(
+					"CompilePartInfo",
+					BindingFlags.NonPublic | BindingFlags.Instance
+				);
+
+				this.Log("Fetched PartLoader.CompilePartInfo: {0}", partLoader_CompilePartInfo);
+			}
+
 			this.runOnce = true;
 
 			this.LogDebug("Awake.");
@@ -241,6 +259,7 @@
 				this.runOnce = false;
 
 				this.SetKerbinRelayRange();
+				this.updateModuleInfos();
 			}
 		}
 
@@ -348,6 +367,8 @@
 			{
 				ARConfiguration.FixedPowerCost = fixedPowerCost;
 				this.SaveConfigValue(FIXED_POWER_KEY, fixedPowerCost);
+
+				this.updateModuleInfos();
 			}
 
 			GUILayout.EndHorizontal();
@@ -359,6 +380,8 @@
 			{
 				ARConfiguration.UseAdditiveRanges = useAdditive;
 				this.SaveConfigValue(USE_ADDITIVE_KEY, useAdditive);
+
+				this.updateModuleInfos();
 			}
 
 			GUILayout.EndHorizontal();
@@ -464,6 +487,92 @@
 			{
 				this.Log("Caught onFacilityUpgraded for {0} at level {1}", fac.id, lvl);
 				this.SetKerbinRelayRange();
+
+				this.updateModuleInfos();
+			}
+		}
+
+		private void updateModuleInfos()
+		{
+			if (PartLoader.Instance != null && PartLoader.Instance.parts != null)
+			{
+				this.Log("Updating module infos in PartLoader");
+				this.updateModuleInfos(PartLoader.Instance.parts);
+			}
+
+			if (RDTestSceneLoader.Instance != null && RDTestSceneLoader.Instance.partsList != null)
+			{
+				this.Log("Updating module infos in RDTestSceneLoader");
+				this.updateModuleInfos(RDTestSceneLoader.Instance.partsList);
+			}
+		}
+
+		private void updateModuleInfos(List<AvailablePart> partsList)
+		{
+			if (partLoader_CompilePartInfo == null)
+			{
+				this.LogError("Cannot recompile part info; partLoader_CompilePartInfo not found.");
+				return;
+			}
+
+			if (PartLoader.Instance == null)
+			{
+				this.LogError("Cannot recompile part info; PartLoader.Instance is null.");
+				return;
+			}
+
+			// We need to go find all of the prefabs and update them, because Squad broke IModuleInfo.
+			AvailablePart availablePart;
+			Part partPrefab;
+			PartModule modulePrefab;
+			object[] compileArgs = new object[2];
+
+			this.Log("Updating module infos...");
+
+			for (int apIdx = 0; apIdx < partsList.Count; apIdx++)
+			{
+				availablePart = partsList[apIdx];
+
+				if (availablePart == null)
+				{
+					continue;
+				}
+
+				partPrefab = availablePart.partPrefab;
+
+				if (partPrefab == null || partPrefab.Modules == null)
+				{
+					continue;
+				}
+
+				for (int pmIdx = 0; pmIdx < partPrefab.Modules.Count; pmIdx++)
+				{
+					modulePrefab = partPrefab.Modules[pmIdx];
+
+					if (modulePrefab == null)
+					{
+						continue;
+					}
+
+					if (modulePrefab is IAntennaRelay)
+					{
+						this.Log("Found prefab IAntennaRelay {0}", modulePrefab);
+
+						this.Log("Recompiling part and module info for {0}", availablePart.name);
+
+						availablePart.moduleInfos.Clear();
+						availablePart.resourceInfos.Clear();
+
+						compileArgs[0] = availablePart;
+						compileArgs[1] = partPrefab;
+                        if (PartLoader.Instance != null)
+                            partLoader_CompilePartInfo.Invoke(PartLoader.Instance, compileArgs);
+                        else
+                            this.Log("PartLoader.Instance is null");
+
+						break;
+					}
+				}
 			}
 		}
 

--- a/ARFlightController.cs
+++ b/ARFlightController.cs
@@ -157,8 +157,8 @@
 				this.toolbarButton.TexturePath = this.toolbarTextures[ConnectionStatus.None];
 				this.toolbarButton.Text = "AntennaRange";
 				this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
-				this.toolbarButton.OnClick += (e) => (this.buttonToggle());
-			}
+                this.toolbarButton.OnClick += (e) => { this.buttonToggle(); };
+            }
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
 			GameEvents.onVesselChange.Add(this.onVesselChange);
@@ -173,7 +173,7 @@
 
 			VesselCommand availableCommand;
 
-			if (ARConfiguration.RequireConnectionForControl)
+			if (ARConfiguration.RequireConnectionForControl && this.vessel != null)
 			{
 				availableCommand = this.vessel.CurrentCommand();
 			}
@@ -317,8 +317,8 @@
 
 					log.AppendFormat("\n\tDoing target search for useful relay {0}", relay);
 
+					relay.FindNearestRelay();
 					relay.RecalculateTransmissionRates();
-					relay.FindNearestRelay();
 				}
 
 				// Very last, find routes for the non-best relays on the active vessel.
@@ -334,8 +334,8 @@
 
 					log.AppendFormat("\nFinding nearest relay for active vessel relay {0}", relay);
 
+					relay.FindNearestRelay();
 					relay.RecalculateTransmissionRates();
-					relay.FindNearestRelay();
 				}
 
 				if (this.toolbarButton != null || this.appLauncherButton != null)

--- a/ARMapRenderer.cs
+++ b/ARMapRenderer.cs
@@ -322,6 +322,12 @@
 				while (enumerator.MoveNext())
 				{
 					lineRenderer = enumerator.Current;
+
+					if (lineRenderer == null)
+					{
+						continue;
+					}
+
 					lineRenderer.enabled = false;
 
 					if (freeObjects)

--- a/AntennaRange.csproj
+++ b/AntennaRange.csproj
@@ -3,8 +3,6 @@
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug_win</Configuration>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.30703</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
     <ProjectGuid>{B36F2C11-962E-4A75-9F41-61AD56D11493}</ProjectGuid>
     <OutputType>Library</OutputType>
     <RootNamespace>AntennaRange</RootNamespace>
@@ -80,6 +78,7 @@
     <Compile Include="ARConfiguration.cs" />
     <Compile Include="ARFlightController.cs" />
     <Compile Include="ARMapRenderer.cs" />
+    <Compile Include="RelayDataCost.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
@@ -102,13 +101,13 @@
     </Reference>
   </ItemGroup>
   <ItemGroup>
+    <None Include="GameData\AntennaRange\AntennaRange.cfg" />
+    <None Include="GameData\AntennaRange\ATM_AntennaRange.cfg" />
+  </ItemGroup>
+  <ItemGroup>
     <ProjectReference Include="..\ToadicusTools\ToadicusTools.csproj">
       <Project>{D48A5542-6655-4149-BC27-B27DF0466F1C}</Project>
       <Name>ToadicusTools</Name>
     </ProjectReference>
   </ItemGroup>
-  <ItemGroup>
-    <None Include="GameData\AntennaRange\AntennaRange.cfg" />
-    <None Include="GameData\AntennaRange\ATM_AntennaRange.cfg" />
-  </ItemGroup>
 </Project>

--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -161,35 +161,59 @@
 		/// Gets the current link resource rate in EC/MiT.
 		/// </summary>
 		/// <value>The current link resource rate in EC/MiT.</value>
-		public virtual double CurrentLinkResourceRate
+		public virtual RelayDataCost CurrentLinkCost
 		{
 			get
 			{
-				return this.DataResourceCost;
-			}
-		}
-
-		public virtual double CurrentNetworkResourceRate
+				return this.moduleRef?.CurrentLinkCost ?? RelayDataCost.Infinity;
+			}
+			set
+			{
+				throw new NotImplementedException(
+					string.Format(
+						"{0} must not assign CurrentLinkCost.  This is probably a bug.",
+						this.GetType().FullName
+					)
+				);
+			}
+		}
+
+		/// <summary>
+		/// Gets the current network link cost back to Kerbin, in EC/MiT.
+		/// </summary>
+		/// <value>The current network link cost back to Kerbin, in EC/MiT.</value>
+		public virtual RelayDataCost CurrentNetworkLinkCost
 		{
 			get
 			{
-				double totalRate = 0;
-
-				IAntennaRelay nextLink = this.moduleRef;
-
-				while (nextLink != null)
-				{
-					totalRate += nextLink.DataResourceCost;
-
-					if (nextLink.KerbinDirect)
+				RelayDataCost cost = new RelayDataCost();
+
+				IAntennaRelay relay = this.moduleRef;
+
+				ushort iters = 0;
+				while (relay != null)
+				{
+					cost += relay.CurrentLinkCost;
+
+					if (relay.KerbinDirect)
 					{
 						break;
 					}
 
-					nextLink = nextLink.targetRelay;
-				}
-
-				return totalRate;
+					iters++;
+
+					if (iters > 255)
+					{
+						this.LogError("Bailing out of AntennaRelay.CurrentNetworkLinkCost because it looks like " +
+							"we're stuck in an infinite loop.  This is probably a bug.");
+						
+						break;
+					}
+
+					relay = relay.targetRelay;
+				}
+
+				return cost;
 			}
 		}
 
@@ -220,69 +244,6 @@
 			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.
@@ -293,67 +254,84 @@
 			return this.canTransmit;
 		}
 
-		// Before transmission, set packetSize.  Per above, packet size increases with the inverse square of
-		// distance.  packetSize maxes out at _basepacketSize * maxDataFactor.
+		/// <summary>
+		/// Recalculates the transmission rates.
+		/// </summary>
 		public void RecalculateTransmissionRates()
 		{
-			float rangeFactor = (float)(this.NominalLinkSqrDistance / this.CurrentLinkSqrDistance);
+			if (!this.canTransmit) {
+				this.moduleRef.CurrentLinkCost = RelayDataCost.Infinity;
+				return;
+			}
+
+			RelayDataCost cost = this.GetPotentialLinkCost(this.CurrentLinkSqrDistance, this.NominalLinkSqrDistance);
+
+			this.moduleRef.CurrentLinkCost = cost;
+		}
+
+		/// <summary>
+		/// Gets the potential link cost, in EC/MiT.
+		/// </summary>
+		/// <returns>The potential link cost, in EC/MiT.</returns>
+		/// <param name="currentSqrDistance">Square of the current distance to the target</param>
+		/// <param name="nominalSqrDistance">Square of the nominal range to the target.</param>
+		public RelayDataCost GetPotentialLinkCost(double currentSqrDistance, double nominalSqrDistance)
+		{
+			RelayDataCost linkCost = new RelayDataCost();
+
+			float rangeFactor = (float)(nominalSqrDistance / currentSqrDistance);
+
+			RelayDataCost baseCost = this.moduleRef?.BaseLinkCost ?? RelayDataCost.Infinity;
 
 			if (ARConfiguration.FixedPowerCost)
 			{
-				this.moduleRef.PacketResourceCost = this.moduleRef.BasePacketResourceCost;
-
-				this.moduleRef.PacketSize = Mathf.Min(
-					this.moduleRef.BasePacketSize * rangeFactor,
-					this.moduleRef.BasePacketSize * this.moduleRef.MaxDataFactor
+				linkCost.PacketResourceCost = baseCost.PacketResourceCost;
+
+				linkCost.PacketSize = Mathf.Min(
+					baseCost.PacketSize * rangeFactor,
+					baseCost.PacketSize * this.moduleRef.MaxDataFactor
 				);
 			}
 			else
 			{
-				if (this.CurrentLinkSqrDistance > this.NominalLinkSqrDistance)
-				{
-					this.moduleRef.PacketSize = this.moduleRef.BasePacketSize;
-					this.moduleRef.PacketResourceCost = this.moduleRef.BasePacketResourceCost / rangeFactor;
+				if (currentSqrDistance > nominalSqrDistance)
+				{
+					linkCost.PacketSize = baseCost.PacketSize;
+					linkCost.PacketResourceCost = baseCost.PacketResourceCost / rangeFactor;
 				}
 				else
 				{
-					this.moduleRef.PacketSize = Mathf.Min(
-						this.moduleRef.BasePacketSize * rangeFactor,
-						this.moduleRef.BasePacketSize * this.moduleRef.MaxDataFactor
+					linkCost.PacketSize = Mathf.Min(
+						baseCost.PacketSize * rangeFactor,
+						baseCost.PacketSize * 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)
+					linkCost.PacketResourceCost = baseCost.PacketResourceCost;
+				}
+			}
+
+			linkCost.PacketResourceCost *= this.moduleRef.PacketThrottle / 100f;
+			linkCost.PacketSize *= this.moduleRef.PacketThrottle / 100f;
+
+			return linkCost;
+		}
+
+		/// <summary>
+		/// Gets the potential link cost, in EC/MiT.
+		/// </summary>
+		/// <returns>The potential link cost, in EC/MiT.</returns>
+		/// <param name="potentialTarget">Potential target relay.</param>
+		public RelayDataCost GetPotentialLinkCost(IAntennaRelay potentialTarget)
 		{
 			if (potentialTarget == null)
 			{
-				return double.PositiveInfinity;
+				return RelayDataCost.Infinity;
+			}
+
+			double currentSqrDistance = this.SqrDistanceTo(potentialTarget);
+
+			if (currentSqrDistance > this.MaxLinkSqrDistanceTo(potentialTarget))
+			{
+				return RelayDataCost.Infinity;
 			}
 
 			double nominalSqrDistance;
@@ -366,16 +344,26 @@
 				nominalSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
 			}
 
-			double currentSqrDistance = this.SqrDistanceTo(potentialTarget);
-
 			return GetPotentialLinkCost(currentSqrDistance, nominalSqrDistance);
 		}
 
-		public double GetPotentialLinkCost(CelestialBody body)
+		/// <summary>
+		/// Gets the potential link cost, in EC/MiT.
+		/// </summary>
+		/// <returns>The potential link cost, in EC/MiT.</returns>
+		/// <param name="body">Potential target Body</param>
+		public RelayDataCost GetPotentialLinkCost(CelestialBody body)
 		{
 			if (body == null || body != Kerbin)
 			{
-				return double.PositiveInfinity;
+				return RelayDataCost.Infinity;
+			}
+
+			double currentSqrDistance = this.SqrDistanceTo(body);
+
+			if (currentSqrDistance > this.MaxLinkSqrDistanceTo(body))
+			{
+				return RelayDataCost.Infinity;
 			}
 
 			double nominalSqrDistance;
@@ -387,8 +375,6 @@
 			{
 				nominalSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
 			}
-
-			double currentSqrDistance = this.SqrDistanceTo(body);
 
 			return GetPotentialLinkCost(currentSqrDistance, nominalSqrDistance);
 		}
@@ -442,12 +428,12 @@
 			CelestialBody bodyOccludingBestOccludedRelay = null;
 			IAntennaRelay needle;
 
-			double cheapestRelayRate = double.PositiveInfinity;
-			double cheapestOccludedRelayRate = double.PositiveInfinity;
-
-			double potentialRelayRate;
-
-			double kerbinRelayRate = this.GetPotentialLinkCost(Kerbin);
+			RelayDataCost cheapestRelayRate = RelayDataCost.Infinity;
+			RelayDataCost cheapestOccludedRelayRate = RelayDataCost.Infinity;
+
+			RelayDataCost potentialRelayRate;
+
+			RelayDataCost kerbinRelayRate = this.GetPotentialLinkCost(Kerbin);
 
 			bool isCircular;
 			int iterCount;
@@ -503,7 +489,15 @@
 
 				// Find the distance from here to the vessel...
 				log.Append("\n\tgetting cost to potential vessel");
-				potentialRelayRate = potentialBestRelay.CurrentNetworkResourceRate + this.GetPotentialLinkCost(potentialBestRelay);
+				potentialRelayRate = potentialBestRelay.CurrentNetworkLinkCost +
+					this.GetPotentialLinkCost(potentialBestRelay);
+
+				log.AppendFormat(
+					"\n\tpotentialRelayRate = {0} ({1} + {2})",
+					potentialRelayRate,
+					potentialBestRelay.CurrentNetworkLinkCost,
+					this.GetPotentialLinkCost(potentialBestRelay)
+				);
 
 				#if BENCH
 				startLOSVesselTicks = performanceTimer.ElapsedTicks;
@@ -540,7 +534,7 @@
 						potentialBestRelay.CanTransmit()
 					)
 					{
-						log.Append("\n\t\t...vessel is cheapest and close enough and potentialBestRelay can transmit");
+						log.Append("\n\t\t...vessel is cheapest and in range and potentialBestRelay can transmit");
 						log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay);
 
 						this.bestOccludedRelay = potentialBestRelay;
@@ -575,9 +569,13 @@
 				if (potentialRelayRate > cheapestRelayRate)
 				{
 					
-					log.AppendFormat("\n\t{0}: Relay {1} discarded because it is more expensive than another the nearest relay.",
+					log.AppendFormat(
+							"\n\t{0}: Relay {1} discarded because it is more expensive than the cheapest relay." +
+							"\n\t\t({2}, {3} > {4})",
 						this.ToString(),
-						potentialBestRelay
+						potentialBestRelay,
+						this.nearestRelay == null ? "NULL" : this.nearestRelay.ToString(),
+						potentialRelayRate, cheapestRelayRate
 					);
 					continue;
 				}
@@ -647,7 +645,7 @@
 						log.AppendFormat("\n\t{0}: found new cheapest relay {1} ({2} EC/MiT)",
 							this.ToString(),
 							this.nearestRelay.ToString(),
-							Math.Sqrt(cheapestRelayRate)
+							cheapestRelayRate
 						);
 					}
 					else
@@ -678,7 +676,7 @@
 
 			log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
 			log.AppendFormat(
-				"\n{0}: nearestRelay={1} ({2})), bestOccludedRelay={3} ({4}), kerbinSqrDistance={5}m²)",
+					"\n{0}: nearestRelay={1} ({2})), bestOccludedRelay={3} ({4}), kerbinRelayRate={5} EC/MiT)",
 				this,
 				this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
 				cheapestRelayRate,
@@ -735,7 +733,7 @@
 						// 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 cheaper ({0} < {1}), so picking it.",
+							log.AppendFormat("\n\t\t\t\t...but the cheapest relay is cheaper ({0} < {1}), so picking it.",
 								cheapestRelayRate, cheapestOccludedRelayRate);
 							
 							this.targetRelay = this.nearestRelay;
@@ -859,7 +857,7 @@
 							// 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 cheaper ({0} < {1}), so picking it.",
+								log.AppendFormat("\n\t\t\t\t...but the cheapest relay is cheaper ({0} < {1}), so picking it.",
 									cheapestRelayRate, cheapestOccludedRelayRate);
 								
 								this.targetRelay = this.nearestRelay;
@@ -868,7 +866,7 @@
 							// Otherwise, target the best occluded relay.
 							else
 							{
-								log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
+								log.AppendFormat("\n\t\t\t\t...and cheaper than the cheapest relay ({0} >= {1}), so picking it.",
 									cheapestRelayRate, cheapestOccludedRelayRate);
 								
 								this.targetRelay = bestOccludedRelay;
@@ -1046,6 +1044,7 @@
 		/// as an <see cref="AntennaRange.IAntennaRelay"/></param>
 		public AntennaRelay(IAntennaRelay module)
 		{
+			this.KerbinDirect = true;
 			this.moduleRef = module;
 
 			#if BENCH

--- a/GameData/AntennaRange/AntennaRange.cfg
+++ b/GameData/AntennaRange/AntennaRange.cfg
@@ -48,6 +48,9 @@
 		simpleRange = 20500000
 		maxPowerFactor = 8
 		maxDataFactor = 4
+
+		basePacketSize = #$packetSize$
+		basePacketResourceCost = #$packetResourceCost$
 	}
 
 	MODULE
@@ -70,6 +73,9 @@
 		simpleRange = 18000000000
 		maxPowerFactor = 4
 		maxDataFactor = 8
+
+		basePacketSize = #$packetSize$
+		basePacketResourceCost = #$packetResourceCost$
 	}
 
 	MODULE
@@ -93,6 +99,9 @@
 		simpleRange = 56250000000
 		maxPowerFactor = 16
 		maxDataFactor = 2
+
+		basePacketSize = #$packetSize$
+		basePacketResourceCost = #$packetResourceCost$
 	}
 
 	MODULE
@@ -118,6 +127,9 @@
 		simpleRange = 25030376544
 		maxPowerFactor = 2.6180339887498948
 		maxDataFactor = 9
+
+		basePacketSize = #$packetSize$
+		basePacketResourceCost = #$packetResourceCost$
 	}
 
 	MODULE
@@ -151,6 +163,9 @@
 	packetSize = 1
 	packetResourceCost = 6.25
 
+	basePacketSize = #$packetSize$
+	baseResourceCost = #$packetResourceCost$
+
 	requiredResource = ElectricCharge
 }
 

--- a/IAntennaRelay.cs
+++ b/IAntennaRelay.cs
@@ -46,16 +46,28 @@
 		/// </summary>
 		IAntennaRelay targetRelay { get; }
 
-		float PacketSize { get; set; }
+		/// <summary>
+		/// Gets the current link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current link resource rate in EC/MiT.</value>
+		RelayDataCost CurrentLinkCost { get; set; }
 
-		float BasePacketSize { get; }
+		/// <summary>
+		/// Gets the base link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The base link resource rate in EC/MiT.</value>
+		RelayDataCost BaseLinkCost { get; }
 
-		float PacketResourceCost { get; set; }
-
-		float BasePacketResourceCost { get; }
-
+		/// <summary>
+		/// Gets the packet throttle.
+		/// </summary>
+		/// <value>The packet throttle in range [0..100].</value>
 		float PacketThrottle { get; }
 
+		/// <summary>
+		/// Gets the max data factor.
+		/// </summary>
+		/// <value>The max data factor.</value>
 		float MaxDataFactor { get; }
 
 		/// <summary>
@@ -68,7 +80,7 @@
 		/// Gets the current network resource rate in EC/MiT.
 		/// </summary>
 		/// <value>The current network resource rate in EC/MiT.</value>
-		double CurrentNetworkResourceRate { get; }
+		RelayDataCost CurrentNetworkLinkCost { get; }
 
 		/// <summary>
 		/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating

--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -52,9 +52,6 @@
 		: ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay, IModuleInfo
 	{
 		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;
@@ -62,8 +59,11 @@
 		// 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;
+		[KSPField(isPersistant = false)]
+		public float basePacketSize;
+
+		[KSPField(isPersistant = false)]
+		public float basePacketResourceCost;
 
 		/// <summary>
 		/// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as
@@ -186,42 +186,40 @@
 			}
 		}
 
-		public float PacketSize
-		{
-			get
-			{
-				return this.packetSize;
+		private RelayDataCost _currentLinkCost = new RelayDataCost();
+		/// <summary>
+		/// Gets the current link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current link resource rate in EC/MiT.</value>
+		public RelayDataCost CurrentLinkCost
+		{
+			get
+			{
+				_currentLinkCost.PacketResourceCost = this.packetResourceCost;
+				_currentLinkCost.PacketSize = this.packetSize;
+				return _currentLinkCost;
 			}
 			set
 			{
-				this.packetSize = value;
-			}
-		}
-
-		public float BasePacketSize
+				this.packetResourceCost = value.PacketResourceCost;
+				this.packetSize = value.PacketSize;
+			}
+		}
+
+		/// <summary>
+		/// Gets the base link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The base link resource rate in EC/MiT.</value>
+		public RelayDataCost BaseLinkCost
 		{
 			get;
 			private set;
 		}
 
-		public float PacketResourceCost
-		{
-			get
-			{
-				return this.packetResourceCost;
-			}
-			set
-			{
-				this.packetResourceCost = value;
-			}
-		}
-
-		public float BasePacketResourceCost
-		{
-			get;
-			private set;
-		}
-
+		/// <summary>
+		/// Gets the packet throttle.
+		/// </summary>
+		/// <value>The packet throttle in range [0..100].</value>
 		public float PacketThrottle
 		{
 			get
@@ -230,6 +228,10 @@
 			}
 		}
 
+		/// <summary>
+		/// Gets the max data factor.
+		/// </summary>
+		/// <value>The max data factor.</value>
 		public float MaxDataFactor
 		{
 			get
@@ -316,23 +318,6 @@
 				}
 
 				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;
 			}
 		}
 
@@ -424,12 +409,14 @@
 		{
 			get
 			{
-				if (this.relay == null)
-				{
-					return float.PositiveInfinity;
-				}
-
-				return this.relay.DataRate;
+				if (this.CanTransmit())
+				{
+					return this.packetSize;
+				}
+				else
+				{
+					return float.Epsilon;
+				}
 			}
 		}
 
@@ -441,25 +428,31 @@
 		{
 			get
 			{
+				if (this.CanTransmit())
+				{
+					return this.packetResourceCost;
+				}
+				else
+				{
+					return float.PositiveInfinity;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Gets the current network resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current network resource rate in EC/MiT.</value>
+		public RelayDataCost CurrentNetworkLinkCost
+		{
+			get
+			{
 				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;
+					return RelayDataCost.Infinity;
+				}
+
+				return this.relay.CurrentNetworkLinkCost;
 			}
 		}
 
@@ -489,16 +482,13 @@
 			this.packetThrottle = 100f;
 		}
 
+		#if DEBUG
 		/// <summary>
 		/// PartModule OnAwake override; runs at Unity Awake.
 		/// </summary>
 		public override void OnAwake()
 		{
 			base.OnAwake();
-
-			this.BasePacketSize = base.packetSize;
-			this.BasePacketResourceCost = base.packetResourceCost;
-			this.moduleInfoContent = new GUIContent();
 
 			this.LogDebug("{0} loaded:\n" +
 				"packetSize: {1}\n" +
@@ -508,12 +498,13 @@
 				"maxDataFactor: {5}\n",
 				this,
 				base.packetSize,
-				this.BasePacketResourceCost,
+				this.packetResourceCost,
 				this.nominalTransmitDistance,
 				this.maxPowerFactor,
 				this.maxDataFactor
 			);
 		}
+		#endif
 
 		/// <summary>
 		/// PartModule OnStart override; runs at Unity Start.
@@ -521,9 +512,10 @@
 		/// <param name="state">State.</param>
 		public override void OnStart (StartState state)
 		{
+			this.BaseLinkCost = new RelayDataCost(this.basePacketResourceCost, this.basePacketSize);
+			this.RecalculateMaxRange();
+
 			base.OnStart (state);
-
-			this.RecalculateMaxRange();
 
 			if (state >= StartState.PreLaunch)
 			{
@@ -551,6 +543,7 @@
 
 			base.OnLoad (node);
 
+			this.BaseLinkCost = new RelayDataCost(this.basePacketResourceCost, this.basePacketSize);
 			this.RecalculateMaxRange();
 		}
 
@@ -574,6 +567,9 @@
 		// HACK: Currently hacks around Squad's extraneous layout box, see KSPModders issue #5118
 		private void drawTooltipWidget(Rect rect)
 		{
+			/*
+			 * Removed all this because Squad doesn't even call it anymore.
+			 *
 			this.moduleInfoContent.text = this.GetInfo();
 
 			if (partTooltipSkin == null)
@@ -615,7 +611,7 @@
 			GUILayout.Space(height - orgHeight
 				- partTooltipBodyStyle.padding.bottom - partTooltipBodyStyle.padding.top
 				- 2f * (partTooltipBodyStyle.margin.bottom + partTooltipBodyStyle.margin.top)
-			);
+			);*/
 		}
 
 		/// <summary>
@@ -633,16 +629,12 @@
 		{
 			using (PooledStringBuilder sb = PooledStringBuilder.Get())
 			{
-				string text;
-
-				sb.Append(base.GetInfo());
-
 				if (ARConfiguration.UseAdditiveRanges)
 				{
-					sb.AppendFormat("Nominal Range to Kerbin: {0:S3}m\n",
+					sb.AppendFormat("<b>Nominal Range to Kerbin: </b>{0:S3}m\n",
 						Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange)
 					);
-					sb.AppendFormat("Maximum Range to Kerbin: {0:S3}m",
+					sb.AppendFormat("<b>Maximum Range to Kerbin: </b>{0:S3}m\n",
 						Math.Sqrt(
 							this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) *
 							ARConfiguration.KerbinRelayRange
@@ -651,13 +643,69 @@
 				}
 				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;
+					sb.AppendFormat("<b>Nominal Range: </b>{0:S3}m\n", this.nominalTransmitDistance);
+					sb.AppendFormat("<b>Maximum Range: </b>{0:S3}m\n", this.maxTransmitDistance);
+				}
+
+				sb.AppendLine();
+
+				sb.AppendFormat("<b>Nominal Packet Size: </b>{0:S2}iT\n", this.BaseLinkCost.PacketSize * 1000000f);
+				sb.AppendFormat(
+					"<b>Nominal Data Rate: </b>{0:S2}iT/sec\n",
+					this.BaseLinkCost.PacketSize / this.packetInterval * 1000000f
+				);
+
+				sb.AppendLine();
+
+				sb.AppendFormat("<b>Within Nominal Range...\n...Maximum Speedup:</b> {0:P0}\n", this.maxDataFactor);
+
+				if (ARConfiguration.FixedPowerCost)
+				{
+					sb.AppendLine();
+
+					sb.AppendFormat(
+						"<b>Outside Nominal Range...\n...Maximum Slowdown:</b> {0:P1}\n",
+						1f / this.maxPowerFactor
+					);
+
+					sb.AppendLine();
+
+					sb.AppendFormat(
+						"<b>Packet Cost:</b> {0:0.0#} {1}\n",
+						this.BaseLinkCost.PacketResourceCost,
+						this.requiredResource == "ElectricCharge" ? "EC" : this.requiredResource
+					);
+					sb.AppendFormat(
+						"<b>Power Drain:</b> {0:0.0#} {1}/s\n",
+						this.BaseLinkCost.PacketResourceCost / this.packetInterval,
+						this.requiredResource == "ElectricCharge" ? "EC" : this.requiredResource
+					);
+				}
+				else
+				{
+					sb.AppendLine();
+
+					sb.AppendFormat(
+						"<b>Nominal Packet Cost:</b> {0:0.0#} {1}\n",
+						this.BaseLinkCost.PacketResourceCost,
+						this.requiredResource == "ElectricCharge" ? "EC" : this.requiredResource
+					);
+					sb.AppendFormat(
+						"<b>Nominal Power Drain:</b> {0:0.0#} {1}/s\n",
+						this.BaseLinkCost.PacketResourceCost / this.packetInterval,
+						this.requiredResource == "ElectricCharge" ? "EC" : this.requiredResource
+					);
+
+					sb.AppendLine();
+
+					sb.AppendFormat(
+						"<b>Outside Nominal Range...\n...Maximum Power Drain:</b> {0:0.0#} {1}/s\n",
+						this.BaseLinkCost.PacketResourceCost / this.packetInterval * this.maxPowerFactor,
+						this.requiredResource == "ElectricCharge" ? "EC" : this.requiredResource
+					);
+				}
+
+				return sb.ToString();
 			}
 		}
 
@@ -691,12 +739,22 @@
 			return this.relay.CanTransmit();
 		}
 
+		/// <summary>
+		/// Recalculates the transmission rates.
+		/// </summary>
 		public void RecalculateTransmissionRates()
 		{
 			if (this.relay != null)
 			{
 				this.relay.RecalculateTransmissionRates();
-			}
+				this.LogDebug("Recalculated transmission rates in MLDT, cost is {0}", this.CurrentLinkCost);
+			}
+			#if DEBUG
+			else
+			{
+				this.LogDebug("Skipping recalculation; relay is null.");
+			}
+			#endif
 		}
 
 		/// <summary>
@@ -902,6 +960,9 @@
 			}
 		}
 
+		/// <summary>
+		/// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
+		/// </summary>
 		public void RecalculateMaxRange()
 		{
 			this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
@@ -925,6 +986,8 @@
 				if (this.part != null && this.part.partInfo != null)
 				{
 					sb.Append(this.part.partInfo.title);
+					sb.Append('#');
+					sb.Append(this.part.flightID);
 				}
 				else
 				{

--- 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.10.3.*")]
+[assembly: AssemblyVersion("1.11.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
@@ -72,7 +72,40 @@
 			}
 		}
 
-		public float PacketSize
+		/// <summary>
+		/// Gets the base link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The base link resource rate in EC/MiT.</value>
+		public RelayDataCost BaseLinkCost
+		{
+			get;
+			private set;
+		}
+
+		/// <summary>
+		/// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
+		/// to be scored in terms of joules/byte
+		/// </summary>
+		public double DataResourceCost
+		{
+			get
+			{
+				if (this.CanTransmit())
+				{
+					return this.moduleRef.DataResourceCost;
+				}
+				else
+				{
+					return float.PositiveInfinity;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Gets the packet throttle.
+		/// </summary>
+		/// <value>The packet throttle in range [0..100].</value>
+		public float PacketThrottle
 		{
 			get
 			{
@@ -81,80 +114,14 @@
 					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;
 			}
 		}
 
+		/// <summary>
+		/// Gets the max data factor.
+		/// </summary>
+		/// <value>The max data factor.</value>
 		public float MaxDataFactor
 		{
 			get
@@ -228,6 +195,9 @@
 			return base.CanTransmit();
 		}
 
+		/// <summary>
+		/// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
+		/// </summary>
 		public void RecalculateMaxRange()
 		{
 			if (this.moduleRef != null)
@@ -248,6 +218,8 @@
 
 				if (this.protoPart != null && this.protoPart.pVesselRef != null)
 				{
+					sb.Append('#');
+					sb.Append(this.protoPart.flightID);
 					sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
 				}
 

file:b/RelayDataCost.cs (new)
--- /dev/null
+++ b/RelayDataCost.cs
@@ -1,1 +1,259 @@
-
+// AntennaRange
+//
+// RelayLinkCost.cs
+//
+// Copyright © 2016, toadicus
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation and/or other
+//    materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be used
+//    to endorse or promote products derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+using System;
+using UnityEngine;
+
+namespace AntennaRange
+{
+	/// <summary>
+	/// A struct representing the cost of sending data through a relay.
+	/// </summary>
+	public struct RelayDataCost : IComparable, IComparable<RelayDataCost>
+	{
+		/// <summary>
+		/// A RelayDataCost object representing infinitely high cost.
+		/// </summary>
+		public static readonly RelayDataCost Infinity = new RelayDataCost(float.PositiveInfinity, 0f);
+
+		/// <param name="one">Left</param>
+		/// <param name="two">Right</param>
+		public static RelayDataCost operator+ (RelayDataCost one, RelayDataCost two)
+		{
+			RelayDataCost gcd, lcd;
+
+			if (one.PacketSize > two.PacketSize) {
+				gcd = one;
+				lcd = two;
+			}
+			else
+			{
+				gcd = two;
+				lcd = one;
+			}
+
+			if (lcd.PacketSize != 0f)
+			{
+				float mul = gcd.PacketSize / lcd.PacketSize;
+
+				lcd.PacketSize *= mul;
+				lcd.PacketResourceCost *= mul;
+			}
+
+			return new RelayDataCost(gcd.PacketResourceCost + lcd.PacketResourceCost, gcd.PacketSize);
+		}
+
+		/// <param name="only">RelayDataCost to be negated</param>
+		public static RelayDataCost operator- (RelayDataCost only)
+		{
+			return new RelayDataCost(-only.PacketResourceCost, only.PacketSize);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static RelayDataCost operator- (RelayDataCost left, RelayDataCost right)
+		{
+			return left + -right;
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator> (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) > 0);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator>= (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) >= 0);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator== (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) == 0);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator<= (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) <= 0);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator< (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) < 0);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator!= (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) != 0);
+		}
+
+		/// <summary>
+		/// The resource cost of a packet, in EC/packet
+		/// </summary>
+		public float PacketResourceCost;
+
+		/// <summary>
+		/// The data capacity of a packet, MiT/packet
+		/// </summary>
+		public float PacketSize;
+
+		/// <summary>
+		/// Gets the resource cost per unit data, in EC/MiT
+		/// </summary>
+		/// <value>The resource cost per unit data, in EC/MiT</value>
+		public double ResourceCostPerData
+		{
+			get
+			{
+				if (this.PacketSize == 0f || float.IsInfinity(this.PacketResourceCost))
+				{
+					return double.PositiveInfinity;
+				}
+
+				return (double)this.PacketResourceCost / (double)this.PacketSize;
+			}
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="AntennaRange.RelayDataCost"/> struct.
+		/// </summary>
+		/// <param name="cost">resource cost of a packet, in EC/packet</param>
+		/// <param name="size">data capacity of a packet, MiT/packet</param>
+		public RelayDataCost(float cost, float size)
+		{
+			this.PacketResourceCost = cost;
+			this.PacketSize = size;
+		}
+
+		/// <summary>
+		/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.RelayDataCost"/>.
+		/// </summary>
+		/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.RelayDataCost"/>.</returns>
+		public override string ToString()
+		{
+			return string.Format("{0} EC/MiT", this.ResourceCostPerData);
+		}
+
+		/// <summary>
+		/// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="AntennaRange.RelayDataCost"/>.
+		/// </summary>
+		/// <param name="obj">The <see cref="System.Object"/> to compare with the current <see cref="AntennaRange.RelayDataCost"/>.</param>
+		/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to the current
+		/// <see cref="AntennaRange.RelayDataCost"/>; otherwise, <c>false</c>.</returns>
+		public override bool Equals(object obj)
+		{
+			if (obj is RelayDataCost)
+			{
+				return ((RelayDataCost)obj == this);
+			}
+			else
+			{
+				return false;
+			}
+		}
+
+		/// <summary>
+		/// Serves as a hash function for a <see cref="AntennaRange.RelayDataCost"/> object.
+		/// </summary>
+		/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table.</returns>
+		public override int GetHashCode()
+		{
+			int hash = 137;
+
+			hash = (hash * 61) + this.PacketResourceCost.GetHashCode();
+			hash = (hash * 61) + this.PacketSize.GetHashCode();
+
+			return hash;
+		}
+
+		/// <summary>
+		/// Compares this RelayDataCost to another object.  Throws NotImplementedException for objects
+		/// that are not RelayDataCost objects
+		/// </summary>
+		/// <returns>-1 if this is less than o, 0 if this equals o, 1 if this is greater than o</returns>
+		/// <param name="o">Another object</param>
+		public int CompareTo(object o)
+		{
+			if (o is RelayDataCost)
+			{
+				return this.CompareTo((RelayDataCost)o);
+			}
+
+			throw new NotImplementedException(
+				string.Format(
+					"Cannot compare {0} to foreign type {1}",
+					this.GetType().Name,
+					o.GetType().Name
+				)
+			);
+		}
+
+		/// <summary>
+		/// Compares this RelayDataCost to another object.  Throws NotImplementedException for objects
+		/// that are not RelayDataCost objects
+		/// </summary>
+		/// <returns>-1 if this is less than o, 0 if this equals o, 1 if this is greater than o</returns>
+		/// <param name="o">Another RelayDataCost</param>
+		public int CompareTo(RelayDataCost o)
+		{
+			int val;
+
+			if (this.ResourceCostPerData > o.ResourceCostPerData)
+			{
+				val = 1;
+			}
+			else if (this.ResourceCostPerData < o.ResourceCostPerData)
+			{
+				val = -1;
+			}
+			else
+			{
+				val = 0;
+			}
+
+			#if DEBUG
+			Debug.LogErrorFormat("RelayLinkCost comparing {0} to {1}, returning {2}", this, o, val);
+			#endif
+
+			return val;
+		}
+	}
+}
+
+

--- a/RelayDatabase.cs
+++ b/RelayDatabase.cs
@@ -419,6 +419,8 @@
 			GameEvents.onVesselWasModified.Add(this.onVesselEvent);
 			GameEvents.onVesselChange.Add(this.onVesselEvent);
 			GameEvents.onVesselDestroy.Add(this.onVesselEvent);
+			GameEvents.onVesselGoOnRails.Add(this.onVesselEvent);
+			GameEvents.onVesselGoOffRails.Add(this.onVesselEvent);
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
 			GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
 			GameEvents.onPartUndock.Add(this.onPartEvent);
@@ -431,6 +433,8 @@
 			GameEvents.onVesselWasModified.Remove(this.onVesselEvent);
 			GameEvents.onVesselChange.Remove(this.onVesselEvent);
 			GameEvents.onVesselDestroy.Remove(this.onVesselEvent);
+			GameEvents.onVesselGoOnRails.Remove(this.onVesselEvent);
+			GameEvents.onVesselGoOffRails.Add(this.onVesselEvent);
 			GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
 			GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
 			GameEvents.onPartUndock.Remove(this.onPartEvent);

--- a/RelayExtensions.cs
+++ b/RelayExtensions.cs
@@ -143,6 +143,12 @@
 			return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
 		}
 
+		/// <summary>
+		/// Returns the square of the maximum link range between two relays.
+		/// </summary>
+		/// <returns>The maximum link range between two relays.</returns>
+		/// <param name="relayOne">Relay one.</param>
+		/// <param name="relayTwo">Relay two.</param>
 		public static double MaxLinkSqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
 		{
 			if (ARConfiguration.UseAdditiveRanges)
@@ -155,6 +161,12 @@
 			}
 		}
 
+		/// <summary>
+		/// Returns the square of the maximum link range between a relay and Kerbin.
+		/// </summary>
+		/// <returns>The maximum link range between a relay and Kerbin.</returns>
+		/// <param name="relayOne">Relay one.</param>
+		/// <param name="body">A CelestialBody (must be Kerbin).</param>
 		public static double MaxLinkSqrDistanceTo(this AntennaRelay relayOne, CelestialBody body)
 		{
 			if (body != AntennaRelay.Kerbin)
@@ -172,6 +184,12 @@
 			}
 		}
 
+		/// <summary>
+		/// Determines if relayOne is in range of the specified relayTwo.
+		/// </summary>
+		/// <returns><c>true</c> if relayOne is in range of the specifie relayTwo; otherwise, <c>false</c>.</returns>
+		/// <param name="relayOne">Relay one.</param>
+		/// <param name="relayTwo">Relay two.</param>
 		public static bool IsInRangeOf(this AntennaRelay relayOne, IAntennaRelay relayTwo)
 		{
 			if (relayOne == null || relayTwo == null)
@@ -182,7 +200,12 @@
 			return relayOne.SqrDistanceTo(relayTwo) <= relayOne.MaxLinkSqrDistanceTo(relayTwo);
 		}
 
-
+		/// <summary>
+		/// Determines if relayOne is in range of the specified body.
+		/// </summary>
+		/// <returns><c>true</c> if relayOne is in range of the specified body; otherwise, <c>false</c>.</returns>
+		/// <param name="relayOne">Relay one.</param>
+		/// <param name="body">Body.</param>
 		public static bool IsInRangeOf(this AntennaRelay relayOne, CelestialBody body)
 		{
 			if (relayOne == null || body == null)
@@ -320,7 +343,13 @@
 		[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);
+			using (var sb = ToadicusTools.Text.PooledStringBuilder.Get())
+			{
+				sb.AppendFormat("[{0}] ", relay == null ? "NULL" : relay.ToString());
+				sb.AppendFormat(format, args);
+
+				ToadicusTools.Logging.PostDebugMessage(sb.ToString());
+			}
 		}
 
 		/// <summary>
@@ -329,7 +358,13 @@
 		[System.Diagnostics.Conditional("DEBUG")]
 		public static void LogDebug(this AntennaRelay relay, string msg)
 		{
-			ToadicusTools.Logging.PostDebugMessage("[{0}] {1}", relay.ToString(), msg);
+			using (var sb = ToadicusTools.Text.PooledStringBuilder.Get())
+			{
+				sb.AppendFormat("[{0}] ", relay == null ? "NULL" : relay.ToString());
+				sb.Append(msg);
+
+				ToadicusTools.Logging.PostDebugMessage(sb.ToString());
+			}
 		}
 	}