ARFlightController: Now re-looping through useful relays to do FindNearestRelay, so all vessels can be available.
ARFlightController: Now re-looping through useful relays to do FindNearestRelay, so all vessels can be available.

--- a/ARConfiguration.cs
+++ b/ARConfiguration.cs
@@ -145,14 +145,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);
@@ -171,6 +170,9 @@
 
 			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();
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
@@ -212,7 +214,7 @@
 
 			this.runOnce = true;
 
-			Tools.PostDebugMessage(this, "Awake.");
+			this.LogDebug("Awake.");
 		}
 
 		public void Update()
@@ -235,7 +237,7 @@
 			{
 				if (this.toolbarButton == null)
 				{
-					Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button.");
+					this.LogDebug("Toolbar available; initializing toolbar button.");
 
 					this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
 					this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
@@ -250,7 +252,7 @@
 			}
 			else if (this.appLauncherButton == null && ApplicationLauncher.Ready)
 			{
-				Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button.");
+				this.LogDebug("Toolbar available; initializing AppLauncher button.");
 
 				this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
 					this.toggleConfigWindow,
@@ -436,8 +438,6 @@
 			{
 				tsLevel = ScenarioUpgradeableFacilities.protoUpgradeables["SpaceCenter/TrackingStation"]
 					.facilityRefs[0].FacilityLevel;
-			
-
 			}
 			else
 			{

--- a/ARFlightController.cs
+++ b/ARFlightController.cs
@@ -39,6 +39,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;
@@ -153,6 +158,9 @@
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
 			GameEvents.onVesselChange.Add(this.onVesselChange);
+
+			usefulRelays = new List<IAntennaRelay>();
+			UsefulRelays = usefulRelays.AsReadOnly();
 		}
 
 		private void FixedUpdate()
@@ -241,12 +249,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;
 				IList<IAntennaRelay> activeVesselRelays;
 
+				usefulRelays.Clear();
+
 				for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
 				{
 					vessel = FlightGlobals.Vessels[vIdx];
@@ -256,33 +269,57 @@
 						continue;
 					}
 
-					log.AppendFormat("Fetching best relay for vessel {0}", vessel);
+					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);
+					}
+				}
+
+				bestActiveRelay = RelayDatabase.Instance.GetBestVesselRelay(FlightGlobals.ActiveVessel);
 				activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel];
 				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 == bestActiveRelay)
+					{
+						continue;
+					}
+
+					log.AppendFormat("\nFinding nearest relay for active vessel relay {0}", relay);
+
 					relay.FindNearestRelay();
 				}
 
+				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];
+
+					log.AppendFormat("\n\tDoing target search for useful relay {0}", relay);
+
+					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 +342,7 @@
 				}
 			}
 
-			log.Print();
+			log.Print(false);
 		}
 
 		private void OnDestroy()

--- a/ARMapRenderer.cs
+++ b/ARMapRenderer.cs
@@ -75,9 +75,7 @@
 
 					lr = obj.AddComponent<LineRenderer>();
 
-					// lr.SetColors(Color.green, Color.green);
 					lr.material = MapView.OrbitLinesMaterial;
-					// lr.SetVertexCount(2);
 
 					this.vesselLineRenderers[idx] = lr;
 
@@ -132,62 +130,30 @@
 					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)

--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -25,7 +25,7 @@
 // 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.
-#define BENCH
+
 using System;
 using System.Collections.Generic;
 using ToadicusTools;
@@ -146,11 +146,11 @@
 			{
 				if (this.KerbinDirect || this.targetRelay == null)
 				{
-					return this.sqrDistanceTo(Kerbin);
+					return this.SqrDistanceTo(Kerbin);
 				}
 				else
 				{
-					return this.sqrDistanceTo(this.targetRelay);
+					return this.SqrDistanceTo(this.targetRelay);
 				}
 			}
 		}
