AntennaRelay.cs: Changed reference tracking to an IAntennaRelay module, now fetching vessel thence, to avoid stale vessel references. Removed the IAntennaRelay IComparer.
AntennaRelay.cs: Changed reference tracking to an IAntennaRelay module, now fetching vessel thence, to avoid stale vessel references. Removed the IAntennaRelay IComparer.

file:a/ARTools.cs -> file:b/ARTools.cs
--- a/ARTools.cs
+++ b/ARTools.cs
@@ -124,6 +124,34 @@
 				return "0";
 			}
 		}
+
+		public static T Min<T>(params T[] values) where T : IComparable<T>
+		{
+			if (values.Length < 2)
+			{
+				throw new ArgumentException("Min must be called with at least two arguments.");
+			}
+
+			IComparable<T> minValue = values[0];
+
+			for (long i = 1; i < values.LongLength; i++)
+			{
+				IComparable<T> value = values[i];
+
+				if (value.CompareTo((T)minValue) < 0)
+				{
+					minValue = value;
+				}
+			}
+
+			return (T)minValue;
+		}
+
+		public static void Restart(this System.Diagnostics.Stopwatch stopwatch)
+		{
+			stopwatch.Reset();
+			stopwatch.Start();
+		}
 	}
 }
 

--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -26,6 +26,9 @@
 		// We don't have a Bard, so we'll hide Kerbin here.
 		protected CelestialBody Kerbin;
 
+		protected IAntennaRelay _nearestRelayCache;
+		protected IAntennaRelay moduleRef;
+
 		protected System.Diagnostics.Stopwatch searchTimer;
 		protected long millisecondsBetweenSearches;
 
@@ -35,66 +38,81 @@
 		/// <value>The parent Vessel.</value>
 		public Vessel vessel
 		{
+			get
+			{
+				return this.moduleRef.vessel;
+			}
+		}
+
+		/// <summary>
+		/// Gets or sets the nearest relay.
+		/// </summary>
+		/// <value>The nearest relay</value>
+		public IAntennaRelay nearestRelay
+		{
+			get
+			{
+				if (this.searchTimer.IsRunning &&
+					this.searchTimer.ElapsedMilliseconds > this.millisecondsBetweenSearches)
+				{
+					this._nearestRelayCache = this.FindNearestRelay();
+					this.searchTimer.Restart();
+				}
+
+				return this._nearestRelayCache;
+			}
+			protected set
+			{
+				this._nearestRelayCache = value;
+			}
+		}
+
+		/// <summary>
+		/// Gets the transmit distance.
+		/// </summary>
+		/// <value>The transmit distance.</value>
+		public double transmitDistance
+		{
+			get
+			{
+				this.nearestRelay = this.FindNearestRelay();
+
+				// If there is no available relay nearby...
+				if (this.nearestRelay == null)
+				{
+					// .. return the distance to Kerbin
+					return this.DistanceTo(this.Kerbin);
+				}
+				else
+				{
+					/// ...otherwise, return the distance to the nearest available relay.
+					return this.DistanceTo(nearestRelay);
+				}
+			}
+		}
+
+		/// <summary>
+		/// The maximum distance at which this relay can operate.
+		/// </summary>
+		/// <value>The max transmit distance.</value>
+		public virtual float maxTransmitDistance
+		{
+			get;
+			set;
+		}
+
+		/// <summary>
+		/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
+		/// the current relay attempt.
+		/// </summary>
+		/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
+		public virtual bool relayChecked
+		{
 			get;
 			protected set;
 		}
 
 		/// <summary>
