Major reworking of the HUD underpinnings to be more modular and require less code duplication. Also made the Editor HUD movable, but for now it's stuck along the left edge.
Major reworking of the HUD underpinnings to be more modular and require less code duplication. Also made the Editor HUD movable, but for now it's stuck along the left edge.

--- a/IVOID_Module.cs
+++ b/IVOID_Module.cs
@@ -51,6 +51,7 @@
 	{
 		void Update();
 		void FixedUpdate();
+		void OnDestroy();
 	}
 
 	public interface IVOID_EditorModule : IVOID_Module {}

--- 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("0.15.*")]
+[assembly: AssemblyVersion("0.16.*")]
 // 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/VOID.csproj
+++ b/VOID.csproj
@@ -100,6 +100,7 @@
     <Compile Include="VOID_StageInfo.cs" />
     <Compile Include="VOID_Styles.cs" />
     <Compile Include="VOID_Data.cs" />
+    <Compile Include="VOID_HUDModule.cs" />
   </ItemGroup>
   <ProjectExtensions>
     <MonoDevelop>

--- a/VOIDEditorMaster.cs
+++ b/VOIDEditorMaster.cs
@@ -40,7 +40,7 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-using Engineer.VesselSimulator;
+using KerbalEngineer.VesselSimulator;
 using KSP;
 using System;
 using ToadicusTools;
@@ -105,6 +105,26 @@
 
 			this.Core.OnGUI();
 		}
+
+		public void OnDestroy()
+		{
+			if (this.Core == null)
+			{
+				return;
+			}
+
+			this.Core.OnDestroy();
+		}
+
+		public void OnApplicationQuit()
+		{
+			if (this.Core == null)
+			{
+				return;
+			}
+
+			this.Core.OnApplicationQuit();
+		}
 	}
 }
 

--- a/VOIDFlightMaster.cs
+++ b/VOIDFlightMaster.cs
@@ -42,7 +42,7 @@
 
 using System;
 using UnityEngine;
-using Engineer.VesselSimulator;
+using KerbalEngineer.VesselSimulator;
 using ToadicusTools;
 
 namespace VOID
@@ -104,6 +104,26 @@
 
 			this.Core.OnGUI();
 		}
+
+		public void OnDestroy()
+		{
+			if (this.Core == null)
+			{
+				return;
+			}
+
+			this.Core.OnDestroy();
+		}
+
+		public void OnApplicationQuit()
+		{
+			if (this.Core == null)
+			{
+				return;
+			}
+
+			this.Core.OnApplicationQuit();
+		}
     }
 }
 