@@ -264,68 +264,38 @@
 			this.KerbinDirect = true;
 
 			/*
-			 * 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;
 
 			#if BENCH
 			startVesselLoopTicks = performanceTimer.ElapsedTicks;
 			#endif
-
-			for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
-			{
-				log.AppendFormat("\nFetching vessel at index {0}", vIdx);
-				potentialVessel = FlightGlobals.Vessels[vIdx];
-				
-				if (potentialVessel == null)
-				{
-					Tools.PostErrorMessage("{0}: Skipping vessel at index {1} because it is null.", this, vIdx);
-					log.AppendFormat("\n\tSkipping vessel at index {0} because it is null.", vIdx);
-					log.Print();
-					return;
-				}
-				#if DEBUG
-				else
-				{
-					log.AppendFormat("\n\tGot vessel {0}", potentialVessel);
-				}
-				#endif
-
-				// Skip vessels of the wrong type.
-				log.Append("\n\tchecking vessel type");
-				switch (potentialVessel.vesselType)
-				{
-					case VesselType.Debris:
-					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.");
+			
+			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;
 				}
 
-				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");
+				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;
 				}
 
@@ -335,7 +305,7 @@
 
 				// Find the distance from here to the vessel...
 				log.Append("\n\tgetting distance to potential vessel");
-				potentialSqrDistance = this.sqrDistanceTo(potentialVessel);
+				potentialSqrDistance = this.SqrDistanceTo(potentialBestRelay);
 				log.Append("\n\tgetting best vessel relay");
 
 				log.Append("\n\tgetting max link distance to potential relay");
@@ -361,7 +331,7 @@
 				// 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
@@ -376,8 +346,8 @@
 
 					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);
@@ -424,9 +394,9 @@
 				if (potentialSqrQuotient > nearestRelaySqrQuotient)
 				{
 					
-					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 farther than another the nearest relay.",
 						this.ToString(),
-						potentialVessel.vesselName
+						potentialBestRelay
 					);
 					continue;
 				}
@@ -466,11 +436,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(
@@ -542,7 +511,7 @@
 
 			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,
@@ -550,7 +519,7 @@
 				bestOccludedSqrQuotient,
 				kerbinSqrDistance
 			);
-
+			
 			#if BENCH
 			startKerbinLOSTicks = this.performanceTimer.ElapsedTicks;
 			#endif
@@ -885,7 +854,7 @@
 
 				// sb.AppendFormat(Tools.SIFormatter, "", start)
 
-				Tools.PostWarningMessage(sb.ToString());
+				this.LogWarning(sb.ToString());
 
 				Tools.PutStringBuilder(sb);
 			}
@@ -918,7 +887,7 @@
 			AntennaRelay.relayCount++;
 			#endif
 
-			Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
+			this.LogDebug("{0}: constructed {1}", this.GetType().Name, this.ToString());
 		}
 	}
 }

--- a/GameData/AntennaRange/AntennaRange.cfg
+++ b/GameData/AntennaRange/AntennaRange.cfg
@@ -64,7 +64,7 @@
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
-		nominalRange = 3500000000
+		nominalRange = 3150000000
 		simpleRange = 18000000000
 		maxPowerFactor = 4
 		maxDataFactor = 8
@@ -87,7 +87,7 @@
 	{
 		@name = ModuleLimitedDataTransmitter
 		@packetResourceCost /= 1.414213
-		nominalRange = 10000000000
+		nominalRange = 9250000000
 		simpleRange = 56250000000
 		maxPowerFactor = 16
 		maxDataFactor = 2
@@ -108,7 +108,7 @@
 {
 	range = 800000
 	range = 200000000000
-	range = 2250000000000
+	range = 2000000000000
 }
 
 EVA_MODULE
@@ -116,6 +116,7 @@
 	name = ModuleLimitedDataTransmitter
 
 	nominalRange = 1389
+	simpleRange = 5000
 	maxPowerFactor = 1
 	maxDataFactor = 1
 

--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -46,7 +46,8 @@
 	/// 
 	/// <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;
@@ -59,6 +60,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
@@ -173,6 +177,7 @@
 				else
 				{
 					this.LogError("Vessel and/or part reference are null, returning null vessel.");
+					this.LogError(new System.Diagnostics.StackTrace().ToString());
 					return null;
 				}
 			}
@@ -416,6 +421,7 @@
 
 			this._basepacketSize = base.packetSize;
 			this._basepacketResourceCost = base.packetResourceCost;
+			this.moduleInfoContent = new GUIContent();
 
 			Tools.PostDebugMessage(string.Format(
 				"{0} loaded:\n" +
@@ -441,10 +447,10 @@
 		{
 			base.OnStart (state);
 
+			this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
+
 			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;
@@ -473,6 +479,54 @@
 		}
 
 		/// <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();
+
+			GUIStyle style0 = PartListTooltips.fetch.tooltipSkin.customStyles[0];
+			GUIStyle style1 = PartListTooltips.fetch.tooltipSkin.customStyles[1];
+
+			float width = rect.width;
+			float orgHeight = rect.height;
+			float height = style0.CalcHeight(this.moduleInfoContent, width);
+
+			rect.height = height;
+
+			GUI.Box(rect, this.moduleInfoContent, style0);
+			GUI.Label(rect, this.GetModuleTitle(), style1);
+
+			GUILayout.Space(height - orgHeight
+				- style0.padding.bottom - style0.padding.top
+				- 2f * (style0.margin.bottom + style0.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>
 		/// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
 		/// </summary>
 		public override string GetInfo()
@@ -481,8 +535,24 @@
 			string text;
 
 			sb.Append(base.GetInfo());
-			sb.AppendFormat(Tools.SIFormatter, "Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
-			sb.AppendFormat(Tools.SIFormatter, "Maximum Range: {0:S3}m\n", this.maxTransmitDistance);
+
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				sb.AppendFormat(Tools.SIFormatter, "Nominal Range to Kerbin: {0:S3}m\n",
+					Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange)
+				);
+				sb.AppendFormat(Tools.SIFormatter, "Maximum Range to Kerbin: {0:S3}m",
+					Math.Sqrt(
+						this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) *
+						ARConfiguration.KerbinRelayRange
+					)
+				);
+			}
+			else
+			{
+				sb.AppendFormat(Tools.SIFormatter, "Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
+				sb.AppendFormat(Tools.SIFormatter, "Maximum Range: {0:S3}m", this.maxTransmitDistance);
+			}
 
 			text = sb.ToString();
 

--- 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.*")]
 // 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)]

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/RelayExtensions.cs
+++ b/RelayExtensions.cs
@@ -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,7 +126,7 @@
 		/// </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)
+		public static double SqrDistanceTo(this AntennaRelay relay, CelestialBody body)
 		{
 			double dist = (relay.vessel.GetWorldPos3D() - body.position).magnitude - body.Radius;
 
@@ -138,7 +138,7 @@
 		/// </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);
 		}
@@ -214,6 +214,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)
+		{
+			Tools.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)
+		{
+			Tools.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)
+		{
+			Tools.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)
+		{
+			Tools.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)
+		{
+			Tools.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)
+		{
+			Tools.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)
+		{
+			Tools.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)
+		{
+			Tools.PostDebugMessage("[{0}] {1}", relay.ToString(), msg);
 		}
 	}