-		/// Gets or sets the nearest relay.
-		/// </summary>
-		/// <value>The nearest relay</value>
-		public IAntennaRelay nearestRelay
-		{
-			get;
-			protected set;
-		}
-
-		/// <summary>
-		/// Gets the transmit distance.
-		/// </summary>
-		/// <value>The transmit distance.</value>
-		public double transmitDistance
-		{
-			get
-			{
-				this.nearestRelay = this.FindNearestRelay();
-
-				// If there is no available relay nearby...
-				if (nearestRelay == null)
-				{
-					// .. return the distance to Kerbin
-					return this.DistanceTo(this.Kerbin);
-				}
-				else
-				{
-					/// ...otherwise, return the distance to the nearest available relay.
-					return this.DistanceTo(nearestRelay);
-				}
-			}
-		}
-
-		/// <summary>
-		/// The maximum distance at which this relay can operate.
-		/// </summary>
-		/// <value>The max transmit distance.</value>
-		public virtual float maxTransmitDistance
-		{
-			get;
-			set;
-		}
-
-		/// <summary>
-		/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
-		/// the current relay attempt.
-		/// </summary>
-		/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
-		public virtual bool relayChecked
-		{
-			get;
-			protected set;
-		}
-
-		/// <summary>
 		/// Determines whether this instance can transmit.
 		/// </summary>
 		/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
@@ -129,80 +147,88 @@
 
 			this.searchTimer.Start();
 
-			// Set this relay as checked, so that we don't check it again.
-			this.relayChecked = true;
-
-			// Get a list of vessels within transmission range.
-			List<Vessel> nearbyVessels = FlightGlobals.Vessels
-				.Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.maxTransmitDistance)
-					.ToList();
-
-			nearbyVessels.RemoveAll(v => v.vesselType == VesselType.Debris);
-			nearbyVessels.RemoveAll(v => v.vesselType == VesselType.Flag);
-
 			Tools.PostDebugMessage(string.Format(
-				"{0}: Non-debris, non-flag vessels in range: {1}",
+				"{0}: finding nearest relay for {1} ({2})",
 				this.GetType().Name,
-				nearbyVessels.Count
-				));
-
-			// Remove this vessel.
-			nearbyVessels.RemoveAll(v => v.id == vessel.id);
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Vessels in range excluding self: {1}",
-				this.GetType().Name,
-				nearbyVessels.Count
-				));
-
-			// Get a flattened list of all IAntennaRelay modules and protomodules in transmission range.
-			List<IAntennaRelay> nearbyRelays = nearbyVessels.SelectMany(v => v.GetAntennaRelays()).ToList();
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Found {1} nearby relays.",
-				this.GetType().Name,
-				nearbyRelays.Count
-				));
-
-			// Remove all relays already checked this time.
-			nearbyRelays.RemoveAll(r => r.relayChecked);
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Found {1} nearby relays not already checked.",
-				this.GetType().Name,
-				nearbyRelays.Count
-				));
-
-			// Remove all relays that cannot transmit.
-			// This call to r.CanTransmit() starts a depth-first recursive search for relays with a path back to Kerbin.
-			nearbyRelays.RemoveAll(r => !r.CanTransmit());
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Found {1} nearby relays not already checked that can transmit.",
-				this.GetType().Name,
-				nearbyRelays.Count
-				));
-
-			// Sort the available relays by distance.
-			nearbyRelays.Sort(new RelayComparer(this.vessel));
-
-			// Get the nearest available relay, or null if there are no available relays nearby.
-			IAntennaRelay _nearestRelay = nearbyRelays.FirstOrDefault();
-
-			// If we have a nearby relay...
-			if (_nearestRelay != null)
-			{
-				// ...but that relay is farther than Kerbin...
-				if (this.DistanceTo(_nearestRelay) > this.DistanceTo(Kerbin))
-				{
-					// ...just use Kerbin.
-					_nearestRelay = null;
+				this,
+				this.vessel.id
+			));
+
+			// Set this vessel as checked, so that we don't check it again.
+			RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true;
+
+			double nearestDistance = double.PositiveInfinity;
+			IAntennaRelay _nearestRelay = null;
+
+			/*
+			 * 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.
+			 * */
+			foreach (Vessel potentialVessel in FlightGlobals.Vessels)
+			{
+				// Skip vessels that have already been checked for a nearest relay this pass.
+				try
+				{
+					if (RelayDatabase.Instance.CheckedVesselsTable[potentialVessel.id])
+					{
+						continue;
+					}
+				}
+				catch (KeyNotFoundException) { /* If the key doesn't exist, don't skip it. */}
+
+				// Skip vessels of the wrong type.
+				switch (potentialVessel.vesselType)
+				{
+					case VesselType.Debris:
+					case VesselType.Flag:
+					case VesselType.EVA:
+					case VesselType.SpaceObject:
+					case VesselType.Unknown:
+						continue;
+					default:
+						break;
+				}
+
+				// Skip vessels with the wrong ID
+				if (potentialVessel.id == vessel.id)
+				{
+					continue;
+				}
+
+				// Find the distance from here to the vessel...
+				double potentialDistance = (potentialVessel.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude;
+
+				/*
+				 * ...so that we can skip the vessel if it is further away than Kerbin, our transmit distance, or a
+				 * vessel we've already checked.
+				 * */
+				if (potentialDistance > Tools.Min(this.maxTransmitDistance, nearestDistance, vessel.DistanceTo(Kerbin)))
+				{
+					continue;
+				}
+
+				nearestDistance = potentialDistance;
+
+				foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays())
+				{
+					if (potentialRelay.CanTransmit())
+					{
+						_nearestRelay = potentialRelay;
+						Tools.PostDebugMessage(string.Format("{0}: found new best relay {1} ({2})",
+							this.GetType().Name,
+							_nearestRelay.ToString(),
+							_nearestRelay.vessel.id
+						));
+						break;
+					}
 				}
 			}
 
 			// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
 			// used next time.