--- a/VOID_CBInfoBrowser.cs
+++ b/VOID_CBInfoBrowser.cs
@@ -286,13 +286,13 @@
 		    else GUILayout.Label((body.orbit.ApA / 1000).ToString("##,#") + "km", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(VOID_Tools.ConvertInterval(body.orbit.timeToAp), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+		    else GUILayout.Label(VOID_Tools.FormatInterval(body.orbit.timeToAp), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 		    else GUILayout.Label((body.orbit.PeA / 1000).ToString("##,#") + "km", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(VOID_Tools.ConvertInterval(body.orbit.timeToPe), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+		    else GUILayout.Label(VOID_Tools.FormatInterval(body.orbit.timeToPe), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 		    else GUILayout.Label((body.orbit.semiMajorAxis / 1000).ToString("##,#") + "km", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
@@ -301,10 +301,10 @@
 		    else GUILayout.Label(body.orbit.eccentricity.ToString("F4") + "", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(VOID_Tools.ConvertInterval(body.orbit.period), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(VOID_Tools.ConvertInterval(body.rotationPeriod), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+		    else GUILayout.Label(VOID_Tools.FormatInterval(body.orbit.period), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+
+		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+		    else GUILayout.Label(VOID_Tools.FormatInterval(body.rotationPeriod), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 		    else GUILayout.Label((body.orbit.orbitalSpeed / 1000).ToString("F2") + "km/s", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));

--- a/VOID_Core.cs
+++ b/VOID_Core.cs
@@ -26,7 +26,7 @@
 // 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 Engineer.VesselSimulator;
+using KerbalEngineer.VesselSimulator;
 using KSP;
 using System;
 using System.Collections.Generic;
@@ -178,6 +178,12 @@
 		internal IButton ToolbarButton;
 
 		internal ApplicationLauncherButton AppLauncherButton;
+
+		/*
+		 * Events
+		 * */
+		public delegate void VOIDEventHandler(object sender);
+		public event VOIDEventHandler onApplicationQuit;
 
 		/*
 		 * Properties
@@ -605,11 +611,29 @@
 				}
 			}
 
-			foreach (IVOID_BehaviorModule module in
-			         this._modules.OfType<IVOID_BehaviorModule>().Where(m => !m.GetType().IsAbstract))
-			{
-				module.FixedUpdate();
-			}
+			foreach (IVOID_Module module in this.Modules)
+			{
+				if (module is IVOID_BehaviorModule)
+				{
+					((IVOID_BehaviorModule)module).FixedUpdate();
+				}
+			}
+		}
+
+		public void OnDestroy()
+		{
+			foreach (IVOID_Module module in this.Modules)
+			{
+				if (module is IVOID_BehaviorModule)
+				{
+					((IVOID_BehaviorModule)module).OnDestroy();
+				}
+			}
+		}
+
+		public void OnApplicationQuit()
+		{
+			this.onApplicationQuit(this);
 		}
 
 		public void ResetGUI()
@@ -785,7 +809,7 @@
 					SimManager.Gravity = this.vessel.mainBody.gravParameter / (radius * radius);
 				}
 
-				SimManager.minSimTime = (long)(this.updatePeriod * 1000);
+				SimManager.minSimTime = new TimeSpan(0, 0, 0, 0, (int)(this.updatePeriod * 1000d));
 
 				SimManager.TryStartSimulation();
 			}
@@ -972,7 +996,7 @@
 			this.ToolbarButton.Text = this.VoidName;
 			this.SetIconTexture(this.powerState | this.activeState);
 
-			this.ToolbarButton.Visibility = new GameScenesVisibility(GameScenes.EDITOR, GameScenes.FLIGHT, GameScenes.SPH);
+			this.ToolbarButton.Visibility = new GameScenesVisibility(GameScenes.EDITOR, GameScenes.FLIGHT);
 
 			this.ToolbarButton.OnClick += 
 				(e) =>

--- a/VOID_Data.cs
+++ b/VOID_Data.cs
@@ -26,7 +26,7 @@
 // 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 Engineer.VesselSimulator;
+using KerbalEngineer.VesselSimulator;
 using KSP;
 using System;
 using System.Collections.Generic;
@@ -242,7 +242,7 @@
 						return double.NaN;
 					}
 
-					return core.LastStage.totalMass - core.LastStage.totalBaseMass;
+					return core.LastStage.resourceMass;
 				},
 				"tons"
 			);
@@ -257,7 +257,7 @@
 						return double.NaN;
 					}
 
-					return core.LastStage.mass - core.LastStage.baseMass;
+					return core.LastStage.resourceMass;
 				},
 				"tons"
 			);
@@ -844,7 +844,7 @@
 						format = string.Intern("T + {0}");
 					}
 
-					return string.Format(format, VOID_Tools.ConvertInterval(interval));
+					return string.Format(format, VOID_Tools.FormatInterval(interval));
 				}
 			);
 
@@ -886,7 +886,7 @@
 						format = string.Intern("T + {0}");
 					}
 
-					return string.Format(format, VOID_Tools.ConvertInterval(interval));
+					return string.Format(format, VOID_Tools.FormatInterval(interval));
 				}
 			);
 
@@ -1040,13 +1040,13 @@
 		public static readonly VOID_StrValue timeToApo =
 			new VOID_StrValue(
 				"Time to Apoapsis",
-				new Func<string>(() => VOID_Tools.ConvertInterval(core.vessel.orbit.timeToAp))
+				new Func<string>(() => VOID_Tools.FormatInterval(core.vessel.orbit.timeToAp))
 			);
 
 		public static readonly VOID_StrValue timeToPeri =
 			new VOID_StrValue(
 				"Time to Periapsis",
-				new Func<string>(() => VOID_Tools.ConvertInterval(core.vessel.orbit.timeToPe))
+				new Func<string>(() => VOID_Tools.FormatInterval(core.vessel.orbit.timeToPe))
 			);
 
 		public static readonly VOID_DoubleValue orbitInclination =
@@ -1072,7 +1072,7 @@
 		public static readonly VOID_StrValue orbitPeriod =
 			new VOID_StrValue(
 				"Period",
-				new Func<string>(() => VOID_Tools.ConvertInterval(core.vessel.orbit.period))
+				new Func<string>(() => VOID_Tools.FormatInterval(core.vessel.orbit.period))
 			);
 
 		public static readonly VOID_DoubleValue semiMajorAxis =

--- a/VOID_DataLogger.cs
+++ b/VOID_DataLogger.cs
@@ -29,6 +29,7 @@
 using KSP;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Text;
 using ToadicusTools;
 using UnityEngine;
@@ -40,206 +41,269 @@
 		/*
 		 * Fields
 		 * */
-		protected bool stopwatch1_running;
-
-		protected bool csv_logging;
-		protected bool first_write;
-
-		protected double stopwatch1;
-
-		protected string csv_log_interval_str;
-
-		protected float csv_log_interval;
-
-		protected double csvWriteTimer;
-		protected double csvCollectTimer;
-
-		protected System.Text.UTF8Encoding enc;
-
-		protected List<string> csvList = new List<string>();
+		#region Fields
+
+		protected bool _loggingActive;
+		protected bool firstWrite;
+
+		[AVOID_SaveValue("logInterval")]
+		protected VOID_SaveValue<float> logInterval;
+		protected string logIntervalStr;
+
+		protected float csvCollectTimer;
+
+		protected List<byte> csvBytes;
+
+		protected string _fileName;
+		protected FileStream _outputFile;
+
+		protected uint outstandingWrites;
+
+		protected System.Text.UTF8Encoding _utf8Encoding;
+
+		#endregion
 
 		/*
 		 * Properties
 		 * */
 
+		#region Properties
+
+		// TODO: Add configurable or incremental file names.
+		protected bool loggingActive
+		{
+			get
+			{
+				return this._loggingActive;
+			}
+			set
+			{
+				if (value != this._loggingActive)
+				{
+					if (value)
+					{
+						this.csvCollectTimer = 0f;
+					}
+					else
+					{
+						this.CloseFileIfOpen();
+					}
+
+					this._loggingActive = value;
+				}
+			}
+		}
+
+		protected string fileName
+		{
+			get
+			{
+				if (this._fileName == null || this._fileName == string.Empty)
+				{
+					this._fileName = KSP.IO.IOUtils.GetFilePathFor(
+						typeof(VOID_Core),
+						string.Format(
+							"{0}_{1}",
+							this.vessel.vesselName,
+							"data.csv"
+						),
+						null
+					);
+				}
+
+				return this._fileName;
+			}
+		}
+
+		protected FileStream outputFile
+		{
+			get
+			{
+				if (this._outputFile == null)
+				{
+					Tools.DebugLogger logger = Tools.DebugLogger.New(this);
+					logger.AppendFormat("Initializing output file '{0}' with mode ", this.fileName);
+
+					if (File.Exists(this.fileName))
+					{
+						logger.Append("append");
+						this._outputFile = new FileStream(
+							this.fileName,
+							FileMode.Append,
+							FileAccess.Write,
+							FileShare.Read,
+							512,
+							true
+						);
+					}
+					else
+					{
+						logger.Append("create");
+						this._outputFile = new FileStream(
+							this.fileName,
+							FileMode.Create,
+							FileAccess.Write,
+							FileShare.Read,
+							512,
+							true
+						);
+
+						byte[] byteOrderMark = utf8Encoding.GetPreamble();
+
+						logger.Append(" and writing preamble");
+						this._outputFile.Write(byteOrderMark, 0, byteOrderMark.Length);
+					}
+
+					logger.Append('.');
+
+					logger.AppendFormat("  File is {0}opened asynchronously.", this._outputFile.IsAsync ? "" : "not ");
+
+					logger.Print();
+				}
+
+				return this._outputFile;
+			}
+		}
+
+		public UTF8Encoding utf8Encoding
+		{
+			get
+			{
+				if (this._utf8Encoding == null)
+				{
+					this._utf8Encoding = new UTF8Encoding(true);
+				}
+
+				return this._utf8Encoding;
+			}
+		}
+
+		#endregion
 
 		/*
 		 * Methods
 		 * */
-		public VOID_DataLogger()
-		{
-			this._Name = "CSV Data Logger";
-
-			this.stopwatch1_running = false;
-
-			this.csv_logging = false;
-			this.first_write = true;
-
-			this.stopwatch1 = 0;
-			this.csv_log_interval_str = "0.5";
-
-			this.csvWriteTimer = 0;
-			this.csvCollectTimer = 0;
-
-			this.WindowPos.x = Screen.width - 520;
-			this.WindowPos.y = 85;
-		}
-
-		public override void ModuleWindow(int _)
-		{
-			GUIStyle txt_white = new GUIStyle(GUI.skin.label);
-			txt_white.normal.textColor = txt_white.focused.textColor = Color.white;
-			txt_white.alignment = TextAnchor.UpperRight;
-			GUIStyle txt_green = new GUIStyle(GUI.skin.label);
-			txt_green.normal.textColor = txt_green.focused.textColor = Color.green;
-			txt_green.alignment = TextAnchor.UpperRight;
-			GUIStyle txt_yellow = new GUIStyle(GUI.skin.label);
-			txt_yellow.normal.textColor = txt_yellow.focused.textColor = Color.yellow;
-			txt_yellow.alignment = TextAnchor.UpperRight;
-
-			GUILayout.BeginVertical();
-
-			GUILayout.Label("System time: " + DateTime.Now.ToString("HH:mm:ss"));
-			GUILayout.Label(VOID_Tools.ConvertInterval(stopwatch1));
-
-			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
-			if (GUILayout.Button("Start"))
-			{
-				if (stopwatch1_running == false) stopwatch1_running = true;
-			}
-			if (GUILayout.Button("Stop"))
-			{
-				if (stopwatch1_running == true) stopwatch1_running = false;
-			}
-			if (GUILayout.Button("Reset"))
-			{
-				if (stopwatch1_running == true) stopwatch1_running = false;
-				stopwatch1 = 0;
-			}
-			GUILayout.EndHorizontal();
-
-			GUIStyle label_style = txt_white;
-			string log_label = "Inactive";
-			if (csv_logging && vessel.situation.ToString() == "PRELAUNCH")
-			{
-				log_label = "Awaiting launch";
-				label_style = txt_yellow;
-			}
-			if (csv_logging && vessel.situation.ToString() != "PRELAUNCH")
-			{
-				log_label = "Active";
-				label_style = txt_green;
-			}
-			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
-			csv_logging = GUILayout.Toggle(csv_logging, "Data logging: ", GUILayout.ExpandWidth(false));
-			GUILayout.Label(log_label, label_style, GUILayout.ExpandWidth(true));
-			GUILayout.EndHorizontal();
-
-			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
-			GUILayout.Label("Interval: ", GUILayout.ExpandWidth(false));
-			csv_log_interval_str = GUILayout.TextField(csv_log_interval_str, GUILayout.ExpandWidth(true));
-			GUILayout.Label("s", GUILayout.ExpandWidth(false));
-			GUILayout.EndHorizontal();
-
-			float new_log_interval;
-			if (float.TryParse(csv_log_interval_str, out new_log_interval))
-			{
-				csv_log_interval = new_log_interval;
-			}
-
-			GUILayout.EndVertical();
-			GUI.DragWindow();
-		}
-
+		#region Monobehaviour Lifecycle
 		public void Update()
 		{
+			if (this.csvBytes != null && this.csvBytes.Count > 0)
+			{
+				// csvList is not empty, write it
+				this.AsyncWriteData();
+			}
+
 			// CSV Logging
 			// from ISA MapSat
-			if (csv_logging)
+			if (loggingActive)
 			{
 				//data logging is on
 				//increment timers
-				csvWriteTimer += Time.deltaTime;
-				csvCollectTimer += Time.deltaTime;
-
-				if (csvCollectTimer >= csv_log_interval && vessel.situation != Vessel.Situations.PRELAUNCH)
+				this.csvCollectTimer += Time.deltaTime;
+
+				if (this.csvCollectTimer >= this.logInterval)
 				{
 					//data logging is on, vessel is not prelaunch, and interval has passed
 					//write a line to the list
-					line_to_csvList();  //write to the csv
+					this.CollectLogData();
 				}
-
-				if (csvList.Count != 0 && csvWriteTimer >= 15f)
-				{
-					// csvList is not empty and interval between writings to file has elapsed
-					//write it
-					string[] csvData;
-					csvData = (string[])csvList.ToArray();
-					Innsewerants_writeData(csvData);
-					csvList.Clear();
-					csvWriteTimer = 0f;
-				}
-			}
-			else
-			{
-				//data logging is off
-				//reset any timers and clear anything from csvList
-				csvWriteTimer = 0f;
-				csvCollectTimer = 0f;
-				if (csvList.Count > 0) csvList.Clear();
-			}
-
-			if (stopwatch1_running)
-			{
-				stopwatch1 += Time.deltaTime;
 			}
 		}
 
 		public void FixedUpdate() {}
 
-		private void Innsewerants_writeData(string[] csvArray)
-		{
-			if (this.enc == null)
-			{
-				this.enc = new System.Text.UTF8Encoding(true, true);
-			}
-
-			KSP.IO.FileStream bFile;
-
-			if (KSP.IO.File.Exists<VOID_Core>(vessel.vesselName + "_data.csv", null))
-			{
-				bFile = KSP.IO.File.Open<VOID_Core>(vessel.vesselName + "_data.csv", KSP.IO.FileMode.Append, null);
-			}
-			else
-			{
-				bFile = KSP.IO.File.Open<VOID_Core>(vessel.vesselName + "_data.csv", KSP.IO.FileMode.Create, null);
-
-				byte[] bom = enc.GetPreamble();
-
-				bFile.Write(bom, 0, bom.Length);
-			}
-
-			foreach (string line in csvArray)
-			{
-				byte[] lineBytes = enc.GetBytes(line);
-
-				bFile.Write(lineBytes, 0, lineBytes.Length);
-			}
-
-			bFile.Dispose();
-		}
-
-		private void line_to_csvList()
-		{
+		public void OnDestroy()
+		{
+			Tools.DebugLogger logger = Tools.DebugLogger.New(this);
+
+			logger.Append("Destroying...");
+
+			this.CloseFileIfOpen();
+
+			logger.Append(" Done.");
+			logger.Print(false);
+		}
+
+		#endregion
+
+		#region VOID_Module Overrides
+
+		public override void LoadConfig()
+		{
+			base.LoadConfig();
+
+			this.logIntervalStr = this.logInterval.value.ToString("#.0##");
+		}
+
+		public override void ModuleWindow(int _)
+		{
+			GUILayout.BeginVertical();
+
+			GUILayout.Label(
+				string.Format("System time: {0}", DateTime.Now.ToString("HH:mm:ss")),
+				GUILayout.ExpandWidth(true)
+			);
+			GUILayout.Label(
+				string.Format("Kerbin time: {0}", VOID_Tools.FormatDate(Planetarium.GetUniversalTime())),
+				GUILayout.ExpandWidth(true)
+			);
+
+			GUIStyle activeLabelStyle = VOID_Styles.labelRed;
+			string activeLabelText = "Inactive";
+			if (loggingActive)
+			{
+				activeLabelText = "Active";
+				activeLabelStyle = VOID_Styles.labelGreen;
+			}
+
+			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
+
+			this.loggingActive = GUILayout.Toggle(loggingActive, "Data logging: ", GUILayout.ExpandWidth(false));
+			GUILayout.Label(activeLabelText, activeLabelStyle, GUILayout.ExpandWidth(true));
+
+			GUILayout.EndHorizontal();
+
+			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
+
+			GUILayout.Label("Interval: ", GUILayout.ExpandWidth(false));
+
+			logIntervalStr = GUILayout.TextField(logIntervalStr, GUILayout.ExpandWidth(true));
+			GUILayout.Label("s", GUILayout.ExpandWidth(false));
+
+			GUILayout.EndHorizontal();
+
+			float newLogInterval;
+			if (float.TryParse(logIntervalStr, out newLogInterval))
+			{
+				logInterval.value = newLogInterval;
+				this.logIntervalStr = this.logInterval.value.ToString("#.0##");
+			}
+
+			GUILayout.EndVertical();
+
+			GUI.DragWindow();
+		}
+
+		#endregion
+
+		#region Data Collection
+
+		private void CollectLogData()
+		{
+			if (this.csvBytes == null)
+			{
+				this.csvBytes = new List<byte>();
+			}
+
 			//called if logging is on and interval has passed
 			//writes one line to the csvList
 
 			StringBuilder line = new StringBuilder();
 
-			if (first_write)
-			{
-				first_write = false;
+			if (firstWrite)
+			{
+				firstWrite = false;
 				line.Append(
+					"\"Kerbin Universal Time (s)\"," +
 					"\"Mission Elapsed Time (s)\t\"," +
 					"\"Altitude ASL (m)\"," +
 					"\"Altitude above terrain (m)\"," +
@@ -258,18 +322,20 @@
 				);
 			}
 
+			// Universal time
+			line.Append(Planetarium.GetUniversalTime().ToString("F2"));
+			line.Append(',');
+
 			//Mission time
 			line.Append(vessel.missionTime.ToString("F3"));
 			line.Append(',');
 
 			//Altitude ASL
-			line.Append(vessel.orbit.altitude.ToString("F3"));
+			line.Append(VOID_Data.orbitAltitude.Value.ToString("F3"));
 			line.Append(',');
 
 			//Altitude (true)
-			double alt_true = vessel.orbit.altitude - vessel.terrainAltitude;
-			if (vessel.terrainAltitude < 0) alt_true = vessel.orbit.altitude;
-			line.Append(alt_true.ToString("F3"));
+			line.Append(VOID_Data.trueAltitude.Value.ToString("F3"));
 			line.Append(',');
 
 			// Surface Latitude
@@ -285,37 +351,35 @@
 			line.Append(',');
 
 			//Orbital velocity
-			line.Append(vessel.orbit.vel.magnitude.ToString("F3"));
+			line.Append(VOID_Data.orbitVelocity.Value.ToString("F3"));
 			line.Append(',');
 
 			//surface velocity
-			line.Append(vessel.srf_velocity.magnitude.ToString("F3"));
+			line.Append(VOID_Data.surfVelocity.Value.ToString("F3"));
 			line.Append(',');
 
 			//vertical speed
-			line.Append(vessel.verticalSpeed.ToString("F3"));
+			line.Append(VOID_Data.vertVelocity.Value.ToString("F3"));
 			line.Append(',');
 
 			//horizontal speed
-			line.Append(vessel.horizontalSrfSpeed.ToString("F3"));
+			line.Append(VOID_Data.horzVelocity.Value.ToString("F3"));
 			line.Append(',');
 
 			//gee force
-			line.Append(vessel.geeForce.ToString("F3"));
+			line.Append(VOID_Data.geeForce.Value.ToString("F3"));
 			line.Append(',');
 
 			//temperature
-			line.Append(vessel.flightIntegrator.getExternalTemperature().ToString("F2"));
+			line.Append(VOID_Data.temperature.Value.ToString("F2"));
 			line.Append(',');
 
 			//gravity
-			double r_vessel = vessel.mainBody.Radius + vessel.mainBody.GetAltitude(vessel.findWorldCenterOfMass());
-			double g_vessel = (VOID_Core.Constant_G * vessel.mainBody.Mass) / (r_vessel * r_vessel);
-			line.Append(g_vessel.ToString("F3"));
+			line.Append(VOID_Data.gravityAccel.Value.ToString("F3"));
 			line.Append(',');
 
 			//atm density
-			line.Append((vessel.atmDensity * 1000).ToString("F3"));
+			line.Append(VOID_Data.atmDensity.Value.ToString("G3"));
 			line.Append(',');
 
 			// Downrange Distance
@@ -323,10 +387,108 @@
 
 			line.Append('\n');
 
-			csvList.Add(line.ToString());
-
-			csvCollectTimer = 0f;
-		}
+			csvBytes.AddRange(this.utf8Encoding.GetBytes(line.ToString()));
+
+			this.csvCollectTimer = 0f;
+		}
+
+		#endregion
+
+		#region File IO Methods
+
+		protected void AsyncWriteCallback(IAsyncResult result)
+		{
+			Tools.PostDebugMessage(this, "Got async callback, IsCompleted = {0}", result.IsCompleted);
+
+			this.outputFile.EndWrite(result);
+			this.outstandingWrites--;
+		}
+
+		private void AsyncWriteData()
+		{
+			WriteState state = new WriteState();
+
+			state.bytes = this.csvBytes.ToArray();
+			state.stream = this.outputFile;
+
+			this.outstandingWrites++;
+			var writeCallback = new AsyncCallback(this.AsyncWriteCallback);
+
+			this.outputFile.BeginWrite(state.bytes, 0, state.bytes.Length, writeCallback, state);
+
+			this.csvBytes.Clear();
+		}
+
+		private void CloseFileIfOpen()
+		{
+			Tools.DebugLogger logger = Tools.DebugLogger.New(this);
+
+			logger.AppendFormat("Cleaning up file {0}...", this.fileName);
+
+			if (this.csvBytes != null && this.csvBytes.Count > 0)
+			{
+				logger.Append(" Writing remaining data...");
+				this.AsyncWriteData();
+			}
+
+			logger.Append(" Waiting for writes to finish.");
+			while (this.outstandingWrites > 0)
+			{
+				logger.Append('.');
+				System.Threading.Thread.Sleep(10);
+			}
+
+			if (this._outputFile != null)
+			{
+				this._outputFile.Close();
+				this._outputFile = null;
+				logger.Append(" File closed.");
+			}
+
+			logger.Print(false);
+		}
+
+		#endregion
+
+		#region Constructors & Destructors
+
+		public VOID_DataLogger()
+		{
+			this._Name = "CSV Data Logger";
+
+			this.loggingActive = false;
+			this.firstWrite = true;
+
+			this.logInterval = 0.5f;
+			this.csvCollectTimer = 0f;
+
+			this.outstandingWrites = 0;
+
+			this.WindowPos.x = Screen.width - 520f;
+			this.WindowPos.y = 85f;
+
+			this.core.onApplicationQuit += delegate(object sender)
+			{
+				this.CloseFileIfOpen();
+			};
+		}
+
+		~VOID_DataLogger()
+		{
+			this.OnDestroy();
+		}
+
+		#endregion
+
+		#region Subclasses
+
+		private class WriteState
+		{
+			public byte[] bytes;
+			public FileStream stream;
+		}
+
+		#endregion
 	}
 }
 

--- a/VOID_EditorCore.cs
+++ b/VOID_EditorCore.cs
@@ -26,7 +26,7 @@
 // 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 Engineer.VesselSimulator;
+using KerbalEngineer.VesselSimulator;
 using KSP;
 using System;
 using System.Collections.Generic;
@@ -115,7 +115,7 @@
 
 			foreach (IVOID_EditorModule module in this.Modules)
 			{
-				if (EditorLogic.startPod == null)
+				if (EditorLogic.RootPart == null)
 				{
 					module.StopGUI();
 					continue;
@@ -130,7 +130,7 @@
 				}
 			}
 
-			if (EditorLogic.startPod == null || !HighLogic.LoadedSceneIsEditor)
+			if (EditorLogic.RootPart == null || !HighLogic.LoadedSceneIsEditor)
 			{
 				this.StopGUI();
 				return;

--- a/VOID_EditorHUD.cs
+++ b/VOID_EditorHUD.cs
@@ -26,7 +26,7 @@
 // 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 Engineer.VesselSimulator;
+using KerbalEngineer.VesselSimulator;
 using KSP;
 using System;
 using System.Collections.Generic;
@@ -37,41 +37,20 @@
 
 namespace VOID
 {
-	public class VOID_EditorHUD : VOID_Module, IVOID_EditorModule
+	public class VOID_EditorHUD : VOID_HUDModule, IVOID_EditorModule
 	{
 		/*
 		 * Fields
 		 * */
-		[AVOID_SaveValue("colorIndex")]
-		protected VOID_SaveValue<int> _colorIndex = 0;
-
-		protected List<Color> textColors = new List<Color>();
-
-		protected GUIStyle labelStyle;
-
+		[AVOID_SaveValue("ehudWindowPos")]
+		protected VOID_SaveValue<Rect> ehudWindowPos;
+
+		protected HUDWindow ehudWindow;
 		protected EditorVesselOverlays _vesselOverlays;
 
 		/*
 		 * Properties
 		 * */
-		public int ColorIndex
-		{
-			get
-			{
-				return this._colorIndex;
-			}
-			set
-			{
-				if (this._colorIndex >= this.textColors.Count - 1)
-				{
-					this._colorIndex = 0;
-					return;
-				}
-
-				this._colorIndex = value;
-			}
-		}
-
 		protected EditorVesselOverlays vesselOverlays
 		{
 			get
@@ -122,57 +101,26 @@
 
 			this.toggleActive = true;
 
-			this.textColors.Add(Color.green);
-			this.textColors.Add(Color.black);
-			this.textColors.Add(Color.white);
-			this.textColors.Add(Color.red);
-			this.textColors.Add(Color.blue);
-			this.textColors.Add(Color.yellow);
-			this.textColors.Add(Color.gray);
-			this.textColors.Add(Color.cyan);
-			this.textColors.Add(Color.magenta);
-
-			this.labelStyle = new GUIStyle ();
-			// this.labelStyle.alignment = TextAnchor.UpperRight;
-			this.labelStyle.normal.textColor = this.textColors [this.ColorIndex];
+			this.ehudWindow = new HUDWindow(
+				this.ehudWindowFunc,
+				new Rect(EditorPanels.Instance.partsPanelWidth + 10f, 125f, 300f, 64f)
+			);
+			this.Windows.Add(this.ehudWindow);
+			this.ehudWindowPos = this.ehudWindow.WindowPos;
 
 			Tools.PostDebugMessage (this.GetType().Name + ": Constructed.");
 		}
 
-		public override void DrawGUI()
-		{
-			SimManager.RequestSimulation();
+		public void ehudWindowFunc(int id)
+		{
+			StringBuilder hudString = new StringBuilder();
 
 			if (this.core.LastStage == null)
 			{
 				return;
 			}
 
-			float hudLeft;
-			StringBuilder hudString;
-
-			if (EditorLogic.fetch.editorScreen == EditorLogic.EditorScreen.Parts)
-			{
-				hudLeft = EditorPanels.Instance.partsPanelWidth + 10;
-			}
-			else if (EditorLogic.fetch.editorScreen == EditorLogic.EditorScreen.Actions)
-			{
-				hudLeft = EditorPanels.Instance.actionsPanelWidth + 10;
-			}
-			else
-			{
-				return;
-			}
-
-			GUI.skin = this.core.Skin;
-
-			Rect hudPos = new Rect (hudLeft, 48, 300, 32);
-
-			hudString = new StringBuilder();
-
 			// GUI.skin = AssetBase.GetGUISkin("KSP window 2");
-
-			labelStyle.normal.textColor = textColors [ColorIndex];
 
 			hudString.Append("Total Mass: ");
 			hudString.Append(this.core.LastStage.totalMass.ToString("F3"));
@@ -212,18 +160,60 @@
 					).ToString("F3"));
 			}
 
-			GUI.Label (
-				hudPos,
-				hudString.ToString(),
-				labelStyle);
-		}
-
-		public override void DrawConfigurables()
-		{
-			if (GUILayout.Button ("Change HUD color", GUILayout.ExpandWidth (false)))
-			{
-				++this.ColorIndex;
-			}
+			GUILayout.Label(hudString.ToString(), VOID_Styles.labelHud, GUILayout.ExpandWidth(true));
+
+			if (!this.positionsLocked)
+			{
+				GUI.DragWindow();
+			}
+
+			GUI.BringWindowToBack(id);
+		}
+
+		public override void DrawGUI()
+		{
+			float hudLeft;
+
+			if (EditorLogic.fetch.editorScreen == EditorScreen.Parts)
+			{
+				hudLeft = EditorPanels.Instance.partsPanelWidth + 10f;
+				hudLeft += EditorPartList.Instance.transformTopLeft.position.x -
+					EditorPartList.Instance.transformTopLeft.parent.parent.position.x -
+					72f;
+			}
+			else if (EditorLogic.fetch.editorScreen == EditorScreen.Actions)
+			{
+				hudLeft = EditorPanels.Instance.actionsPanelWidth + 10f;
+			}
+			else
+			{
+				return;
+			}
+
+			bool snapToEdge = Mathf.Abs(this.ehudWindowPos.value.xMin - hudLeft) < 15f;
+
+			Tools.PostDebugMessage(this,
+				"EditorPartList topLeft.parent.parent.position: {0}\n" +
+				"EditorPartList topLeft.parent.position: {1}\n" +
+				"EditorPartList topLeft.position: {2}\n" +
+				"snapToEdge: {3} (pos.Xmin: {4}; hudLeft: {5})",
+				EditorPartList.Instance.transformTopLeft.parent.parent.position,
+				EditorPartList.Instance.transformTopLeft.parent.position,
+				EditorPartList.Instance.transformTopLeft.position,
+				snapToEdge, this.ehudWindowPos.value.xMin, hudLeft
+			);
+
+			base.DrawGUI();
+
+			Rect hudPos = this.ehudWindow.WindowPos;
+
+			hudPos.xMin = hudLeft;
+			hudPos.width = this.ehudWindow.defaultWindowPos.width;
+
+			this.ehudWindowPos.value = hudPos;
+
+			this.ehudWindow.WindowPos = this.ehudWindowPos;
+
 		}
 	}
 }

--- a/VOID_HUD.cs
+++ b/VOID_HUD.cs
@@ -26,7 +26,7 @@
 // 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 Engineer.VesselSimulator;
+using KerbalEngineer.VesselSimulator;
 using KSP;
 using System;
 using System.Collections.Generic;
@@ -36,47 +36,22 @@
 
 namespace VOID
 {
-	public class VOID_HUD : VOID_Module, IVOID_Module
+	public class VOID_HUD : VOID_HUDModule, IVOID_Module
 	{
 		/*
 		 * Fields
 		 * */
-		[AVOID_SaveValue("colorIndex")]
-		protected VOID_SaveValue<int> _colorIndex;
-
-		protected List<Color> textColors;
-
-		protected Rect leftHUDdefaultPos;
-		protected Rect rightHUDdefaultPos;
-
 		[AVOID_SaveValue("leftHUDPos")]
 		protected VOID_SaveValue<Rect> leftHUDPos;
 		[AVOID_SaveValue("rightHUDPos")]
 		protected VOID_SaveValue<Rect> rightHUDPos;
 
-		[AVOID_SaveValue("positionsLocked")]
-		protected VOID_SaveValue<bool> positionsLocked;
+		protected HUDWindow leftWindow;
+		protected HUDWindow rightWindow;
 
 		/*
 		 * Properties
 		 * */
-		public int ColorIndex
-		{
-			get
-			{
-				return this._colorIndex;
-			}
-			set
-			{
-				if (this._colorIndex >= this.textColors.Count - 1)
-				{
-					this._colorIndex = 0;
-					return;
-				}
-
-				this._colorIndex = value;
-			}
-		}
 
 		/* 
 		 * Methods
@@ -87,29 +62,25 @@
 
 			this.toggleActive = true;
 
-			this._colorIndex = 0;
+			this.leftWindow = new HUDWindow(this.leftHUDWindow, new Rect(Screen.width * .375f - 300f, 0f, 300f, 90f));
+			this.Windows.Add(this.leftWindow);
 
-			this.textColors = new List<Color>();
+			this.leftHUDPos = this.leftWindow.WindowPos;
 
-			this.textColors.Add(Color.green);
-			this.textColors.Add(Color.black);
-			this.textColors.Add(Color.white);
-			this.textColors.Add(Color.red);
-			this.textColors.Add(Color.blue);
-			this.textColors.Add(Color.yellow);
-			this.textColors.Add(Color.gray);
-			this.textColors.Add(Color.cyan);
-			this.textColors.Add(Color.magenta);
+			this.rightWindow = new HUDWindow(this.rightHUDWindow, new Rect(Screen.width * .625f, 0f, 300f, 90f));
+			this.Windows.Add(this.rightWindow);
 
-			this.leftHUDdefaultPos = new Rect(Screen.width * .375f - 300f, 0f, 300f, 90f);
-			this.leftHUDPos = new Rect(this.leftHUDdefaultPos);
-
-			this.rightHUDdefaultPos = new Rect(Screen.width * .625f, 0f, 300f, 90f);
-			this.rightHUDPos = new Rect(this.rightHUDdefaultPos);
-
-			this.positionsLocked = true;
+			this.rightHUDPos = this.rightWindow.WindowPos;
 
 			Tools.PostDebugMessage ("VOID_HUD: Constructed.");
+		}
+
+		public override void DrawGUI()
+		{
+			base.DrawGUI();
+
+			this.leftHUDPos.value = this.leftWindow.WindowPos;
+			this.rightHUDPos.value = this.rightWindow.WindowPos;
 		}
 
 		protected void leftHUDWindow(int id)
@@ -219,52 +190,6 @@
 
 			GUI.BringWindowToBack(id);
 		}
-
-		public override void DrawGUI()
-		{
-			VOID_Styles.labelHud.normal.textColor = textColors [ColorIndex];
-
-			GUI.skin = this.core.Skin;
-
-			if ((TimeWarp.WarpMode == TimeWarp.Modes.LOW) || (TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate))
-			{
-				SimManager.RequestSimulation();
-			}
-
-			this.leftHUDPos.value = GUI.Window(
-				this.core.windowID,
-				this.leftHUDPos,
-				VOID_Tools.GetWindowHandler(this.leftHUDWindow),
-				GUIContent.none,
-				GUIStyle.none
-			);
-
-			this.rightHUDPos.value = GUI.Window(
-				this.core.windowID,
-				this.rightHUDPos,
-				VOID_Tools.GetWindowHandler(this.rightHUDWindow),
-				GUIContent.none,
-				GUIStyle.none
-			);
-		}
-
-		public override void DrawConfigurables()
-		{
-			if (GUILayout.Button (string.Intern("Change HUD color"), GUILayout.ExpandWidth (false)))
-			{
-				++this.ColorIndex;
-			}
-
-			if (GUILayout.Button(string.Intern("Reset HUD Positions"), GUILayout.ExpandWidth(false)))
-			{
-				this.leftHUDPos = new Rect(this.leftHUDdefaultPos);
-				this.rightHUDPos = new Rect(this.rightHUDdefaultPos);
-			}
-
-			this.positionsLocked = GUILayout.Toggle(this.positionsLocked,
-				string.Intern("Lock HUD Positions"),
-				GUILayout.ExpandWidth(false));
-		}
 	}
 }
 

--- a/VOID_HUDAdvanced.cs
+++ b/VOID_HUDAdvanced.cs
@@ -26,7 +26,7 @@
 // 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 Engineer.VesselSimulator;
+using KerbalEngineer.VesselSimulator;
 using KSP;
 using System;
 using System.Collections.Generic;
@@ -37,37 +37,38 @@
 
 namespace VOID
 {
-	public class VOID_HUDAdvanced : VOID_Module, IVOID_Module
+	public class VOID_HUDAdvanced : VOID_HUDModule, IVOID_Module
 	{
 		/*
 		 * Fields
 		 * */
 		protected VOID_HUD primaryHUD;
 
-		protected Rect leftHUDdefaultPos;
-		protected Rect rightHUDdefaultPos;
+		protected HUDWindow leftHUD;
+		protected HUDWindow rightHUD;
 
 		[AVOID_SaveValue("leftHUDPos")]
 		protected VOID_SaveValue<Rect> leftHUDPos;
 		[AVOID_SaveValue("rightHUDPos")]
 		protected VOID_SaveValue<Rect> rightHUDPos;
 
-		[AVOID_SaveValue("positionsLocked")]
-		protected VOID_SaveValue<bool> positionsLocked;
-
 		/*
 		 * Properties
 		 * */
-		public int ColorIndex
+		public override int ColorIndex
 		{
 			get
 			{
-				if (this.primaryHUD == null)
-				{
-					return 0;
-				}
-
-				return this.primaryHUD.ColorIndex;
+				if (this.primaryHUD != null)
+				{
+					return this.primaryHUD.ColorIndex;
+				}
+
+				return base.ColorIndex;
+			}
+			set
+			{
+				base.ColorIndex = value;
 			}
 		}
 
@@ -80,19 +81,23 @@
 
 			this.toggleActive = true;
 
-			this.leftHUDdefaultPos = new Rect(
+			this.leftHUD = new HUDWindow(this.leftHUDWindow, new Rect(
 				Screen.width * .5f - (float)GameSettings.UI_SIZE * .25f - 300f,
 				Screen.height - 200f,
-				300f, 90f
+				300f, 90f)
 			);
-			this.leftHUDPos = new Rect(this.leftHUDdefaultPos);
-
-			this.rightHUDdefaultPos = new Rect(
+			this.Windows.Add(this.leftHUD);
+
+			this.leftHUDPos = this.leftHUD.WindowPos;
+
+			this.rightHUD = new HUDWindow(this.rightHUDWindow, new Rect(
 				Screen.width * .5f + (float)GameSettings.UI_SIZE * .25f,
 				Screen.height - 200f,
-				300f, 90f
+				300f, 90f)
 			);
-			this.rightHUDPos = new Rect(this.rightHUDdefaultPos);
+			this.Windows.Add(this.rightHUD);
+
+			this.rightHUDPos = this.rightHUD.WindowPos;
 
 			this.positionsLocked = true;
 
@@ -182,8 +187,8 @@
 				}
 
 				rightHUD.AppendFormat("Burn Time (Rem/Total): {0} / {1}\n",
-					VOID_Tools.ConvertInterval(VOID_Data.currentNodeBurnRemaining.Value),
-					VOID_Tools.ConvertInterval(VOID_Data.currentNodeBurnDuration.Value)
+					VOID_Tools.FormatInterval(VOID_Data.currentNodeBurnRemaining.Value),
+					VOID_Tools.FormatInterval(VOID_Data.currentNodeBurnDuration.Value)
 				);
 
 				if (VOID_Data.burnTimeDoneAtNode.Value != string.Empty)
@@ -229,32 +234,11 @@
 					}
 				}
 			}
-			else
-			{
-				if ((TimeWarp.WarpMode == TimeWarp.Modes.LOW) || (TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate))
-				{
-					SimManager.RequestSimulation();
-				}
-
-				this.leftHUDPos.value = GUI.Window(
-					this.core.windowID,
-					this.leftHUDPos,
-					VOID_Tools.GetWindowHandler(this.leftHUDWindow),
-					GUIContent.none,
-					GUIStyle.none
-				);
-
-				if (VOID_Data.upcomingManeuverNodes > 0)
-				{
-					this.rightHUDPos.value = GUI.Window(
-						this.core.windowID,
-						this.rightHUDPos,
-						VOID_Tools.GetWindowHandler(this.rightHUDWindow),
-						GUIContent.none,
-						GUIStyle.none
-					);
-				}
-			}
+
+			base.DrawGUI();
+
+			this.leftHUDPos.value = this.leftHUD.WindowPos;
+			this.rightHUDPos.value = this.rightHUD.WindowPos;
 		}
 
 		public override void DrawConfigurables()

file:b/VOID_HUDModule.cs (new)
--- /dev/null
+++ b/VOID_HUDModule.cs
@@ -1,1 +1,167 @@
+// VOID
+//
+// VOID_HUDModule.cs
+//
+// Copyright © 2014, 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 KerbalEngineer.VesselSimulator;
+using KSP;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using ToadicusTools;
+using UnityEngine;
+
+namespace VOID
+{
+	public abstract class VOID_HUDModule : VOID_Module
+	{
+		[AVOID_SaveValue("colorIndex")]
+		protected VOID_SaveValue<int> _colorIndex;
+
+		protected List<Color> textColors;
+
+		[AVOID_SaveValue("positionsLocked")]
+		protected VOID_SaveValue<bool> positionsLocked;
+
+		public virtual int ColorIndex
+		{
+			get
+			{
+				return this._colorIndex;
+			}
+			set
+			{
+				if (this._colorIndex >= this.textColors.Count - 1)
+				{
+					this._colorIndex = 0;
+					return;
+				}
+
+				this._colorIndex = value;
+			}
+		}
+
+		public virtual List<HUDWindow> Windows
+		{
+			get;
+			protected set;
+		}
+
+		public VOID_HUDModule() : base()
+		{
+			this._colorIndex = 0;
+
+			this.textColors = new List<Color>();
+
+			this.textColors.Add(Color.green);
+			this.textColors.Add(Color.black);
+			this.textColors.Add(Color.white);
+			this.textColors.Add(Color.red);
+			this.textColors.Add(Color.blue);
+			this.textColors.Add(Color.yellow);
+			this.textColors.Add(Color.gray);
+			this.textColors.Add(Color.cyan);
+			this.textColors.Add(Color.magenta);
+
+			this.positionsLocked = true;
+
+			this.Windows = new List<HUDWindow>();
+		}
+
+		public override void DrawGUI()
+		{
+			VOID_Styles.labelHud.normal.textColor = textColors [ColorIndex];
+
+			GUI.skin = this.core.Skin;
+
+			if (HighLogic.LoadedSceneIsEditor ||
+				(TimeWarp.WarpMode == TimeWarp.Modes.LOW) || (TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate)
+			)
+			{
+				SimManager.RequestSimulation();
+			}
+
+			foreach (HUDWindow window in this.Windows)
+			{
+				window.WindowPos = GUI.Window(
+					this.core.windowID,
+					window.WindowPos,
+					VOID_Tools.GetWindowHandler(window.WindowFunction),
+					GUIContent.none,
+					GUIStyle.none
+				);
+			}
+		}
+
+		public override void DrawConfigurables()
+		{
+			if (GUILayout.Button (string.Intern("Change HUD color"), GUILayout.ExpandWidth (false)))
+			{
+				++this.ColorIndex;
+			}
+
+			if (GUILayout.Button(string.Intern("Reset HUD Positions"), GUILayout.ExpandWidth(false)))
+			{
+				foreach (HUDWindow window in this.Windows)
+				{
+					window.WindowPos = new Rect(window.defaultWindowPos);
+				}
+			}
+
+			this.positionsLocked = GUILayout.Toggle(this.positionsLocked,
+				string.Intern("Lock HUD Positions"),
+				GUILayout.ExpandWidth(false));
+		}
+	}
+
+	public class HUDWindow
+	{
+		public readonly Rect defaultWindowPos;
+
+		public Action<int> WindowFunction
+		{
+			get;
+			private set;
+		}
+
+		public Rect WindowPos
+		{
+			get;
+			set;
+		}
+
+		private HUDWindow() {}
+
+		public HUDWindow(Action<int> windowFunc, Rect defaultPos)
+		{
+			this.WindowFunction = windowFunc;
+			this.defaultWindowPos = defaultPos;
+			this.WindowPos = new Rect(this.defaultWindowPos);
+		}
+	}
+}
+
+

--- a/VOID_Module.cs
+++ b/VOID_Module.cs
@@ -253,7 +253,6 @@
 			switch (HighLogic.LoadedScene)
 			{
 				case GameScenes.EDITOR:
-				case GameScenes.SPH:
 					if (cursorInWindow)
 					{
 						InputLockManager.SetControlLock(

--- a/VOID_Rendezvous.cs
+++ b/VOID_Rendezvous.cs
@@ -205,7 +205,7 @@
 					{
 						GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 						GUILayout.Label("Period:");
-						GUILayout.Label(VOID_Tools.ConvertInterval(v.orbit.period), GUILayout.ExpandWidth(false));
+						GUILayout.Label(VOID_Tools.FormatInterval(v.orbit.period), GUILayout.ExpandWidth(false));
 						GUILayout.EndHorizontal();
 
 						GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));

--- a/VOID_StageInfo.cs
+++ b/VOID_StageInfo.cs
@@ -3,7 +3,7 @@
 // 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/
 
-using Engineer.VesselSimulator;
+using KerbalEngineer.VesselSimulator;
 using KSP;
 using System;
 using System.Collections.Generic;
@@ -99,7 +99,7 @@
 				(TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate)
 			)
 			{
-				Engineer.VesselSimulator.SimManager.RequestSimulation();
+				KerbalEngineer.VesselSimulator.SimManager.RequestSimulation();
 			}
 
 			if (!this.stylesApplied)

--- a/VOID_Styles.cs
+++ b/VOID_Styles.cs
@@ -63,6 +63,12 @@
 			private set;
 		}
 
+		public static GUIStyle labelGreen
+		{
+			get;
+			private set;
+		}
+
 		public static GUIStyle labelHud
 		{
 			get;
@@ -106,7 +112,9 @@
 
 			labelRed = new GUIStyle(GUI.skin.label);
 			labelRed.normal.textColor = Color.red;
-			labelRed.alignment = TextAnchor.MiddleCenter;
+
+			labelGreen = new GUIStyle(GUI.skin.label);
+			labelGreen.normal.textColor = Color.green;
 
 			Ready = true;
 		}

--- a/VOID_TWR.cs
+++ b/VOID_TWR.cs
@@ -27,7 +27,7 @@
 				(TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate)
 			)
 			{
-				Engineer.VesselSimulator.SimManager.RequestSimulation();
+				KerbalEngineer.VesselSimulator.SimManager.RequestSimulation();
 			}
 
 			GUILayout.BeginVertical();

--- a/VOID_Tools.cs
+++ b/VOID_Tools.cs
@@ -95,13 +95,13 @@
 		#endregion
 
 		#region VESSEL_EXTENSIONS_SCIENCE
-		public static CBAttributeMap.MapAttribute GetBiome(this Vessel vessel)
-		{
-			CBAttributeMap.MapAttribute mapAttribute;
+		public static CBAttributeMapSO.MapAttribute GetBiome(this Vessel vessel)
+		{
+			CBAttributeMapSO.MapAttribute mapAttribute;
 
 			try
 			{
-				CBAttributeMap BiomeMap = vessel.mainBody.BiomeMap;
+				CBAttributeMapSO BiomeMap = vessel.mainBody.BiomeMap;
 
 				double lat = vessel.latitude * Math.PI / 180d;
 				double lon = vessel.longitude * Math.PI / 180d;
@@ -162,7 +162,7 @@
 			}
 			catch (NullReferenceException)
 			{
-				mapAttribute = new CBAttributeMap.MapAttribute();
+				mapAttribute = new CBAttributeMapSO.MapAttribute();
 				mapAttribute.name = "N/A";
 			}
 
@@ -335,7 +335,7 @@
 						func(id);
 					}
 					#if DEBUG
-					catch (ArgumentException ex)
+					catch (ArgumentException)
 					#else
 					catch (ArgumentException)
 					#endif
@@ -366,78 +366,171 @@
 		}
 
 		/// <summary>
-		/// Converts the interval given in seconds to a human-friendly
-		/// time period in [years], [days], hours, minutes, and seconds.
+		/// Formats the interval given in seconds as a human-friendly
+		/// time period in [[[[years, ]days, ]hours, ]minutes, and ]seconds.
 		/// 
 		/// Uses sidereal days, since "6 hours per day" is the Kerbal standard.
 		/// </summary>
 		/// <returns>Human readable interval</returns>
 		/// <param name="seconds"></param>
-		public static string ConvertInterval(double seconds)
-		{
-			double SecondsPerMinute = 60d;
-			double SecondsPerHour = 3600d;
-			double SecondsPerDay;
-			double SecondsPerYear;
-
-			if (GameSettings.KERBIN_TIME)
-			{
-				SecondsPerDay = 21600d;
-				SecondsPerYear = 9203545d;
-			}
-			else
-			{
-				SecondsPerDay = 86164.1d;
-				SecondsPerYear = 31558149d;
-			}
-
-			int years;
-			int days;
-			int hours;
-			int minutes;
-
-			years = (int)(seconds / SecondsPerYear);
-
-			seconds %= SecondsPerYear;
-
-			days = (int)(seconds / SecondsPerDay);
-
-			seconds %= SecondsPerDay;
-
-			hours = (int)(seconds / SecondsPerHour);
-
-			seconds %= SecondsPerHour;
-
-			minutes = (int)(seconds / SecondsPerMinute);
-
-			seconds %= SecondsPerMinute;
-
-			string format_1 = string.Intern("{0:D1}y {1:D1}d {2:D2}h {3:D2}m {4:00.0}s");
-			string format_2 = string.Intern("{0:D1}d {1:D2}h {2:D2}m {3:00.0}s");
-			string format_3 = string.Intern("{0:D2}h {1:D2}m {2:00.0}s");
-			string format_4 = string.Intern("{0:D2}m {1:00.0}s");
-			string format_5 = string.Intern("{0:00.0}s");
-
-			if (years > 0)
-			{
-				return string.Format(format_1, years, days, hours, minutes, seconds);
-			}
-			else if (days > 0)
-			{
-				return string.Format(format_2, days, hours, minutes, seconds);
-			}
-			else if (hours > 0)
-			{
-				return string.Format(format_3, hours, minutes, seconds);
-			}
-			else if (minutes > 0)
-			{
-				return string.Format(format_4, minutes, seconds);
-			}
-			else
-			{
-				return string.Format(format_5, seconds);
-			}
+		public static string FormatInterval(double seconds)
+		{
+			return UnpackedTime.FromSeconds(seconds).FormatAsSpan();
+		}
+
+		/// <summary>
+		/// Formats the date given in seconds since epoch as a human-friendly
+		/// date in the format YY, DD, HH:MM:SS
+		/// </summary>
+		/// <returns>The date.</returns>
+		/// <param name="seconds">Seconds.</param>
+		public static string FormatDate(double seconds)
+		{
+			return UnpackedTime.FromSeconds(seconds).FormatAsDate();
+		}
+
+		public class UnpackedTime
+		{
+			public const double SecondsPerMinute = 60d;
+			public const double SecondsPerHour = 3600d;
+
+			public static double SecondsPerDay
+			{
+				get
+				{
+					if (GameSettings.KERBIN_TIME)
+					{
+						return 21600d;
+					}
+					else
+					{
+						return 86164.1d;
+					}
+				}
+			}
+
+			public static double SecondsPerYear
+			{
+				get
+				{
+					if (GameSettings.KERBIN_TIME)
+					{
+						return 9203545d;
+					}
+					else
+					{
+						return 31558149d;
+					}
+				}
+			}
+
+			public static UnpackedTime FromSeconds(double seconds)
+			{
+				UnpackedTime time = new UnpackedTime();
+
+				time.years = (int)(seconds / SecondsPerYear);
+
+				seconds %= SecondsPerYear;
+
+				time.days = (int)(seconds / SecondsPerDay);
+
+				seconds %= SecondsPerDay;
+
+				time.hours = (int)(seconds / SecondsPerHour);
+
+				seconds %= SecondsPerHour;
+
+				time.minutes = (int)(seconds / SecondsPerMinute);
+
+				seconds %= SecondsPerMinute;
+
+				time.seconds = seconds;
+
+				return time;
+			}
+
+			public static explicit operator UnpackedTime(double seconds)
+			{
+				return FromSeconds(seconds);
+			}
+
+			public static implicit operator double(UnpackedTime time)
+			{
+				return time.ToSeconds();
+			}
+
+			public static UnpackedTime operator+ (UnpackedTime lhs, UnpackedTime rhs)
+			{
+				return FromSeconds(lhs.ToSeconds() + rhs.ToSeconds());
+			}
+
+			public static UnpackedTime operator- (UnpackedTime lhs, UnpackedTime rhs)
+			{
+				return FromSeconds(lhs.ToSeconds() - rhs.ToSeconds());
+			}
+
+			public int years;
+			public int days;
+			public int hours;
+			public int minutes;
+			public double seconds;
+
+			public double ToSeconds()
+			{
+				return (double)years * SecondsPerYear +
+					(double)days * SecondsPerDay +
+					(double)hours * SecondsPerHour +
+					(double)minutes * SecondsPerMinute +
+					seconds;
+			}
+
+			public string FormatAsSpan()
+			{
+				string format_1 = "{0:D1}y {1:D1}d {2:D2}h {3:D2}m {4:00.0}s";
+				string format_2 = "{0:D1}d {1:D2}h {2:D2}m {3:00.0}s";
+				string format_3 = "{0:D2}h {1:D2}m {2:00.0}s";
+				string format_4 = "{0:D2}m {1:00.0}s";
+				string format_5 = "{0:00.0}s";
+
+				if (this.years > 0)
+				{
+					return string.Format(format_1, this.years, this.days, this.hours, this.minutes, this.seconds);
+				}
+				else if (this.days > 0)
+				{
+					return string.Format(format_2, this.days, this.hours, this.minutes, this.seconds);
+				}
+				else if (this.hours > 0)
+				{
+					return string.Format(format_3, this.hours, this.minutes, this.seconds);
+				}
+				else if (this.minutes > 0)
+				{
+					return string.Format(format_4, this.minutes, this.seconds);
+				}
+				else
+				{
+					return string.Format(format_5, this.seconds);
+				}
+			}
+
+			public string FormatAsDate()
+			{
+				string format = "Y{0:D1}, D{1:D1} {2:D2}:{3:D2}:{4:00.0}s";
+
+				return string.Format(format, years, days, hours, minutes, seconds);
+			}
+
+			public UnpackedTime(int years, int days, int hours, int minutes, double seconds)
+			{
+				this.years = years;
+				this.days = days;
+				this.hours = hours;
+				this.minutes = minutes;
+				this.seconds = seconds;
+			}
+
+			public UnpackedTime() : this(0, 0, 0, 0, 0d) {}
 		}
 
 		public static string UppercaseFirst(string s)

--- a/VOID_VesselInfo.cs
+++ b/VOID_VesselInfo.cs
@@ -26,8 +26,8 @@
 // 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 Engineer.VesselSimulator;
-using Engineer.Extensions;
+using KerbalEngineer.VesselSimulator;
+using KerbalEngineer.Extensions;
 using KSP;
 using System;
 using System.Collections.Generic;