-			this.relayChecked = false;
+			RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id);
 
 			// Return the nearest available relay, or null if there are no available relays nearby.
 			return _nearestRelay;
@@ -212,9 +238,9 @@
 		/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class.
 		/// </summary>
 		/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param>
-		public AntennaRelay(Vessel v)
-		{
-			this.vessel = v;
+		public AntennaRelay(IAntennaRelay module)
+		{
+			this.moduleRef = module;
 
 			this.searchTimer = new System.Diagnostics.Stopwatch();
 			this.millisecondsBetweenSearches = 5000;
@@ -223,46 +249,6 @@
 			// we hope it is safe enough.
 			this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin");
 		}
-
-		/*
-		 * Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance.
-		 * */
-		internal class RelayComparer : IComparer<IAntennaRelay>
-		{
-			/// <summary>
-			/// The reference Vessel (usually the active vessel).
-			/// </summary>
-			protected Vessel referenceVessel;
-
-			// We don't want no stinking public parameterless constructors.
-			private RelayComparer() {}
-
-			/// <summary>
-			/// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay+RelayComparer"/> class for use
-			/// in sorting relays by distance.
-			/// </summary>
-			/// <param name="reference">The reference Vessel</param>
-			public RelayComparer(Vessel reference)
-			{
-				this.referenceVessel = reference;
-			}
-
-			/// <summary>
-			/// Compare the <see cref="IAntennaRelay"/>s "one" and "two".
-			/// </summary>
-			/// <param name="one">The first IAntennaRelay in the comparison</param>
-			/// <param name="two">The second IAntennaRelay in the comparison</param>
-			public int Compare(IAntennaRelay one, IAntennaRelay two)
-			{
-				double distanceOne;
-				double distanceTwo;
-
-				distanceOne = one.vessel.DistanceTo(referenceVessel);
-				distanceTwo = two.vessel.DistanceTo(referenceVessel);
-
-				return distanceOne.CompareTo(distanceTwo);
-			}
-		}
 	}
 }
 

file:b/EventSniffer.cs (new)
--- /dev/null
+++ b/EventSniffer.cs
@@ -1,1 +1,207 @@
-
+// AntennaRange © 2014 toadicus
+//
+// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
+// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
+
+#if DEBUG
+using KSP;
+using System;
+using System.Text;
+using UnityEngine;
+
+namespace AntennaRange
+{
+	[KSPAddon(KSPAddon.Startup.Flight, false)]
+	public class EventSniffer : MonoBehaviour
+	{
+		public void Awake()
+		{
+			GameEvents.onSameVesselDock.Add(this.onSameVesselDockUndock);
+			GameEvents.onSameVesselUndock.Add(this.onSameVesselDockUndock);
+			GameEvents.onPartUndock.Add(this.onPartUndock);
+			GameEvents.onUndock.Add(this.onReportEvent);
+			GameEvents.onPartCouple.Add(this.onPartCouple);
+			GameEvents.onPartJointBreak.Add(this.onPartJointBreak);
+		}
+
+		public void Destroy()
+		{
+			GameEvents.onSameVesselDock.Remove(this.onSameVesselDockUndock);
+			GameEvents.onSameVesselUndock.Remove(this.onSameVesselDockUndock);
+			GameEvents.onPartUndock.Remove(this.onPartUndock);
+			GameEvents.onUndock.Remove(this.onReportEvent);
+			GameEvents.onPartCouple.Remove(this.onPartCouple);
+			GameEvents.onPartJointBreak.Remove(this.onPartJointBreak);
+		}
+
+		public void onSameVesselDockUndock(GameEvents.FromToAction<ModuleDockingNode, ModuleDockingNode> data)
+		{
+			this.FromModuleToModuleHelper(
+				this.getStringBuilder(),
+				new GameEvents.FromToAction<PartModule, PartModule>(data.from, data.to)
+			);
+		}
+
+		public void onPartJointBreak(PartJoint joint)
+		{
+			this.PartJointHelper(this.getStringBuilder(), joint);
+		}
+
+		public void onPartUndock(Part part)
+		{
+			this.PartEventHelper(this.getStringBuilder(), part);
+		}
+
+		public void onReportEvent(EventReport data)
+		{
+			this.EventReportHelper(this.getStringBuilder(), data);
+		}
+
+		public void onPartCouple(GameEvents.FromToAction<Part, Part> data)
+		{
+			this.FromPartToPartHelper(this.getStringBuilder(), data);
+		}
+
+		internal void EventReportHelper(StringBuilder sb, EventReport data)
+		{
+			sb.Append("\n\tOrigin Part:");
+			this.appendPartAncestry(sb, data.origin);
+
+			sb.AppendFormat(
+				"\n\tother: '{0}'" +
+				"\n\tmsg: '{1}'" +
+				"\n\tsender: '{2}'",
+				data.other,
+				data.msg,
+				data.sender
+			);
+
+			Debug.Log(sb.ToString());
+		}
+
+		internal void PartEventHelper(StringBuilder sb, Part part)
+		{
+			this.appendPartAncestry(sb, part);
+
+			Debug.Log(sb.ToString());
+		}
+
+		internal void FromPartToPartHelper(StringBuilder sb, GameEvents.FromToAction<Part, Part> data)
+		{
+			sb.Append("\n\tFrom:");
+
+			this.appendPartAncestry(sb, data.from);
+
+			sb.Append("\n\tTo:");
+
+			this.appendPartAncestry(sb, data.to);
+
+			Debug.Log(sb.ToString());
+		}
+
+		internal void FromModuleToModuleHelper(StringBuilder sb, GameEvents.FromToAction<PartModule, PartModule> data)
+		{
+			sb.Append("\n\tFrom:");
+
+			this.appendModuleAncestry(sb, data.from);
+
+			sb.Append("\n\tTo:");
+
+			this.appendModuleAncestry(sb, data.to);
+
+			Debug.Log(sb.ToString());
+		}
+
+		internal void PartJointHelper(StringBuilder sb, PartJoint joint)
+		{
+			sb.Append("PartJoint: ");
+			if (joint != null)
+			{
+				sb.Append(joint);
+				this.appendPartAncestry(sb, joint.Host);
+			}
+			else
+			{
+				sb.Append("null");
+			}
+
+			Debug.Log(sb.ToString());
+		}
+
+		internal StringBuilder appendModuleAncestry(StringBuilder sb, PartModule module, uint tabs = 1)
+		{
+			sb.Append('\n');
+			for (ushort i=0; i < tabs; i++)
+			{
+				sb.Append('\t');
+			}
+			sb.Append("Module: ");
+
+			if (module != null)
+			{
+				sb.Append(module.moduleName);
+				this.appendPartAncestry(sb, module.part, tabs + 1u);
+			}
+			else
+			{
+				sb.Append("null");
+			}
+
+			return sb;
+		}
+
+		internal StringBuilder appendPartAncestry(StringBuilder sb, Part part, uint tabs = 1)
+		{
+			sb.Append('\n');
+			for (ushort i=0; i < tabs; i++)
+			{
+				sb.Append('\t');
+			}
+			sb.Append("Part: ");
+
+			if (part != null)
+			{
+				sb.AppendFormat("'{0}' ({1})", part.partInfo.title, part.partName);
+				this.appendVessel(sb, part.vessel, tabs + 1u);
+			}
+			else
+			{
+				sb.Append("null");
+			}
+
+			return sb;
+		}
+
+		internal StringBuilder appendVessel(StringBuilder sb, Vessel vessel, uint tabs = 1)
+		{
+			sb.Append('\n');
+			for (ushort i=0; i < tabs; i++)
+			{
+				sb.Append('\t');
+			}
+			sb.Append("Vessel: ");
+
+			if (vessel != null)
+			{
+				sb.AppendFormat("'{0}' ({1})", vessel.vesselName, vessel.id);
+			}
+			else
+			{
+				sb.Append("null");
+			}
+
+			return sb;
+		}
+
+		internal StringBuilder getStringBuilder()
+		{
+			StringBuilder sb = new StringBuilder();
+			sb.AppendFormat("{0}: called {1} ",
+				this.GetType().Name,
+				new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name
+			);
+			return sb;
+		}
+	}
+}
+#endif

--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -318,7 +318,7 @@
 					"{0}: {1} on {2} cannot transmit: {3}",
 					this.GetType().Name,
 					this.part.partInfo.title,
-					this.vessel.name,
+					this.vessel.vesselName,
 					Enum.GetName(typeof(PartStates), partState)
 				));
 				return false;
@@ -417,11 +417,17 @@
 
 		public override string ToString()
 		{
-			return string.Format(
-				"{0} on {1}.",
-				this.part.partInfo.title,
-				vessel.name
-			);
+			StringBuilder msg = new StringBuilder();
+
+			msg.Append(this.part.partInfo.title);
+
+			if (vessel != null)
+			{
+				msg.Append(" on ");
+				msg.Append(vessel.vesselName);
+			}
+
+			return msg.ToString();
 		}
 
 		// When debugging, it's nice to have a button that just tells you everything.

--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -25,7 +25,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("0.6.2.*")]
+[assembly: AssemblyVersion("0.6.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
@@ -76,7 +76,7 @@
 					"{0}: {1} on {2} cannot transmit: {3}",
 					this.GetType().Name,
 					this.title,
-					this.vessel.name,
+					this.vessel.vesselName,
 					Enum.GetName(typeof(PartStates), partState)
 				));
 				return false;
@@ -104,6 +104,14 @@
 			this.protoPart = pps;
 			this.vessel = pps.pVesselRef.vesselRef;
 		}
+
+		~ProtoAntennaRelay()
+		{
+			Tools.PostDebugMessage(string.Format(
+				"{0}: destroyed",
+				this.ToString()
+			));
+		}
 	}
 }
 

--- a/RelayDatabase.cs
+++ b/RelayDatabase.cs
@@ -7,6 +7,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
 using UnityEngine;
 
 namespace AntennaRange
@@ -45,6 +46,12 @@
 		// Vessel.id-keyed hash table of part counts, used for caching
 		protected Dictionary<Guid, int> vesselPartCountTable;
 
+		// Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time.
+		public Dictionary<Guid, bool> CheckedVesselsTable;
+
+		protected int cacheHits;
+		protected int cacheMisses;
+
 		/*
 		 * Properties
 		 * */
@@ -58,12 +65,19 @@
 				{
 					// ...Generate an entry for this vessel.
 					this.AddVessel(vessel);
+					this.cacheMisses++;
 				}
 				// If our part count disagrees with the vessel's part count...
-				if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
+				else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
 				{
 					// ...Update the our vessel in the cache
 					this.UpdateVessel(vessel);
+					this.cacheMisses++;
+				}
+				// Otherwise, it's a hit
+				else
+				{
+					this.cacheHits++;
 				}
 
 				// Return the Part-hashed table of relays for this vessel
@@ -79,13 +93,13 @@
 		public bool AddVessel(Vessel vessel)
 		{
 			// If this vessel is already here...
-			if (relayDatabase.ContainsKey(vessel.id))
+			if (this.ContainsKey(vessel))
 			{
 				// ...post an error
 				Debug.LogWarning(string.Format(
 					"{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
 					this.GetType().Name,
-					vessel.name,
+					vessel.vesselName,
 					vessel.id
 				));
 
@@ -110,12 +124,12 @@
 		public void UpdateVessel(Vessel vessel)
 		{
 			// Squak if the database doesn't have the vessel
-			if (!relayDatabase.ContainsKey(vessel.id))
+			if (!this.ContainsKey(vessel))
 			{
 				throw new InvalidOperationException(string.Format(
-					"{0}: Update called vessel '{1}' (id: {2}) not in database: vessel will be added.",
+					"{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.",
 					this.GetType().Name,
-					vessel.name,
+					vessel.vesselName,
 					vessel.id
 				));
 			}
@@ -128,10 +142,23 @@
 			this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
 		}
 
+		// Remove a vessel from the cache, if it exists.
+		public void DirtyVessel(Vessel vessel)
+		{
+			if (this.relayDatabase.ContainsKey(vessel.id))
+			{
+				this.relayDatabase.Remove(vessel.id);
+			}
+			if (this.vesselPartCountTable.ContainsKey(vessel.id))
+			{
+				this.vesselPartCountTable.Remove(vessel.id);
+			}
+		}
+
 		// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
 		public bool ContainsKey(Guid key)
 		{
-			return (this.relayDatabase.ContainsKey(key) & this.vesselPartCountTable.ContainsKey(key));
+			return this.relayDatabase.ContainsKey(key);
 		}
 
 		// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
@@ -141,24 +168,24 @@
 		}
 
 		// Runs when a vessel is modified (or when we switch to one, to catch docking events)
-		public void onVesselWasModified(Vessel vessel)
+		public void onVesselEvent(Vessel vessel)
 		{
 			// If we have this vessel in our cache...
 			if (this.ContainsKey(vessel))
 			{
 				// If our part counts disagree (such as if a part has been added or broken off,
 				// or if we've just docked or undocked)...
-				if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
+				if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
 				{
 					Tools.PostDebugMessage(string.Format(
-						"{0}: dirtying cache for vessel '{1}' (id: {2}).",
+						"{0}: dirtying cache for vessel '{1}' ({2}).",
 						this.GetType().Name,
-						vessel.name,
+						vessel.vesselName,
 						vessel.id
 					));
 
 					// Dirty the cache (real vessels will never have negative part counts)
-					this.vesselPartCountTable[vessel.id] = -1;
+					this.DirtyVessel(vessel);
 				}
 			}
 		}
@@ -170,8 +197,24 @@
 			if (FlightGlobals.ActiveVessel != null)
 			{
 				// ... dirty its cache
-				this.onVesselWasModified(FlightGlobals.ActiveVessel);
-			}
+				this.onVesselEvent(FlightGlobals.ActiveVessel);
+			}
+		}
+
+		// Runs when parts are undocked
+		public void onPartEvent(Part part)
+		{
+			if (part != null && part.vessel != null)
+			{
+				this.onVesselEvent(part.vessel);
+			}
+		}
+
+		// Runs when parts are coupled, as in docking
+		public void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
+		{
+			this.onPartEvent(data.from);
+			this.onPartEvent(data.to);
 		}
 
 		// Produce a Part-hashed table of relays for the given vessel
@@ -183,7 +226,7 @@
 			Tools.PostDebugMessage(string.Format(
 				"{0}: Getting antenna relays from vessel {1}.",
 				"IAntennaRelay",
-				vessel.name
+				vessel.vesselName
 			));
 
 			// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
@@ -191,7 +234,7 @@
 				Tools.PostDebugMessage(string.Format(
 					"{0}: vessel {1} is loaded, searching for modules in loaded parts.",
 					"IAntennaRelay",
-					vessel.name
+					vessel.vesselName
 				));
 
 				// Loop through the Parts in the Vessel...
@@ -216,22 +259,47 @@
 			{
 				Tools.PostDebugMessage(string.Format(
 					"{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
-					"IAntennaRelay",
-					vessel.name
+					this.GetType().Name,
+					vessel.vesselName
 				));
 
 				// Loop through the ProtoPartModuleSnapshots in the Vessel...
 				foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots)
 				{
+					Tools.PostDebugMessage(string.Format(
+						"{0}: Searching in protopartsnapshot {1}",
+						this.GetType().Name,
+						pps
+					));
+
 					// ...Fetch the prefab, because it's more useful for what we're doing.
 					Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
+
+					Tools.PostDebugMessage(string.Format(
+						"{0}: Got partPrefab {1} in protopartsnapshot {2}",
+						this.GetType().Name,
+						partPrefab,
+						pps
+					));
 
 					// ...loop through the PartModules in the prefab...
 					foreach (PartModule module in partPrefab.Modules)
 					{
+						Tools.PostDebugMessage(string.Format(
+							"{0}: Searching in partmodule {1}",
+							this.GetType().Name,
+							module
+						));
+
 						// ...if the module is a relay...
 						if (module is IAntennaRelay)
 						{
+							Tools.PostDebugMessage(string.Format(
+								"{0}: partmodule {1} is antennarelay",
+								this.GetType().Name,
+								module
+							));
+
 							// ...build a new ProtoAntennaRelay and add it to the table
 							relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps));
 							// ...neglect relay objects after the first in each part.
@@ -242,9 +310,10 @@
 			}
 
 			Tools.PostDebugMessage(string.Format(
-				"{0}: vessel '{1}' has {2} transmitters.",
+				"{0}: vessel '{1}' ({2}) has {3} transmitters.",
 				"IAntennaRelay",
-				vessel.name,
+				vessel.vesselName,
+				vessel.id,
 				relays.Count
 			));
 		}
@@ -253,24 +322,63 @@
 		protected RelayDatabase()
 		{
 			// Initialize the databases
-			relayDatabase =	new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
-			vesselPartCountTable = new Dictionary<Guid, int>();
+			this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
+			this.vesselPartCountTable = new Dictionary<Guid, int>();
+			this.CheckedVesselsTable = new Dictionary<Guid, bool>();
+
+			this.cacheHits = 0;
+			this.cacheMisses = 0;
 
 			// Subscribe to some events
-			GameEvents.onVesselWasModified.Add(this.onVesselWasModified);
-			GameEvents.onVesselChange.Add(this.onVesselWasModified);
+			GameEvents.onVesselWasModified.Add(this.onVesselEvent);
+			GameEvents.onVesselChange.Add(this.onVesselEvent);
+			GameEvents.onVesselDestroy.Add(this.onVesselEvent);
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
+			GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
+			GameEvents.onPartUndock.Add(this.onPartEvent);
 		}
 
 		~RelayDatabase()
 		{
 			// Unsubscribe from the events
-			GameEvents.onVesselWasModified.Remove(this.onVesselWasModified);
-			GameEvents.onVesselChange.Remove(this.onVesselWasModified);
+			GameEvents.onVesselWasModified.Remove(this.onVesselEvent);
+			GameEvents.onVesselChange.Remove(this.onVesselEvent);
+			GameEvents.onVesselDestroy.Remove(this.onVesselEvent);
 			GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
+			GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
+			GameEvents.onPartUndock.Remove(this.onPartEvent);
 
 			Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
-		}
+
+			KSPLog.print(string.Format(
+				"{0} destructed.  Cache hits: {1}, misses: {2}, hit rate: {3:P1}",
+				this.GetType().Name,
+				this.cacheHits,
+				this.cacheMisses,
+				(float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits)
+			));
+		}
+
+		#if DEBUG
+		public void Dump()
+		{
+			StringBuilder sb = new StringBuilder();
+
+			sb.Append("Dumping RelayDatabase:");
+
+			foreach (Guid id in this.relayDatabase.Keys)
+			{
+				sb.AppendFormat("\nVessel {0}:", id);
+
+				foreach (IAntennaRelay relay in this.relayDatabase[id].Values)
+				{
+					sb.AppendFormat("\n\t{0}", relay.ToString());
+				}
+			}
+
+			Tools.PostDebugMessage(sb.ToString());
+		}
+		#endif
 	}
 }