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.11.0.*")]
+[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
@@ -9,6 +9,7 @@
     <RootNamespace>VOID</RootNamespace>
     <AssemblyName>VOID</AssemblyName>
     <CodePage>65001</CodePage>
+    <UseMSBuildEngine>False</UseMSBuildEngine>
     <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <ReleaseVersion>0.11</ReleaseVersion>
@@ -94,6 +95,12 @@
     <Compile Include="VOID_Localization.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="VOID_HUDAdvanced.cs" />
+    <Compile Include="VOID_TWR.cs" />
+    <Compile Include="VOID_CareerStatus.cs" />
+    <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
@@ -51,6 +51,9 @@
 		[AVOID_SaveValue("togglePhysical")]
 		protected VOID_SaveValue<bool> togglePhysical = false;
 
+		[AVOID_SaveValue("toggleScience")]
+		protected VOID_SaveValue<bool> toggleScience = false;
+
 		public VOID_CBInfoBrowser()
 		{
 			this._Name = "Celestial Body Information Browser";
@@ -72,20 +75,20 @@
 
 			GUILayout.BeginVertical(GUILayout.Width(150));
 
-			selectedBody1 = VOID_Core.Instance.allBodies[selectedBodyIdx1];
-			selectedBody2 = VOID_Core.Instance.allBodies[selectedBodyIdx2];
+			selectedBody1 = this.core.allBodies[selectedBodyIdx1];
+			selectedBody2 = this.core.allBodies[selectedBodyIdx2];
 
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 			if (GUILayout.Button("<", GUILayout.ExpandWidth(false)))
 			{
 				selectedBodyIdx1--;
-				if (selectedBodyIdx1 < 0) selectedBodyIdx1 = VOID_Core.Instance.allBodies.Count - 1;
-			}
-			GUILayout.Label(VOID_Core.Instance.allBodies[selectedBodyIdx1].bodyName, VOID_Core.Instance.LabelStyles["center_bold"], GUILayout.ExpandWidth(true));
+				if (selectedBodyIdx1 < 0) selectedBodyIdx1 = this.core.allBodies.Count - 1;
+			}
+			GUILayout.Label(this.core.allBodies[selectedBodyIdx1].bodyName, VOID_Styles.labelCenterBold, GUILayout.ExpandWidth(true));
 			if (GUILayout.Button(">", GUILayout.ExpandWidth(false)))
 			{
 				selectedBodyIdx1++;
-				if (selectedBodyIdx1 > VOID_Core.Instance.allBodies.Count - 1) selectedBodyIdx1 = 0;
+				if (selectedBodyIdx1 > this.core.allBodies.Count - 1) selectedBodyIdx1 = 0;
 			}
 			GUILayout.EndHorizontal();
 			GUILayout.EndVertical();
@@ -95,13 +98,13 @@
 			if (GUILayout.Button("<", GUILayout.ExpandWidth(false)))
 			{
 				selectedBodyIdx2--;
-				if (selectedBodyIdx2 < 0) selectedBodyIdx2 = VOID_Core.Instance.allBodies.Count - 1;
-			}
-			GUILayout.Label(VOID_Core.Instance.allBodies[selectedBodyIdx2].bodyName, VOID_Core.Instance.LabelStyles["center_bold"], GUILayout.ExpandWidth(true));
+				if (selectedBodyIdx2 < 0) selectedBodyIdx2 = this.core.allBodies.Count - 1;
+			}
+			GUILayout.Label(this.core.allBodies[selectedBodyIdx2].bodyName, VOID_Styles.labelCenterBold, GUILayout.ExpandWidth(true));
 			if (GUILayout.Button(">", GUILayout.ExpandWidth(false)))
 			{
 				selectedBodyIdx2++;
-				if (selectedBodyIdx2 > VOID_Core.Instance.allBodies.Count - 1) selectedBodyIdx2 = 0;
+				if (selectedBodyIdx2 > this.core.allBodies.Count - 1) selectedBodyIdx2 = 0;
 			}
 			GUILayout.EndHorizontal();
 			GUILayout.EndVertical();
@@ -208,87 +211,153 @@
 				GUILayout.EndHorizontal();
 			}
 
+			if (GUILayout.Button("Scientific Parameters", GUILayout.ExpandWidth(true)))
+			{
+				toggleScience.value = !toggleScience;
+			}
+
+			if (toggleScience)
+			{
+				GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
+
+				//begin physical info value label column
+				GUILayout.BeginVertical(GUILayout.Width(150));
+
+
+				/*
+				 *  public float RecoveryValue = 1f;
+
+					public float InSpaceHighDataValue = 1f;
+
+					public float spaceAltitudeThreshold = 250000f;
+
+					public float flyingAltitudeThreshold = 18000f;
+
+					public float InSpaceLowDataValue = 1f;
+
+					public float SplashedDataValue = 1f;
+
+					public float LandedDataValue = 1f;
+
+					public float FlyingHighDataValue = 1f;
+
+					public float FlyingLowDataValue = 1f;
+					*/
+
+				GUILayout.Label("Surface Multiplier:");
+				GUILayout.Label("Ocean Multiplier:");
+				GUILayout.Label("Flying-Low Multiplier:");
+				GUILayout.Label("Flying-High Multiplier:");
+				GUILayout.Label("Low Orbit Multiplier:");
+				GUILayout.Label("High Orbit Multiplier:");
+				GUILayout.Label("'Flying-High' Altitude:");
+				GUILayout.Label("'High Orbit' Altitude:");
+				GUILayout.Label("Recovery Multiplier:");
+
+				//end physical info value label column
+				GUILayout.EndVertical();
+
+				//begin primary physical values column
+				GUILayout.BeginVertical(GUILayout.Width(150));
+
+				this.cbColumnScience(selectedBody1);
+
+				//end primary physical column
+				GUILayout.EndVertical();
+
+				//begin secondary physical values column
+				GUILayout.BeginVertical(GUILayout.Width(150));
+
+				this.cbColumnScience(selectedBody2);
+
+				//end target physical values column
+				GUILayout.EndVertical();
+
+				//end physical value horizontal chunk
+				GUILayout.EndHorizontal();
+			}
+
 			GUI.DragWindow();
 		}
 
 		private void body_OP_show_orbital_info(CelestialBody body)
 		{
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label((body.orbit.ApA / 1000).ToString("##,#") + "km", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(VOID_Tools.ConvertInterval(body.orbit.timeToAp), VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label((body.orbit.PeA / 1000).ToString("##,#") + "km", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(VOID_Tools.ConvertInterval(body.orbit.timeToPe), VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label((body.orbit.semiMajorAxis / 1000).ToString("##,#") + "km", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(body.orbit.eccentricity.ToString("F4") + "", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(VOID_Tools.ConvertInterval(body.orbit.period), VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(VOID_Tools.ConvertInterval(body.rotationPeriod), VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label((body.orbit.orbitalSpeed / 1000).ToString("F2") + "km/s", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+		    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.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.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));
+
+		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+		    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.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));
 
 			// Toadicus edit: convert mean anomaly into degrees.
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label((body.orbit.meanAnomaly * 180d / Math.PI).ToString("F3") + "°", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(body.orbit.trueAnomaly.ToString("F3") + "°", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+		    else GUILayout.Label((body.orbit.meanAnomaly * 180d / Math.PI).ToString("F3") + "°", 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.trueAnomaly.ToString("F3") + "°", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 			// Toadicus edit: convert eccentric anomaly into degrees.
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label((body.orbit.eccentricAnomaly * 180d / Math.PI).ToString("F3") + "°", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(body.orbit.inclination.ToString("F3") + "°", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(body.orbit.LAN.ToString("F3") + "°", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-		    else GUILayout.Label(body.orbit.argumentOfPeriapsis.ToString("F3") + "°", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+		    else GUILayout.Label((body.orbit.eccentricAnomaly * 180d / Math.PI).ToString("F3") + "°", 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.inclination.ToString("F3") + "°", 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.LAN.ToString("F3") + "°", 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.argumentOfPeriapsis.ToString("F3") + "°", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+
+		    if (body.bodyName == "Sun") GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 		    else
 		    {
 		        string body_tidally_locked = "No";
 		        if (body.tidallyLocked) body_tidally_locked = "Yes";
-		        GUILayout.Label(body_tidally_locked, VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+		        GUILayout.Label(body_tidally_locked, VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 		    }
 		}
 
 		private void body_OP_show_physical_info(CelestialBody body)
 		{
 
-			GUILayout.Label((body.Radius / 1000).ToString("##,#") + "km", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-			GUILayout.Label((((body.Radius * body.Radius) * 4 * Math.PI) / 1000).ToString("0.00e+00") + "km²", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+			GUILayout.Label((body.Radius / 1000).ToString("##,#") + "km", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+
+			GUILayout.Label((((body.Radius * body.Radius) * 4 * Math.PI) / 1000).ToString("0.00e+00") + "km²", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 			// divide by 1000 to convert m to km
-			GUILayout.Label((((4d / 3) * Math.PI * (body.Radius * body.Radius * body.Radius)) / 1000).ToString("0.00e+00") + "km³", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-			GUILayout.Label(body.Mass.ToString("0.00e+00") + "kg", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+			GUILayout.Label((((4d / 3) * Math.PI * (body.Radius * body.Radius * body.Radius)) / 1000).ToString("0.00e+00") + "km³", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+
+			GUILayout.Label(body.Mass.ToString("0.00e+00") + "kg", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 			double p = body.Mass / ((body.Radius * body.Radius * body.Radius) * (4d / 3) * Math.PI);
 
-			GUILayout.Label(p.ToString("##,#") + "kg/m³", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-			if (body.bodyName == "Sun") GUILayout.Label(Tools.MuMech_ToSI(body.sphereOfInfluence), VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-			else GUILayout.Label(Tools.MuMech_ToSI(body.sphereOfInfluence), VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-
-			GUILayout.Label(body.orbitingBodies.Count.ToString(), VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(p.ToString("##,#") + "kg/m³", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+
+			if (body.bodyName == "Sun") GUILayout.Label(Tools.MuMech_ToSI(body.sphereOfInfluence), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+			else GUILayout.Label(Tools.MuMech_ToSI(body.sphereOfInfluence), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+
+			GUILayout.Label(body.orbitingBodies.Count.ToString(), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 			//show # artificial satellites
 			int num_art_sats = 0;
@@ -297,31 +366,87 @@
 				if (v.mainBody == body && v.situation.ToString() == "ORBITING") num_art_sats++;
 			}
 
-			GUILayout.Label(num_art_sats.ToString(), VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(num_art_sats.ToString(), VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 			double g_ASL = (VOID_Core.Constant_G * body.Mass) / (body.Radius * body.Radius);
 
-			GUILayout.Label(Tools.MuMech_ToSI(g_ASL) + "m/s²", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(Tools.MuMech_ToSI(g_ASL) + "m/s²", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 
 			if (body.atmosphere)
 			{
 				GUILayout.Label("≈ " + Tools.MuMech_ToSI(body.maxAtmosphereAltitude) + "m",
-					VOID_Core.Instance.LabelStyles["right"],
+					VOID_Styles.labelRight,
 					GUILayout.ExpandWidth(true));
 
 				string O2 = "No";
 				if (body.atmosphereContainsOxygen == true) O2 = "Yes";
-				GUILayout.Label(O2, VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+				GUILayout.Label(O2, VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 			}
 			else
 			{
-				GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
-				GUILayout.Label("N/A", VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+				GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+				GUILayout.Label("N/A", VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
 			}
 
 			string ocean = "No";
 			if (body.ocean == true) ocean = "Yes";
-			GUILayout.Label(ocean, VOID_Core.Instance.LabelStyles["right"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(ocean, VOID_Styles.labelRight, GUILayout.ExpandWidth(true));
+		}
+
+		private void cbColumnScience(CelestialBody body)
+		{
+			/*GUILayout.Label("Surface Science Multiplier:");
+			GUILayout.Label("Ocean Science Multiplier:");
+			GUILayout.Label("Low-Atmosphere Science Multiplier:");
+			GUILayout.Label("High-Atmosphere Science Multiplier:");
+			GUILayout.Label("Low Orbit Science Multiplier:");
+			GUILayout.Label("High Orbit Science Multiplier:");
+			GUILayout.Label("'In Space' Altitude:");
+			GUILayout.Label("'Flying' Altitude:");
+			GUILayout.Label("Recovery Multiplier:");*/
+
+			var scienceValues = body.scienceValues;
+
+			GUILayout.Label(scienceValues.LandedDataValue.ToString("0.0#"),
+				VOID_Styles.labelRight,
+				GUILayout.ExpandWidth(true));
+
+			GUILayout.Label(
+				body.ocean ? scienceValues.SplashedDataValue.ToString("0.0#") : "N/A",
+				VOID_Styles.labelRight,
+				GUILayout.ExpandWidth(true));
+
+			GUILayout.Label(
+				body.atmosphere ? scienceValues.FlyingLowDataValue.ToString("0.0#") : "N/A",
+				VOID_Styles.labelRight,
+				GUILayout.ExpandWidth(true));
+
+			GUILayout.Label(
+				body.atmosphere ? scienceValues.FlyingHighDataValue.ToString("0.0#") : "N/A",
+				VOID_Styles.labelRight,
+				GUILayout.ExpandWidth(true));
+
+			GUILayout.Label(scienceValues.InSpaceLowDataValue.ToString("0.0#"),
+				VOID_Styles.labelRight,
+				GUILayout.ExpandWidth(true));
+
+			GUILayout.Label(scienceValues.InSpaceHighDataValue.ToString("0.0#"),
+				VOID_Styles.labelRight,
+				GUILayout.ExpandWidth(true));
+
+			GUILayout.Label(
+				body.atmosphere ? scienceValues.flyingAltitudeThreshold.ToString("N0") : "N/A",
+				VOID_Styles.labelRight,
+				GUILayout.ExpandWidth(true));
+
+			GUILayout.Label(
+				scienceValues.spaceAltitudeThreshold.ToString("N0"),
+				VOID_Styles.labelRight,
+				GUILayout.ExpandWidth(true));
+
+			GUILayout.Label(scienceValues.RecoveryValue.ToString("0.0#"),
+				VOID_Styles.labelRight,
+				GUILayout.ExpandWidth(true));
 		}
 	}
 }

--- /dev/null
+++ b/VOID_CareerStatus.cs
@@ -1,1 +1,241 @@
-
+// VOID
+//
+// VOID_CareerStatus.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 KSP;
+using System;
+using System.Text;
+using ToadicusTools;
+using UnityEngine;
+
+namespace VOID
+{
+	public class VOID_CareerStatus : VOID_WindowModule
+	{
+		public static VOID_CareerStatus Instance
+		{
+			get;
+			private set;
+		}
+
+		public static string formatDelta(double delta)
+		{
+			if (delta > 0)
+			{
+				return string.Format("<color='green'>{0:#,#.##}↑</color>", delta);
+			}
+			else if (delta < 0)
+			{
+				return string.Format("<color='red'>{0:#,#.##}↓</color>", delta);
+			}
+			else
+			{
+				return string.Intern("0");
+			}
+		}
+
+		public static string formatDelta(float delta)
+		{
+			return formatDelta((double)delta);
+		}
+
+		private GUIContent fundsContent;
+		private GUIContent repContent;
+		private GUIContent scienceContent;
+
+		private Texture2D fundsIconGreen;
+		private Texture2D fundsIconRed;
+		private Texture2D reputationIconGreen;
+		private Texture2D reputationIconRed;
+		private Texture2D scienceIcon;
+
+		public override bool toggleActive
+		{
+			get
+			{
+				switch (HighLogic.CurrentGame.Mode)
+				{
+					case Game.Modes.CAREER:
+					case Game.Modes.SCIENCE_SANDBOX:
+						return base.toggleActive;
+					default:
+						return false;
+				}
+			}
+			set
+			{
+				switch (HighLogic.CurrentGame.Mode)
+				{
+					case Game.Modes.CAREER:
+					case Game.Modes.SCIENCE_SANDBOX:
+						base.toggleActive = value;
+						break;
+					default:
+						return;
+				}
+			}
+		}
+
+		public double lastFundsChange
+		{
+			get;
+			private set;
+		}
+
+		public float lastRepChange
+		{
+			get;
+			private set;
+		}
+
+		public float lastScienceChange
+		{
+			get;
+			private set;
+		}
+
+		public double currentFunds
+		{
+			get;
+			private set;
+		}
+
+		public float currentReputation
+		{
+			get;
+			private set;
+		}
+
+		public float currentScience
+		{
+			get;
+			private set;
+		}
+
+		public override void ModuleWindow(int _)
+		{
+			GUILayout.BeginVertical();
+
+			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
+			GUILayout.Label(VOID_Data.fundingStatus.Label);
+			GUILayout.FlexibleSpace();
+			this.fundsContent.text = VOID_Data.fundingStatus.Value;
+			GUILayout.Label(this.fundsContent, GUILayout.ExpandWidth(false));
+			GUILayout.EndHorizontal();
+
+			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
+			GUILayout.Label(VOID_Data.reputationStatus.Label);
+			GUILayout.FlexibleSpace();
+			this.repContent.text = VOID_Data.reputationStatus.Value;
+			GUILayout.Label(this.repContent, GUILayout.ExpandWidth(false));
+			GUILayout.EndHorizontal();
+
+			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
+			GUILayout.Label(VOID_Data.scienceStatus.Label);
+			GUILayout.FlexibleSpace();
+			this.scienceContent.text = VOID_Data.scienceStatus.Value;
+			GUILayout.Label(this.scienceContent, GUILayout.ExpandWidth(false));
+			GUILayout.EndHorizontal();
+
+			GUILayout.EndVertical();
+
+			GUI.DragWindow();
+		}
+
+		// TODO: Update event handlers to do something useful with the new "reasons" parameter.
+		private void onFundsChange(double newValue, TransactionReasons reasons)
+		{
+			this.lastFundsChange = newValue - this.currentFunds;
+			this.currentFunds = newValue;
+		}
+
+		private void onRepChange(float newValue, TransactionReasons reasons)
+		{
+			this.lastRepChange = newValue - this.currentReputation;
+			this.currentReputation = newValue;
+		}
+
+		private void onScienceChange(float newValue, TransactionReasons reasons)
+		{
+			this.lastScienceChange = newValue - this.currentScience;
+			this.currentScience = newValue;
+		}
+
+		/*
+		 *  MissionRecoveryDialog::fundsIconGreen.name: UiElements_05
+         *  MissionRecoveryDialog::fundsIconRed.name: UiElements_06
+         *  MissionRecoveryDialog::reputationIconGreen.name: UiElements_07
+         *  MissionRecoveryDialog::reputationIconRed.name: UiElements_08
+         *  MissionRecoveryDialog::scienceIcon.name: UiElements_12
+		 * */
+		public VOID_CareerStatus() : base()
+		{
+			VOID_CareerStatus.Instance = this;
+
+			this._Name = "Career Status";
+
+			GameEvents.OnFundsChanged.Add(this.onFundsChange);
+			GameEvents.OnReputationChanged.Add(this.onRepChange);
+			GameEvents.OnScienceChanged.Add(this.onScienceChange);
+
+			bool texturesLoaded;
+
+			texturesLoaded = IOTools.LoadTexture(out this.fundsIconGreen, "VOID/Textures/fundsgreen.png", 10, 18);
+			texturesLoaded &= IOTools.LoadTexture(out this.fundsIconRed, "VOID/Textures/fundsred.png", 10, 18);
+			texturesLoaded &= IOTools.LoadTexture(out this.reputationIconGreen, "VOID/Textures/repgreen.png", 16, 18);
+			texturesLoaded &= IOTools.LoadTexture(out this.reputationIconRed, "VOID/Textures/repred.png", 16, 18);
+			texturesLoaded &= IOTools.LoadTexture(out this.scienceIcon, "VOID/Textures/science.png", 16, 18);
+
+			this.fundsContent = new GUIContent();
+			this.repContent = new GUIContent();
+			this.scienceContent = new GUIContent();
+
+			if (texturesLoaded)
+			{
+				this.fundsContent.image = this.fundsIconGreen;
+				this.repContent.image = this.reputationIconGreen;
+				this.scienceContent.image = this.scienceIcon;
+			}
+
+			this.currentFunds = Funding.Instance != null ? Funding.Instance.Funds : double.NaN;
+			this.currentReputation = Reputation.Instance != null ? Reputation.Instance.reputation : float.NaN;
+			this.currentScience = ResearchAndDevelopment.Instance != null ?
+				ResearchAndDevelopment.Instance.Science : float.NaN;
+		}
+
+		~VOID_CareerStatus()
+		{
+			GameEvents.OnFundsChanged.Remove(this.onFundsChange);
+			GameEvents.OnReputationChanged.Remove(this.onRepChange);
+			GameEvents.OnScienceChanged.Remove(this.onScienceChange);
+
+			VOID_CareerStatus.Instance = null;
+		}
+	}
+}
+
+

--- a/VOID_Core.cs
+++ b/VOID_Core.cs
@@ -26,11 +26,12 @@
 // 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;
 using System.Linq;
+using System.Text;
 using ToadicusTools;
 using UnityEngine;
 
@@ -70,18 +71,19 @@
 		public static void Reset()
 		{
 			_instance.StopGUI();
+			_instance.Dispose();
 			_instance = null;
 			_initialized = false;
 		}
 		#endregion
 
-		public static double Constant_G = 6.674e-11;
+		public static readonly double Constant_G = 6.674e-11;
 
 		/*
 		 * Fields
 		 * */
 		protected string VoidName = "VOID";
-		protected string VoidVersion = "0.11.0";
+		protected string VoidVersion;
 
 		protected bool _factoryReset = false;
 
@@ -118,9 +120,8 @@
 		protected int _windowID = 0;
 
 		protected bool GUIStylesLoaded = false;
-		protected Dictionary<string, GUIStyle> _LabelStyles = new Dictionary<string, GUIStyle>();
-
-		protected CelestialBody _Kerbin;
+
+		protected CelestialBody _homeBody;
 
 		[AVOID_SaveValue("togglePower")]
 		public VOID_SaveValue<bool> togglePower = true;
@@ -165,7 +166,8 @@
 				"ExpRecoveryDialogSkin",
 				"KSP window 5",
 				"KSP window 6",
-				"PartTooltipSkin"
+				"PartTooltipSkin",
+				"KSCContextMenuSkin"
 			};
 		protected bool skinsLoaded = false;
 
@@ -174,6 +176,14 @@
 		[AVOID_SaveValue("UseBlizzyToolbar")]
 		protected VOID_SaveValue<bool> _UseToolbarManager;
 		internal IButton ToolbarButton;
+
+		internal ApplicationLauncherButton AppLauncherButton;
+
+		/*
+		 * Events
+		 * */
+		public delegate void VOIDEventHandler(object sender);
+		public event VOIDEventHandler onApplicationQuit;
 
 		/*
 		 * Properties
@@ -225,14 +235,6 @@
 			}
 		}
 
-		public Dictionary<string, GUIStyle> LabelStyles
-		{
-			get
-			{
-				return this._LabelStyles;
-			}
-		}
-
 		public List<CelestialBody> allBodies
 		{
 			get
@@ -241,19 +243,25 @@
 			}
 		}
 
-		public CelestialBody Kerbin
-		{
-			get
-			{
-				if (this._Kerbin == null)
-				{
-					if (FlightGlobals.Bodies != null)
+		public List<CelestialBody> sortedBodyList
+		{
+			get;
+			private set;
+		}
+
+		public CelestialBody HomeBody
+		{
+			get
+			{
+				if (this._homeBody == null)
+				{
+					if (Planetarium.fetch != null)
 					{
-						this._Kerbin = FlightGlobals.Bodies.First(b => b.name == "Kerbin");
+						this._homeBody = Planetarium.fetch.Home;
 					}
 				}
 
-				return this._Kerbin;
+				return this._homeBody;
 			}
 		}
 
@@ -279,6 +287,18 @@
 			{
 				return this._updatePeriod;
 			}
+		}
+
+		public Stage[] Stages
+		{
+			get;
+			protected set;
+		}
+
+		public Stage LastStage
+		{
+			get;
+			protected set;
 		}
 
 		protected IconState powerState
@@ -333,10 +353,24 @@
 				}
 				if (value == true)
 				{
+					if (this.AppLauncherButton != null)
+					{
+						ApplicationLauncher.Instance.RemoveModApplication(this.AppLauncherButton);
+						this.AppLauncherButton = null;
+					}
+
 					this.InitializeToolbarButton();
 				}
 
 				_UseToolbarManager.value = value;
+			}
+		}
+
+		protected virtual ApplicationLauncher.AppScenes appIconVisibleScenes
+		{
+			get
+			{
+				return ApplicationLauncher.AppScenes.FLIGHT;
 			}
 		}
 
@@ -362,17 +396,33 @@
 			if (!this.GUIStylesLoaded)
 			{
 				this.LoadGUIStyles();
+
+				Tools.PostDebugMessage(
+					this,
+					"ToolbarAvailable: {0}, UseToobarManager: {1}",
+					ToolbarManager.ToolbarAvailable,
+					this.UseToolbarManager);
 			}
 
 			if (!this.UseToolbarManager)
 			{
-				if (GUI.Button(VOIDIconPos, VOIDIconTexture, this.iconStyle) && this.VOIDIconLocked)
-				{
-					this.ToggleMainWindow();
+				if (this.AppLauncherButton == null)
+				{
+					Tools.PostDebugMessage(this,
+						"UseToolbarManager = false (ToolbarAvailable = {0}) and " +
+						"AppLauncherButton is null, making AppLauncher button.",
+						ToolbarManager.ToolbarAvailable
+					);
+					this.InitializeAppLauncherButton();
 				}
 			}
 			else if (this.ToolbarButton == null)
 			{
+				Tools.PostDebugMessage(this,
+					"UseToolbarManager = true (ToolbarAvailable = {0}) and " +
+					"ToolbarButton is null, making Toolbar button.",
+					ToolbarManager.ToolbarAvailable
+				);
 				this.InitializeToolbarButton();
 			}
 
@@ -384,13 +434,20 @@
 				_mainWindowPos = GUILayout.Window(
 					this.windowID,
 					_mainWindowPos,
-					this.VOIDMainWindow,
+					VOID_Tools.GetWindowHandler(this.VOIDMainWindow),
 					string.Join(" ", new string[] { this.VoidName, this.VoidVersion }),
 					GUILayout.Width(250),
 					GUILayout.Height(50)
 				);
 
-				_mainWindowPos = Tools.ClampRectToScreen(_mainWindowPos);
+				if (HighLogic.LoadedSceneIsEditor)
+				{
+					_mainWindowPos = Tools.ClampRectToEditorPad(_mainWindowPos);
+				}
+				else
+				{
+					_mainWindowPos = Tools.ClampRectToScreen(_mainWindowPos);
+				}
 
 				if (_mainWindowPos != this.mainWindowPos)
 				{
@@ -405,13 +462,20 @@
 				_configWindowPos = GUILayout.Window(
 					this.windowID,
 					_configWindowPos,
-					this.VOIDConfigWindow,
+					VOID_Tools.GetWindowHandler(this.VOIDConfigWindow),
 					string.Join(" ", new string[] { this.VoidName, "Configuration" }),
 					GUILayout.Width(250),
 					GUILayout.Height(50)
 				);
 
-				_configWindowPos = Tools.ClampRectToScreen(_configWindowPos);
+				if (HighLogic.LoadedSceneIsEditor)
+				{
+					_configWindowPos = Tools.ClampRectToEditorPad(_configWindowPos);
+				}
+				else
+				{
+					_configWindowPos = Tools.ClampRectToScreen(_configWindowPos);
+				}
 
 				if (_configWindowPos != this.configWindowPos)
 				{
@@ -420,7 +484,7 @@
 			}
 		}
 
-		public void OnGUI()
+		public virtual void OnGUI()
 		{
 			if (Event.current.type == EventType.Repaint)
 			{
@@ -474,20 +538,14 @@
 			}
 		}
 
-		public void Update()
+		public virtual void Update()
 		{
 			this.LoadBeforeUpdate();
 
 			if (this.vessel != null && this.vesselSimActive)
 			{
-				double radius = VOID_Core.Instance.vessel.Radius();
-				SimManager.Gravity = VOID_Core.Instance.vessel.mainBody.gravParameter /
-					(radius * radius);
-				SimManager.TryStartSimulation();
-			}
-			else if (!this.vesselSimActive)
-			{
-				SimManager.ClearResults();
+				Tools.PostDebugMessage(this, "Updating SimManager.");
+				this.UpdateSimManager();
 			}
 
 			if (!this.guiRunning)
@@ -524,7 +582,7 @@
 			this._updateTimer += Time.deltaTime;
 		}
 
-		public void FixedUpdate()
+		public virtual void FixedUpdate()
 		{
 			bool newPowerState = this.powerAvailable;
 
@@ -553,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()
@@ -601,7 +677,7 @@
 			}
 			else
 			{
-				GUILayout.Label("-- POWER LOST --", this.LabelStyles["red"]);
+				GUILayout.Label("-- POWER LOST --", VOID_Styles.labelRed);
 			}
 
 			this.configWindowMinimized.value = !GUILayout.Toggle(!this.configWindowMinimized, "Configuration");
@@ -646,7 +722,6 @@
 			_content.tooltip = "Select previous skin";
 			if (GUILayout.Button(_content, GUILayout.ExpandWidth(true)))
 			{
-				this.GUIStylesLoaded = false;
 				this._skinIdx--;
 				Tools.PostDebugMessage(string.Format(
 					"{0}: new this._skinIdx = {1} :: skin_list.Count = {2}",
@@ -658,13 +733,12 @@
 
 			_content.text = this.Skin.name;
 			_content.tooltip = "Current skin";
-			GUILayout.Label(_content, this.LabelStyles["center"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(_content, VOID_Styles.labelCenter, GUILayout.ExpandWidth(true));
 
 			_content.text = "►";
 			_content.tooltip = "Select next skin";
 			if (GUILayout.Button(_content, GUILayout.ExpandWidth(true)))
 			{
-				this.GUIStylesLoaded = false;
 				this._skinIdx++;
 				Tools.PostDebugMessage(string.Format(
 					"{0}: new this._skinIdx = {1} :: skin_list.Count = {2}",
@@ -683,6 +757,7 @@
 			if (this._skinName != skinNames[this._skinIdx])
 			{
 				this._skinName.value = skinNames[this._skinIdx];
+				this.GUIStylesLoaded = false;
 			}
 
 			GUILayout.EndHorizontal();
@@ -694,7 +769,7 @@
 				this.stringFrequency = (1f / this.updatePeriod).ToString();
 			}
 			this.stringFrequency = GUILayout.TextField(this.stringFrequency.ToString(), 5, GUILayout.ExpandWidth(true));
-			// GUILayout.FlexibleSpace();
+
 			if (GUILayout.Button("Apply"))
 			{
 				double updateFreq = 1f / this.updatePeriod;
@@ -711,45 +786,89 @@
 			this._factoryReset = GUILayout.Toggle(this._factoryReset, "Factory Reset");
 		}
 
+		protected void UpdateSimManager()
+		{
+			if (SimManager.ResultsReady())
+			{
+				Tools.PostDebugMessage(this, "VesselSimulator results ready, setting Stages.");
+
+				this.Stages = SimManager.Stages;
+
+				if (this.Stages != null)
+				{
+					this.LastStage = this.Stages.Last();
+				}
+
+				if (HighLogic.LoadedSceneIsEditor)
+				{
+					SimManager.Gravity = VOID_Data.KerbinGee;
+				}
+				else
+				{
+					double radius = this.vessel.Radius();
+					SimManager.Gravity = this.vessel.mainBody.gravParameter / (radius * radius);
+				}
+
+				SimManager.minSimTime = new TimeSpan(0, 0, 0, 0, (int)(this.updatePeriod * 1000d));
+
+				SimManager.TryStartSimulation();
+			}
+			#if DEBUG
+			else
+			{
+				Tools.PostDebugMessage(this, "VesselSimulator results not ready.");
+			}
+			#endif
+		}
+
 		protected void LoadModulesOfType<T>()
 		{
-			var types = AssemblyLoader.loadedAssemblies
-				.Select(a => a.assembly.GetExportedTypes())
-				.SelectMany(t => t)
-				.Where(v => typeof(T).IsAssignableFrom(v)
-					&& !(v.IsInterface || v.IsAbstract) &&
-					!typeof(VOID_Core).IsAssignableFrom(v)
-				);
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Found {1} modules to check.",
-				this.GetType().Name,
-				types.Count()
-			));
-			foreach (var voidType in types)
-			{
-				if (!HighLogic.LoadedSceneIsEditor &&
-					typeof(IVOID_EditorModule).IsAssignableFrom(voidType))
-				{
-					continue;
-				}
-
-				Tools.PostDebugMessage(string.Format(
-					"{0}: found Type {1}",
-					this.GetType().Name,
-					voidType.Name
-				));
-
-				this.LoadModule(voidType);
+			StringBuilder sb = new StringBuilder("Loading modules...");
+			sb.AppendLine();
+
+			foreach (AssemblyLoader.LoadedAssembly assy in AssemblyLoader.loadedAssemblies)
+			{
+				foreach (Type loadedType in assy.assembly.GetExportedTypes())
+				{
+					if (
+						loadedType.IsInterface ||
+						loadedType.IsAbstract ||
+						!typeof(T).IsAssignableFrom(loadedType) ||
+						this.GetType().IsAssignableFrom(loadedType)
+					)
+					{
+						continue;
+					}
+
+					// HACK: This stops editor modules from loading in flight.  It is a dirty hack and should be fixed.
+					if (!HighLogic.LoadedSceneIsEditor && typeof(IVOID_EditorModule).IsAssignableFrom(loadedType))
+					{
+						continue;
+					}
+
+					sb.AppendFormat("Loading IVOID_Module type {0}...", loadedType.Name);
+
+					try
+					{
+						this.LoadModule(loadedType);
+						sb.AppendLine("Success.");
+					}
+					catch (Exception ex)
+					{
+						sb.AppendFormat("Failed, caught {0}", ex.GetType().Name);
+						sb.AppendLine();
+
+						#if DEBUG
+						Debug.LogException(ex);
+						#endif
+					}
+				}
 			}
 
 			this._modulesLoaded = true;
 
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Loaded {1} modules.",
-				this.GetType().Name,
-				this.Modules.Count
-			));
+			sb.AppendFormat("Loaded {0} modules.", this.Modules.Count);
+			sb.AppendLine();
 		}
 
 		protected void LoadModule(Type T)
@@ -835,32 +954,7 @@
 
 		protected void LoadGUIStyles()
 		{
-			this.LabelStyles["link"] = new GUIStyle(GUI.skin.label);
-			this.LabelStyles["link"].fontStyle = FontStyle.Bold;
-
-			this.LabelStyles["center"] = new GUIStyle(GUI.skin.label);
-			this.LabelStyles["center"].normal.textColor = Color.white;
-			this.LabelStyles["center"].alignment = TextAnchor.UpperCenter;
-
-			this.LabelStyles["center_bold"] = new GUIStyle(GUI.skin.label);
-			this.LabelStyles["center_bold"].normal.textColor = Color.white;
-			this.LabelStyles["center_bold"].alignment = TextAnchor.UpperCenter;
-			this.LabelStyles["center_bold"].fontStyle = FontStyle.Bold;
-
-			this.LabelStyles["right"] = new GUIStyle(GUI.skin.label);
-			this.LabelStyles["right"].normal.textColor = Color.white;
-			this.LabelStyles["right"].alignment = TextAnchor.UpperRight;
-
-			this.LabelStyles["red"] = new GUIStyle(GUI.skin.label);
-			this.LabelStyles["red"].normal.textColor = Color.red;
-			this.LabelStyles["red"].alignment = TextAnchor.MiddleCenter;
-
-			this.iconStyle = new GUIStyle(GUI.skin.button);
-			this.iconStyle.padding = new RectOffset(0, 0, 0, 0);
-			// this.iconStyle.margin = new RectOffset(0, 0, 0, 0);
-			// this.iconStyle.contentOffset = new Vector2(0, 0);
-			this.iconStyle.overflow = new RectOffset(0, 0, 0, 0);
-			// this.iconStyle.border = new RectOffset(0, 0, 0, 0);
+			VOID_Styles.OnSkinChanged();
 
 			this.GUIStylesLoaded = true;
 		}
@@ -877,13 +971,24 @@
 			{
 				this.LoadVesselTypes();
 			}
+
+			if (this.sortedBodyList == null && FlightGlobals.Bodies != null && FlightGlobals.Bodies.Count > 0)
+			{
+				this.sortedBodyList = new List<CelestialBody>(FlightGlobals.Bodies);
+				this.sortedBodyList.Sort(new CBListComparer());
+				this.sortedBodyList.Reverse();
+
+				Debug.Log(string.Format("sortedBodyList: {0}", string.Join("\n\t", this.sortedBodyList.Select(b => b.bodyName).ToArray())));
+			}
+
 		}
 
 		protected void InitializeToolbarButton()
 		{
-			// Do nothing if the Toolbar is not available.
+			// Do nothing if (the Toolbar is not available.
 			if (!ToolbarManager.ToolbarAvailable)
 			{
+				Tools.PostDebugMessage(this, "Refusing to make a ToolbarButton: ToolbarAvailable = false");
 				return;
 			}
 
@@ -891,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) =>
@@ -900,6 +1005,27 @@
 			};
 
 			Tools.PostDebugMessage(string.Format("{0}: Toolbar Button initialized.", this.GetType().Name));
+		}
+
+		protected void InitializeAppLauncherButton()
+		{
+			if (ApplicationLauncher.Ready)
+			{
+				this.AppLauncherButton = ApplicationLauncher.Instance.AddModApplication(
+					this.ToggleMainWindow, this.ToggleMainWindow,
+					this.appIconVisibleScenes,
+					this.VOIDIconTexture
+				);
+
+				Tools.PostDebugMessage(
+					this,
+					"AppLauncherButton initialized in {0}",
+					Enum.GetName(
+						typeof(GameScenes),
+						HighLogic.LoadedScene
+					)
+				);
+			}
 		}
 
 		protected void ToggleMainWindow()
@@ -936,7 +1062,12 @@
 				this.ToolbarButton.TexturePath = texturePath;
 			}
 
-			this.VOIDIconTexture = GameDatabase.Instance.GetTexture(texturePath, false);
+			this.VOIDIconTexture = GameDatabase.Instance.GetTexture(texturePath.Replace("icon", "appIcon"), false);
+
+			if (this.AppLauncherButton != null)
+			{
+				this.AppLauncherButton.SetTexture(VOIDIconTexture);
+			}
 		}
 
 		protected void CheckAndSave()
@@ -992,7 +1123,11 @@
 		{
 			this._Name = "VOID Core";
 
-			this._Active.value = true;
+			System.Version version = this.GetType().Assembly.GetName().Version;
+
+			this.VoidVersion = string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.MajorRevision);
+
+			this.toggleActive = true;
 
 			this._skinName = this.defaultSkin;
 			this._skinIdx = int.MinValue;
@@ -1011,6 +1146,20 @@
 			this.SetIconTexture(this.powerState | this.activeState);
 		}
 
+		public virtual void Dispose()
+		{
+			if (this.AppLauncherButton != null)
+			{
+				ApplicationLauncher.Instance.RemoveModApplication(this.AppLauncherButton);
+				this.AppLauncherButton = null;
+			}
+			if (this.ToolbarButton != null)
+			{
+				this.ToolbarButton.Destroy();
+				this.ToolbarButton = null;
+			}
+		}
+
 		protected enum IconState
 		{
 			PowerOff = 1,
@@ -1019,25 +1168,6 @@
 			Active = 8
 		}
 	}
-
-	public static partial class VOID_Data
-	{
-		public static VOID_Core core
-		{
-			get
-			{
-				return VOID_Core.Instance;
-			}
-		}
-
-		public static double KerbinGee
-		{
-			get
-			{
-				return core.Kerbin.gravParameter / (core.Kerbin.Radius * core.Kerbin.Radius);
-			}
-		}
-	}
 }
 
 

file:b/VOID_Data.cs (new)
--- /dev/null
+++ b/VOID_Data.cs
@@ -1,1 +1,1181 @@
-
+// VOID
+//
+// VOID_Data.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 ToadicusTools;
+using UnityEngine;
+
+namespace VOID
+{
+	public static class VOID_Data
+	{
+		private static Dictionary<int, IVOID_DataValue> dataValues = new Dictionary<int, IVOID_DataValue>();
+		public static Dictionary<int, IVOID_DataValue> DataValues
+		{
+			get
+			{
+				return dataValues;
+			}
+		}
+
+		#region Constants
+
+		private static double kerbinGee;
+
+		public static double KerbinGee
+		{
+			get
+			{
+				if (kerbinGee == default(double))
+				{
+					kerbinGee = core.HomeBody.gravParameter / (core.HomeBody.Radius * core.HomeBody.Radius);
+				}
+
+				return kerbinGee;
+			}
+		}
+
+		#endregion
+
+		#region Core Data
+
+		public static VOID_Core core
+		{
+			get
+			{
+				if (HighLogic.LoadedSceneIsEditor)
+				{
+					return VOID_EditorCore.Instance;
+				}
+				else
+				{
+					return VOID_Core.Instance;
+				}
+			}
+		}
+
+		#endregion
+
+		#region Atmosphere
+
+		public static readonly VOID_DoubleValue atmDensity =
+			new VOID_DoubleValue(
+				"Atmosphere Density",
+				new Func<double>(() => core.vessel.atmDensity * 1000f),
+				"g/m³"
+			);
+
+		public static readonly VOID_FloatValue atmLimit =
+			new VOID_FloatValue(
+				"Atmosphere Limit",
+				new Func<float>(() => core.vessel.mainBody.maxAtmosphereAltitude),
+				"m"
+			);
+
+		public static readonly VOID_DoubleValue atmPressure =
+			new VOID_DoubleValue(
+				"Pressure",
+				new Func<double>(() => core.vessel.staticPressure),
+				"atm"
+			);
+
+		public static readonly VOID_FloatValue temperature =
+			new VOID_FloatValue(
+				"Temperature",
+				new Func<float>(() => core.vessel.flightIntegrator.getExternalTemperature()),
+				"°C"
+			);
+
+		#endregion
+
+		#region Attitude
+
+		public static readonly VOID_StrValue vesselHeading =
+			new VOID_StrValue(
+				"Heading",
+				delegate()
+				{
+					double heading = core.vessel.getSurfaceHeading();
+					string cardinal = VOID_Tools.get_heading_text(heading);
+
+					return string.Format(
+						"{0}° {1}",
+						heading.ToString("F2"),
+						cardinal
+					);
+				}
+			);
+
+		public static readonly VOID_DoubleValue vesselPitch =
+			new VOID_DoubleValue(
+				"Pitch",
+				() => core.vessel.getSurfacePitch(),
+				"°"
+			);
+
+		#endregion
+
+		#region Career
+
+		public static readonly VOID_StrValue fundingStatus =
+			new VOID_StrValue(
+				string.Intern("Funds"),
+				delegate()
+				{
+					if (VOID_CareerStatus.Instance == null)
+					{
+						return string.Empty;
+					}
+
+					return string.Format("{0} ({1})",
+						VOID_CareerStatus.Instance.currentFunds.ToString("#,#.##"),
+						VOID_CareerStatus.formatDelta(VOID_CareerStatus.Instance.lastFundsChange)
+					);
+				}
+			);
+
+		public static readonly VOID_StrValue reputationStatus =
+			new VOID_StrValue(
+				string.Intern("Reputation"),
+				delegate()
+				{
+					if (VOID_CareerStatus.Instance == null)
+					{
+						return string.Empty;
+					}
+
+					return string.Format("{0} ({1})",
+						VOID_CareerStatus.Instance.currentReputation.ToString("#,#.##"),
+						VOID_CareerStatus.formatDelta(VOID_CareerStatus.Instance.lastRepChange)
+					);
+				}
+			);
+
+		public static readonly VOID_StrValue scienceStatus =
+			new VOID_StrValue(
+				string.Intern("Science"),
+				delegate()
+				{
+					if (VOID_CareerStatus.Instance == null)
+					{
+						return string.Empty;
+					}
+
+					return string.Format("{0} ({1})",
+						VOID_CareerStatus.Instance.currentScience.ToString("#,#.##"),
+						VOID_CareerStatus.formatDelta(VOID_CareerStatus.Instance.lastScienceChange)
+					);
+				}
+			);
+
+		#endregion
+
+		#region Control
+
+		public static readonly VOID_FloatValue mainThrottle =
+			new VOID_FloatValue(
+				"Throttle",
+				new Func<float>(() => core.vessel.ctrlState.mainThrottle * 100f),
+				"%"
+			);
+
+		#endregion
+
+		#region Engineering
+
+		public static readonly VOID_IntValue partCount =
+			new VOID_IntValue(
+				"Parts",
+				new Func<int>(() => core.vessel.Parts.Count),
+				""
+			);
+
+		#region Mass
+
+		public static readonly VOID_StrValue comboResourceMass =
+			new VOID_StrValue(
+				"Resource Mass (curr / total)",
+				delegate()
+				{
+					return string.Format("{0} / {1}",
+						stageResourceMass.ValueUnitString("F3"),
+						resourceMass.ValueUnitString("F3")
+					);
+				}
+			);
+
+		public static readonly VOID_DoubleValue resourceMass =
+			new VOID_DoubleValue(
+				"Resource Mass",
+				delegate()
+				{
+					if (core.Stages == null || core.LastStage == null)
+					{
+						return double.NaN;
+					}
+
+					return core.LastStage.resourceMass;
+				},
+				"tons"
+			);
+
+		public static readonly VOID_DoubleValue stageResourceMass =
+			new VOID_DoubleValue(
+				"Resource Mass (Stage)",
+				delegate()
+				{
+					if (core.LastStage == null)
+					{
+						return double.NaN;
+					}
+
+					return core.LastStage.resourceMass;
+				},
+				"tons"
+			);
+
+		public static readonly VOID_DoubleValue totalMass =
+			new VOID_DoubleValue(
+				"Total Mass",
+				delegate()
+				{
+					if (core.Stages == null || core.LastStage == null)
+					{
+						return double.NaN;
+					}
+
+					return core.LastStage.totalMass;
+				},
+				"tons"
+			);
+
+		#endregion
+
+		#region DeltaV
+
+		public static readonly VOID_DoubleValue stageDeltaV =
+			new VOID_DoubleValue(
+				"DeltaV (Current Stage)",
+				delegate()
+				{
+					if (core.Stages == null || core.LastStage == null)
+						return double.NaN;
+					return core.LastStage.deltaV;
+				},
+				"m/s"
+			);
+
+		public static readonly VOID_DoubleValue totalDeltaV =
+			new VOID_DoubleValue(
+				"DeltaV (Total)",
+				delegate()
+				{
+					if (core.Stages == null || core.LastStage == null)
+						return double.NaN;
+					return core.LastStage.totalDeltaV;
+				},
+				"m/s"
+			);
+
+		#endregion
+
+		#region Propulsion
+
+		public static readonly VOID_StrValue currmaxThrustWeight =
+			new VOID_StrValue(
+				"T:W (curr/max)",
+				delegate()
+				{
+					if (core.Stages == null || core.LastStage == null)
+						return "N/A";
+
+					return string.Format(
+						"{0} / {1}",
+						(VOID_Data.currThrustWeight.Value).ToString("F2"),
+						(VOID_Data.maxThrustWeight.Value).ToString("F2")
+					);
+				}
+			);
+
+		public static readonly VOID_StrValue currmaxThrust =
+			new VOID_StrValue(
+				"Thrust (curr/max)",
+				delegate()
+				{
+					if (core.Stages == null || core.LastStage == null)
+						return "N/A";
+
+					double currThrust = core.LastStage.actualThrust;
+					double maxThrust = core.LastStage.thrust;
+
+					return string.Format(
+						"{0} / {1}",
+						currThrust.ToString("F1"),
+						maxThrust.ToString("F1")
+					);
+				}
+			);
+
+		public static readonly VOID_DoubleValue stageMassFlow =
+			new VOID_DoubleValue(
+				"Stage Mass Flow",
+				delegate()
+				{
+					if (core.LastStage == null)
+					{
+						return double.NaN;
+					}
+
+					double stageIsp = core.LastStage.isp;
+					double stageThrust = stageNominalThrust;
+
+					Tools.PostDebugMessage(typeof(VOID_Data), "calculating stageMassFlow from:\n" +
+						"\tstageIsp: {0}\n" +
+						"\tstageThrust: {1}\n" +
+						"\tKerbinGee: {2}\n",
+						stageIsp,
+						stageThrust,
+						KerbinGee
+					);
+
+					return stageThrust / (stageIsp * KerbinGee);
+				},
+				"Mg/s"
+			);
+
+		public static readonly VOID_DoubleValue stageNominalThrust =
+			new VOID_DoubleValue(
+				"Nominal Stage Thrust",
+				delegate()
+				{
+					if (core.LastStage == null)
+					{
+						return double.NaN;
+					}
+
+					if (core.LastStage.actualThrust == 0d)
+					{
+						return core.LastStage.thrust;
+					}
+					else
+					{
+						return core.LastStage.actualThrust;
+					}
+				},
+				"kN"
+			);
+
+		#endregion
+
+		#region Kinetics
+
+		public static readonly VOID_DoubleValue currThrustWeight =
+			new VOID_DoubleValue(
+				"T:W Ratio",
+				delegate()
+				{
+					if (core.LastStage == null)
+					{
+						return double.NaN;
+					}
+
+					return core.LastStage.actualThrustToWeight;
+				},
+				""
+			);
+
+
+
+		public static readonly VOID_DoubleValue maxThrustWeight =
+			new VOID_DoubleValue(
+				"T:W Ratio",
+				delegate()
+				{
+					if (core.LastStage == null)
+					{
+						return double.NaN;
+					}
+
+					return core.LastStage.thrustToWeight;
+				},
+				""
+			);
+
+		public static readonly VOID_DoubleValue nominalThrustWeight =
+			new VOID_DoubleValue(
+				"Thrust-to-Weight Ratio",
+				delegate()
+				{
+					if (HighLogic.LoadedSceneIsEditor || currThrustWeight.Value == 0d)
+					{
+						return maxThrustWeight.Value;
+					}
+
+					return currThrustWeight.Value;
+				},
+				""
+			);
+
+		public static readonly VOID_DoubleValue surfaceThrustWeight =
+			new VOID_DoubleValue(
+				"Max T:W @ surface",
+				delegate()
+				{
+					if (core.Stages == null || core.LastStage == null)
+						return double.NaN;
+
+					double maxThrust = core.LastStage.thrust;
+					double mass = core.LastStage.totalMass;
+					double gravity = (VOID_Core.Constant_G * core.vessel.mainBody.Mass) /
+					               (core.vessel.mainBody.Radius * core.vessel.mainBody.Radius);
+					double weight = mass * gravity;
+
+					return maxThrust / weight;
+				},
+				""
+			);
+
+		public static readonly VOID_Vector3dValue vesselThrustOffset =
+			new VOID_Vector3dValue(
+				"Thrust Offset",
+				delegate()
+				{
+					if (core.vessel == null)
+					{
+						return Vector3d.zero;
+					}
+
+					List<PartModule> engineModules = core.vessel.getModulesOfType<PartModule>();
+
+					Vector3d thrustPos = Vector3d.zero;
+					Vector3d thrustDir = Vector3d.zero;
+					float thrust = 0;
+
+					foreach (PartModule engine in engineModules)
+					{
+						float moduleThrust = 0;
+
+						switch (engine.moduleName)
+						{
+							case "ModuleEngines":
+							case "ModuleEnginesFX":
+								break;
+							default:
+								continue;
+						}
+
+						if (!engine.isEnabled)
+						{
+							continue;
+						}
+
+						CenterOfThrustQuery cotQuery = new CenterOfThrustQuery();
+
+						if (engine is ModuleEngines)
+						{
+							ModuleEngines engineModule = engine as ModuleEngines;
+
+							moduleThrust = engineModule.finalThrust;
+
+							engineModule.OnCenterOfThrustQuery(cotQuery);
+						}
+						else // engine is ModuleEnginesFX
+						{
+							ModuleEnginesFX engineFXModule = engine as ModuleEnginesFX;
+
+							moduleThrust = engineFXModule.finalThrust;
+
+							engineFXModule.OnCenterOfThrustQuery(cotQuery);
+						}
+
+						if (moduleThrust != 0d)
+						{
+							cotQuery.thrust = moduleThrust;
+						}
+
+						thrustPos += cotQuery.pos * cotQuery.thrust;
+						thrustDir += cotQuery.dir * cotQuery.thrust;
+						thrust += cotQuery.thrust;
+					}
+
+					if (thrust != 0)
+					{
+						thrustPos /= thrust;
+						thrustDir /= thrust;
+					}
+
+					Transform vesselTransform = core.vessel.transform;
+
+					thrustPos = vesselTransform.InverseTransformPoint(thrustPos);
+					thrustDir = vesselTransform.InverseTransformDirection(thrustDir);
+
+					Vector3d thrustOffset = VectorTools.PointDistanceToLine(
+						                      thrustPos, thrustDir.normalized, core.vessel.findLocalCenterOfMass());
+
+					Tools.PostDebugMessage(typeof(VOID_Data), "vesselThrustOffset:\n" +
+						"\tthrustPos: {0}\n" +
+						"\tthrustDir: {1}\n" +
+						"\tthrustOffset: {2}\n" +
+						"\tvessel.CoM: {3}",
+						thrustPos,
+						thrustDir.normalized,
+						thrustOffset,
+						core.vessel.findWorldCenterOfMass()
+					);
+
+					return thrustOffset;
+				},
+				"m"
+			);
+
+		#endregion
+
+		#region Air Breathing
+
+		public static readonly VOID_StrValue intakeAirStatus =
+			new VOID_StrValue(
+				"Intake Air (Curr / Req)",
+				delegate()
+				{
+					double currentAmount;
+					double currentRequirement;
+
+					currentAmount = 0d;
+					currentRequirement = 0d;
+
+					foreach (Part part in core.vessel.Parts)
+					{
+						if (part.enabled)
+						{
+							ModuleEngines engineModule;
+							ModuleEnginesFX enginesFXModule;
+							List<Propellant> propellantList = null;
+
+							if (part.tryGetFirstModuleOfType<ModuleEngines>(out engineModule))
+							{
+								propellantList = engineModule.propellants;
+							}
+							else if (part.tryGetFirstModuleOfType<ModuleEnginesFX>(out enginesFXModule))
+								{
+									propellantList = enginesFXModule.propellants;
+								}
+
+							if (propellantList != null)
+							{
+								foreach (Propellant propellant in propellantList)
+								{
+									if (propellant.name == "IntakeAir")
+									{
+										currentRequirement += propellant.currentRequirement / TimeWarp.fixedDeltaTime;
+										break;
+									}
+								}
+							}
+						}
+
+						ModuleResourceIntake intakeModule;
+
+						if (part.enabled && part.tryGetFirstModuleOfType<ModuleResourceIntake>(out intakeModule))
+						{
+							if (intakeModule.resourceName == "IntakeAir")
+							{
+								currentAmount += intakeModule.airFlow;
+							}
+						}
+					}
+
+					if (currentAmount == 0 && currentRequirement == 0)
+					{
+						return "N/A";
+					}
+
+					return string.Format("{0:F3} / {1:F3}", currentAmount, currentRequirement);
+				}
+			);
+
+		#endregion
+
+		#region Crew
+
+		public static readonly VOID_IntValue vesselCrewCount =
+			new VOID_IntValue(
+				"Crew Onboard",
+				delegate()
+				{
+					if (core.vessel != null)
+					{
+						return core.vessel.GetCrewCount();
+					}
+					else
+					{
+						return 0;
+					}
+				},
+				""
+			);
+
+		public static readonly VOID_IntValue vesselCrewCapacity =
+			new VOID_IntValue(
+				"Crew Capacity",
+				delegate()
+				{
+					if (core.vessel != null)
+					{
+						return core.vessel.GetCrewCapacity();
+					}
+					else
+					{
+						return 0;
+					}
+				},
+				""
+			);
+
+		#endregion
+
+		#endregion
+
+		#region Location
+
+		public static readonly VOID_DoubleValue downrangeDistance =
+			new VOID_DoubleValue(
+				"Downrange Distance",
+				delegate()
+				{
+
+					if (core.vessel == null ||
+					  Planetarium.fetch == null ||
+					  core.vessel.mainBody != Planetarium.fetch.Home)
+					{
+						return double.NaN;
+					}
+
+					double vesselLongitude = core.vessel.longitude * Math.PI / 180d;
+					double vesselLatitude = core.vessel.latitude * Math.PI / 180d;
+
+					const double kscLongitude = 285.442323427289 * Math.PI / 180d;
+					const double kscLatitude = -0.0972112860655246 * Math.PI / 180d;
+
+					double diffLon = vesselLongitude - kscLongitude;
+					double diffLat = vesselLatitude - kscLatitude;
+
+					double sinHalfDiffLat = Math.Sin(diffLat / 2d);
+					double sinHalfDiffLon = Math.Sin(diffLon / 2d);
+
+					double cosVesselLon = Math.Cos(vesselLongitude);
+					double cosKSCLon = Math.Cos(kscLongitude);
+
+					double haversine =
+						sinHalfDiffLat * sinHalfDiffLat +
+						cosVesselLon * cosKSCLon * sinHalfDiffLon * sinHalfDiffLon;
+
+					double arc = 2d * Math.Atan2(Math.Sqrt(haversine), Math.Sqrt(1d - haversine));
+
+					return core.vessel.mainBody.Radius * arc;
+				},
+				"m"
+			);
+
+		public static readonly VOID_StrValue surfLatitude =
+			new VOID_StrValue(
+				"Latitude",
+				new Func<string>(() => VOID_Tools.GetLatitudeString(core.vessel))
+			);
+
+		public static readonly VOID_StrValue surfLongitude =
+			new VOID_StrValue(
+				"Longitude",
+				new Func<string>(() => VOID_Tools.GetLongitudeString(core.vessel))
+			);
+
+		public static readonly VOID_DoubleValue trueAltitude =
+			new VOID_DoubleValue(
+				"Altitude (true)",
+				delegate()
+				{
+					double alt_true = core.vessel.orbit.altitude - core.vessel.terrainAltitude;
+					// HACK: This assumes that on worlds with oceans, all water is fixed at 0 m,
+					// and water covers the whole surface at 0 m.
+					if (core.vessel.terrainAltitude < 0 && core.vessel.mainBody.ocean)
+						alt_true = core.vessel.orbit.altitude;
+					return alt_true;
+				},
+				"m"
+			);
+
+		#endregion
+
+		#region Kinematics
+
+		public static readonly VOID_DoubleValue geeForce =
+			new VOID_DoubleValue(
+				"G-force",
+				new Func<double>(() => core.vessel.geeForce),
+				"gees"
+			);
+
+		public static readonly VOID_DoubleValue horzVelocity =
+			new VOID_DoubleValue(
+				"Horizontal speed",
+				new Func<double>(() => core.vessel.horizontalSrfSpeed),
+				"m/s"
+			);
+
+		public static readonly VOID_DoubleValue surfVelocity =
+			new VOID_DoubleValue(
+				"Surface velocity",
+				new Func<double>(() => core.vessel.srf_velocity.magnitude),
+				"m/s"
+			);
+
+		public static readonly VOID_DoubleValue vertVelocity =
+			new VOID_DoubleValue(
+				"Vertical speed",
+				new Func<double>(() => core.vessel.verticalSpeed),
+				"m/s"
+			);
+
+		public static readonly VOID_DoubleValue vesselAccel =
+			new VOID_DoubleValue(
+				"Acceleration",
+				() => geeForce * KerbinGee,
+				"m/s²"
+			);
+
+		public static readonly VOID_DoubleValue vesselAngularVelocity =
+			new VOID_DoubleValue(
+				"Angular Velocity",
+				delegate()
+				{
+					if (core.vessel != null)
+					{
+						return core.vessel.angularVelocity.magnitude;
+					}
+					else
+					{
+						return double.NaN;
+					}
+				},
+				"rad/s"
+			);
+
+		#endregion
+
+		#region Navigation
+
+		public static int upcomingManeuverNodes
+		{
+			get
+			{
+				if (core.vessel == null ||
+				    core.vessel.patchedConicSolver == null ||
+				    core.vessel.patchedConicSolver.maneuverNodes == null)
+				{
+					return 0;
+				}
+
+				return core.vessel.patchedConicSolver.maneuverNodes.Count;
+			}
+		}
+
+		public static readonly VOID_StrValue burnTimeDoneAtNode =
+			new VOID_StrValue(
+				"Full burn time to be half done at node",
+				delegate()
+				{
+					if (core.LastStage == null && upcomingManeuverNodes < 1)
+					{
+						return "N/A";
+					}
+
+					ManeuverNode node = core.vessel.patchedConicSolver.maneuverNodes[0];
+
+					if ((node.UT - Planetarium.GetUniversalTime()) < 0)
+					{
+						return string.Empty;
+					}
+
+					double interval = (node.UT - currentNodeBurnDuration) - Planetarium.GetUniversalTime();
+
+					if (double.IsNaN(interval))
+					{
+						return string.Intern("NaN");
+					}
+
+					int sign = Math.Sign(interval);
+					interval = Math.Abs(interval);
+
+					string format;
+
+					if (sign >= 0)
+					{
+						format = string.Intern("T - {0}");
+					}
+					else
+					{
+						format = string.Intern("T + {0}");
+					}
+
+					return string.Format(format, VOID_Tools.FormatInterval(interval));
+				}
+			);
+
+		public static readonly VOID_StrValue burnTimeHalfDoneAtNode =
+			new VOID_StrValue(
+				"Full burn time to be half done at node",
+				delegate()
+				{
+					if (core.LastStage == null && upcomingManeuverNodes < 1)
+					{
+						return "N/A";
+					}
+
+					ManeuverNode node = core.vessel.patchedConicSolver.maneuverNodes[0];
+
+					if ((node.UT - Planetarium.GetUniversalTime()) < 0)
+					{
+						return string.Empty;
+					}
+
+					double interval = (node.UT - currentNodeHalfBurnDuration) - Planetarium.GetUniversalTime();
+
+					if (double.IsNaN(interval))
+					{
+						return string.Intern("NaN");
+					}
+
+					int sign = Math.Sign(interval);
+					interval = Math.Abs(interval);
+
+					string format;
+
+					if (sign >= 0)
+					{
+						format = string.Intern("T - {0}");
+					}
+					else
+					{
+						format = string.Intern("T + {0}");
+					}
+
+					return string.Format(format, VOID_Tools.FormatInterval(interval));
+				}
+			);
+
+		public static readonly VOID_DoubleValue currManeuverDeltaV =
+			new VOID_DoubleValue(
+				"Current Maneuver Delta-V",
+				delegate()
+				{
+					if (upcomingManeuverNodes > 0)
+					{
+						return core.vessel.patchedConicSolver.maneuverNodes[0].DeltaV.magnitude;
+					}
+					else
+					{
+						return double.NaN;
+					}
+				},
+				"m/s"
+			);
+
+		public static readonly VOID_DoubleValue currManeuverDVRemaining =
+			new VOID_DoubleValue(
+				"Remaining Maneuver Delta-V",
+				delegate()
+				{
+					if (upcomingManeuverNodes > 0)
+					{
+						return core.vessel.patchedConicSolver.maneuverNodes[0].GetBurnVector(core.vessel.orbit).magnitude;
+					}
+					else
+					{
+						return double.NaN;
+					}
+				},
+				"m/s"
+			);
+
+		public static readonly VOID_DoubleValue currentNodeBurnDuration =
+			new VOID_DoubleValue(
+				"Total Burn Time",
+				delegate()
+				{
+					if (core.LastStage == null || currManeuverDeltaV.Value == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					double stageThrust = stageNominalThrust;
+
+					return burnTime(currManeuverDeltaV.Value, totalMass, stageMassFlow, stageThrust);
+				},
+				"s"
+			);
+
+		public static readonly VOID_DoubleValue currentNodeBurnRemaining =
+			new VOID_DoubleValue(
+				"Burn Time Remaining",
+				delegate()
+				{
+					if (core.LastStage == null || currManeuverDVRemaining == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					double stageThrust = stageNominalThrust;
+
+					return burnTime(currManeuverDVRemaining, totalMass, stageMassFlow, stageThrust);
+				},
+				"s"
+			);
+
+		public static readonly VOID_DoubleValue currentNodeHalfBurnDuration =
+			new VOID_DoubleValue(
+				"Half Burn Time",
+				delegate()
+				{
+					if (core.LastStage == null || currManeuverDeltaV.Value == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					double stageThrust = stageNominalThrust;
+
+					return burnTime(currManeuverDeltaV.Value / 2d, totalMass, stageMassFlow, stageThrust);
+				},
+				"s"
+			);
+
+		public static readonly VOID_DoubleValue nextManeuverDeltaV =
+			new VOID_DoubleValue(
+				"Current Maneuver Delta-V",
+				delegate()
+				{
+					if (upcomingManeuverNodes > 1)
+					{
+						return core.vessel.patchedConicSolver.maneuverNodes[1].DeltaV.magnitude;
+					}
+					else
+					{
+						return double.NaN;
+					}
+				},
+				"m/s"
+			);
+
+		#endregion
+
+		#region Orbits
+
+		public static readonly VOID_StrValue primaryName =
+			new VOID_StrValue(
+				VOID_Localization.void_primary,
+				delegate()
+				{
+					if (core.vessel == null)
+					{
+						return string.Empty;
+					}
+					return core.vessel.mainBody.name;
+				}
+			);
+
+		public static readonly VOID_DoubleValue orbitAltitude =
+			new VOID_DoubleValue(
+				"Altitude (ASL)",
+				new Func<double>(() => core.vessel.orbit.altitude),
+				"m"
+			);
+
+		public static readonly VOID_DoubleValue orbitVelocity =
+			new VOID_DoubleValue(
+				VOID_Localization.void_velocity,
+				new Func<double>(() => core.vessel.orbit.vel.magnitude),
+				"m/s"
+			);
+
+		public static readonly VOID_DoubleValue orbitApoAlt =
+			new VOID_DoubleValue(
+				VOID_Localization.void_apoapsis,
+				new Func<double>(() => core.vessel.orbit.ApA),
+				"m"
+			);
+
+		public static readonly VOID_DoubleValue oribtPeriAlt =
+			new VOID_DoubleValue(
+				VOID_Localization.void_periapsis,
+				new Func<double>(() => core.vessel.orbit.PeA),
+				"m"
+			);
+
+		public static readonly VOID_StrValue timeToApo =
+			new VOID_StrValue(
+				"Time to Apoapsis",
+				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.FormatInterval(core.vessel.orbit.timeToPe))
+			);
+
+		public static readonly VOID_DoubleValue orbitInclination =
+			new VOID_DoubleValue(
+				"Inclination",
+				new Func<double>(() => core.vessel.orbit.inclination),
+				"°"
+			);
+
+		public static readonly VOID_DoubleValue gravityAccel =
+			new VOID_DoubleValue(
+				"Gravity",
+				delegate()
+				{
+					double orbitRadius = core.vessel.mainBody.Radius +
+					                   core.vessel.mainBody.GetAltitude(core.vessel.findWorldCenterOfMass());
+					return (VOID_Core.Constant_G * core.vessel.mainBody.Mass) /
+					(orbitRadius * orbitRadius);
+				},
+				"m/s²"
+			);
+
+		public static readonly VOID_StrValue orbitPeriod =
+			new VOID_StrValue(
+				"Period",
+				new Func<string>(() => VOID_Tools.FormatInterval(core.vessel.orbit.period))
+			);
+
+		public static readonly VOID_DoubleValue semiMajorAxis =
+			new VOID_DoubleValue(
+				"Semi-Major Axis",
+				new Func<double>(() => core.vessel.orbit.semiMajorAxis),
+				"m"
+			);
+
+		public static readonly VOID_DoubleValue eccentricity =
+			new VOID_DoubleValue(
+				"Eccentricity",
+				new Func<double>(() => core.vessel.orbit.eccentricity),
+				""
+			);
+
+		public static readonly VOID_DoubleValue meanAnomaly =
+			new VOID_DoubleValue(
+				"Mean Anomaly",
+				new Func<double>(() => core.vessel.orbit.meanAnomaly * 180d / Math.PI),
+				"°"
+			);
+
+		public static readonly VOID_DoubleValue trueAnomaly = 
+			new VOID_DoubleValue(
+				"True Anomaly",
+				new Func<double>(() => core.vessel.orbit.trueAnomaly),
+				"°"
+			);
+
+		public static readonly VOID_DoubleValue eccAnomaly =
+			new VOID_DoubleValue(
+				"Eccentric Anomaly",
+				new Func<double>(() => core.vessel.orbit.eccentricAnomaly * 180d / Math.PI),
+				"°"
+			);
+
+		public static readonly VOID_DoubleValue longitudeAscNode =
+			new VOID_DoubleValue(
+				"Long. Ascending Node",
+				new Func<double>(() => core.vessel.orbit.LAN),
+				"°"
+			);
+
+		public static readonly VOID_DoubleValue argumentPeriapsis =
+			new VOID_DoubleValue(
+				"Argument of Periapsis",
+				new Func<double>(() => core.vessel.orbit.argumentOfPeriapsis),
+				"°"
+			);
+
+		public static readonly VOID_DoubleValue localSiderealLongitude =
+			new VOID_DoubleValue(
+				"Local Sidereal Longitude",
+				new Func<double>(() => VOID_Tools.FixDegreeDomain(
+						core.vessel.longitude + core.vessel.orbit.referenceBody.rotationAngle)),
+				"°"
+			);
+
+		#endregion
+
+		#region Science
+
+		public static readonly VOID_StrValue expSituation =
+			new VOID_StrValue(
+				"Situation",
+				new Func<string>(() => core.vessel.GetExperimentSituation().HumanString())
+			);
+
+		public static readonly VOID_StrValue currBiome =
+			new VOID_StrValue(
+				"Biome",
+				new Func<string>(() => VOID_Tools.GetBiome(core.vessel).name)
+			);
+
+		#endregion
+
+		#region Surface
+
+		public static readonly VOID_DoubleValue terrainElevation =
+			new VOID_DoubleValue(
+				"Terrain elevation",
+				new Func<double>(() => core.vessel.terrainAltitude),
+				"m"
+			);
+
+		#endregion
+
+		private static double burnTime(double deltaV, double initialMass, double massFlow, double thrust)
+		{
+			Tools.PostDebugMessage(typeof(VOID_Data), "calculating burnTime from:\n" +
+				"\tdeltaV: {0}\n" +
+				"\tinitialMass: {1}\n" +
+				"\tmassFlow: {2}\n" +
+				"\tthrust: {3}\n",
+				deltaV,
+				initialMass,
+				massFlow,
+				thrust
+			);
+			return initialMass / massFlow * (1d - Math.Exp(-deltaV * massFlow / thrust));
+		}
+	}
+}
+
+

--- a/VOID_DataLogger.cs
+++ b/VOID_DataLogger.cs
@@ -29,6 +29,8 @@
 using KSP;
 using System;
 using System.Collections.Generic;
+using System.IO;
+using System.Text;
 using ToadicusTools;
 using UnityEngine;
 
@@ -39,208 +41,455 @@
 		/*
 		 * 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 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 (Single.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)
-		{
-			var efile = KSP.IO.File.AppendText<VOID_Core>(vessel.vesselName + "_data.csv", null);
-			foreach (string line in csvArray)
-			{
-				efile.Write(line);
-			}
-			efile.Close();
-		}
-
-		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
 
-			string line = "";
-			if (first_write && !KSP.IO.File.Exists<VOID_Core>(vessel.vesselName + "_data.csv", null))
-			{
-				first_write = false;
-				line += "Mission Elapsed Time (s);Altitude ASL (m);Altitude above terrain (m);Orbital Velocity (m/s);Surface Velocity (m/s);Vertical Speed (m/s);Horizontal Speed (m/s);Gee Force (gees);Temperature (°C);Gravity (m/s²);Atmosphere Density (g/m³);\n";
-			}
+			StringBuilder line = new StringBuilder();
+
+			if (firstWrite)
+			{
+				firstWrite = false;
+				line.Append(
+					"\"Kerbin Universal Time (s)\"," +
+					"\"Mission Elapsed Time (s)\t\"," +
+					"\"Altitude ASL (m)\"," +
+					"\"Altitude above terrain (m)\"," +
+					"\"Surface Latitude (°)\"," +
+					"\"Surface Longitude (°)\"," +
+					"\"Orbital Velocity (m/s)\"," +
+					"\"Surface Velocity (m/s)\"," +
+					"\"Vertical Speed (m/s)\"," +
+					"\"Horizontal Speed (m/s)\"," +
+					"\"Gee Force (gees)\"," +
+					"\"Temperature (°C)\"," +
+					"\"Gravity (m/s²)\"," +
+					"\"Atmosphere Density (g/m³)\"," +
+					"\"Downrange Distance  (m)\"," +
+					"\n"
+				);
+			}
+
+			// Universal time
+			line.Append(Planetarium.GetUniversalTime().ToString("F2"));
+			line.Append(',');
+
 			//Mission time
-			line += vessel.missionTime.ToString("F3") + ";";
+			line.Append(vessel.missionTime.ToString("F3"));
+			line.Append(',');
+
 			//Altitude ASL
-			line += 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 += alt_true.ToString("F3") + ";";
+			line.Append(VOID_Data.trueAltitude.Value.ToString("F3"));
+			line.Append(',');
+
+			// Surface Latitude
+			line.Append('"');
+			line.Append(VOID_Data.surfLatitude.Value);
+			line.Append('"');
+			line.Append(',');
+
+			// Surface Longitude
+			line.Append('"');
+			line.Append(VOID_Data.surfLongitude.Value);
+			line.Append('"');
+			line.Append(',');
+
 			//Orbital velocity
-			line += vessel.orbit.vel.magnitude.ToString("F3") + ";";
+			line.Append(VOID_Data.orbitVelocity.Value.ToString("F3"));
+			line.Append(',');
+
 			//surface velocity
-			line += vessel.srf_velocity.magnitude.ToString("F3") + ";";
+			line.Append(VOID_Data.surfVelocity.Value.ToString("F3"));
+			line.Append(',');
+
 			//vertical speed
-			line += vessel.verticalSpeed.ToString("F3") + ";";
+			line.Append(VOID_Data.vertVelocity.Value.ToString("F3"));
+			line.Append(',');
+
 			//horizontal speed
-			line += vessel.horizontalSrfSpeed.ToString("F3") + ";";
+			line.Append(VOID_Data.horzVelocity.Value.ToString("F3"));
+			line.Append(',');
+
 			//gee force
-			line += vessel.geeForce.ToString("F3") + ";";
+			line.Append(VOID_Data.geeForce.Value.ToString("F3"));
+			line.Append(',');
+
 			//temperature
-			line += 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 += g_vessel.ToString("F3") + ";";
+			line.Append(VOID_Data.gravityAccel.Value.ToString("F3"));
+			line.Append(',');
+
 			//atm density
-			line += (vessel.atmDensity * 1000).ToString("F3") + ";";
-			line += "\n";
-			if (csvList.Contains(line) == false) csvList.Add(line);
-			csvCollectTimer = 0f;
-		}
+			line.Append(VOID_Data.atmDensity.Value.ToString("G3"));
+			line.Append(',');
+
+			// Downrange Distance
+			line.Append((VOID_Data.downrangeDistance.Value.ToString("G3")));
+
+			line.Append('\n');
+
+			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_DataValue.cs
+++ b/VOID_DataValue.cs
@@ -34,6 +34,10 @@
 {
 	public interface IVOID_DataValue
 	{
+		string Label { get; }
+		string Units { get; }
+		object Value { get; }
+
 		void Refresh();
 		string ValueUnitString();
 		void DoGUIHorizontal();
@@ -65,11 +69,20 @@
 		public string Label { get; protected set; }
 		public string Units { get; protected set; }
 
+		object IVOID_DataValue.Value
+		{
+			get
+			{
+				return (object)this.Value;
+			}
+		}
+
 		public T Value
 		{
 			get
 			{
 				if (
+					HighLogic.LoadedSceneIsEditor ||
 					(VOID_Core.Instance.updateTimer - this.lastUpdate > VOID_Core.Instance.updatePeriod) ||
 					(this.lastUpdate > VOID_Core.Instance.updateTimer)
 				)
@@ -89,6 +102,8 @@
 			this.Units = Units;
 			this.ValueFunc = ValueFunc;
 			this.lastUpdate = 0;
+
+			VOID_Data.DataValues[this.GetHashCode()] = this;
 		}
 
 		public void Refresh()
@@ -114,6 +129,21 @@
 			GUILayout.FlexibleSpace ();
 			GUILayout.Label (this.ValueUnitString(), GUILayout.ExpandWidth (false));
 			GUILayout.EndHorizontal ();
+		}
+
+		public override int GetHashCode()
+		{
+			int hash;
+			unchecked
+			{
+				hash = 79999;
+
+				hash = hash * 104399 + this.Label.GetHashCode();
+				hash = hash * 104399 + this.ValueFunc.GetHashCode();
+				hash = hash * 104399 + this.Units.GetHashCode();
+			}
+
+			return hash;
 		}
 
 		public override string ToString()

--- 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;
@@ -69,8 +69,17 @@
 			if (_initialized)
 			{
 				_instance.StopGUI();
+				_instance.Dispose();
 				_instance = null;
 				_initialized = false;
+			}
+		}
+
+		protected override ApplicationLauncher.AppScenes appIconVisibleScenes
+		{
+			get
+			{
+				return ApplicationLauncher.AppScenes.VAB | ApplicationLauncher.AppScenes.SPH;
 			}
 		}
 
@@ -79,7 +88,7 @@
 			this._Name = "VOID Editor Core";
 		}
 
-		public new void OnGUI() {}
+		public override void OnGUI() {}
 
 		public override void DrawGUI()
 		{
@@ -90,7 +99,7 @@
 
 			Rect _iconPos = Tools.DockToWindow (this.VOIDIconPos, this.mainWindowPos);
 
-			_iconPos = Tools.ClampRectToScreen (_iconPos, (int)_iconPos.width, (int)_iconPos.height);
+			_iconPos = Tools.ClampRectToEditorPad (_iconPos);
 
 			if (_iconPos != this.VOIDIconPos)
 			{
@@ -100,11 +109,13 @@
 			base.DrawGUI();
 		}
 
-		public new void Update()
+		public override void Update()
 		{
+			this.LoadBeforeUpdate();
+
 			foreach (IVOID_EditorModule module in this.Modules)
 			{
-				if (EditorLogic.startPod == null)
+				if (EditorLogic.RootPart == null)
 				{
 					module.StopGUI();
 					continue;
@@ -119,7 +130,7 @@
 				}
 			}
 
-			if (EditorLogic.startPod == null || !HighLogic.LoadedSceneIsEditor)
+			if (EditorLogic.RootPart == null || !HighLogic.LoadedSceneIsEditor)
 			{
 				this.StopGUI();
 				return;
@@ -131,18 +142,14 @@
 
 			if (EditorLogic.SortedShipList.Count > 0 && this.vesselSimActive)
 			{
-				SimManager.Gravity = VOID_Data.KerbinGee;
-				SimManager.TryStartSimulation();
-			}
-			else if (!this.vesselSimActive)
-			{
-				SimManager.ClearResults();
+				Tools.PostDebugMessage(this, "Updating SimManager.");
+				this.UpdateSimManager();
 			}
 
 			this.CheckAndSave ();
 		}
 
-		public new void FixedUpdate() {}
+		public override void FixedUpdate() {}
 	}
 }
 

--- 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
@@ -120,60 +99,31 @@
 		{
 			this._Name = "Heads-Up Display";
 
-			this._Active.value = 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.toggleActive = true;
+
+			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();
-
-			if (SimManager.LastStage == null)
+		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;
-			}
-
-			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(SimManager.LastStage.totalMass.ToString("F3"));
+			hudString.Append(this.core.LastStage.totalMass.ToString("F3"));
 			hudString.Append('t');
 
 			hudString.Append(' ');
@@ -184,19 +134,19 @@
 			hudString.Append('\n');
 
 			hudString.Append("Total Delta-V: ");
-			hudString.Append(Tools.MuMech_ToSI(SimManager.LastStage.totalDeltaV));
+			hudString.Append(Tools.MuMech_ToSI(this.core.LastStage.totalDeltaV));
 			hudString.Append("m/s");
 
 			hudString.Append('\n');
 
 			hudString.Append("Bottom Stage Delta-V");
-			hudString.Append(Tools.MuMech_ToSI(SimManager.LastStage.deltaV));
+			hudString.Append(Tools.MuMech_ToSI(this.core.LastStage.deltaV));
 			hudString.Append("m/s");
 
 			hudString.Append('\n');
 
 			hudString.Append("Bottom Stage T/W Ratio: ");
-			hudString.Append(SimManager.LastStage.thrustToWeight.ToString("F3"));
+			hudString.Append(this.core.LastStage.thrustToWeight.ToString("F3"));
 
 			if (this.CoMmarker.gameObject.activeInHierarchy && this.CoTmarker.gameObject.activeInHierarchy)
 			{
@@ -210,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
@@ -85,31 +60,27 @@
 		{
 			this._Name = "Heads-Up Display";
 
-			this._Active.value = true;
+			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)
@@ -118,9 +89,9 @@
 
 			leftHUD = new StringBuilder();
 
-			VOID_Core.Instance.LabelStyles["hud"].alignment = TextAnchor.UpperRight;
+			VOID_Styles.labelHud.alignment = TextAnchor.UpperRight;
 
-			if (VOID_Core.Instance.powerAvailable)
+			if (this.core.powerAvailable)
 			{
 				leftHUD.AppendFormat("Primary: {0} Inc: {1}",
 					VOID_Data.primaryName.ValueUnitString(),
@@ -145,11 +116,11 @@
 			}
 			else
 			{
-				VOID_Core.Instance.LabelStyles["hud"].normal.textColor = Color.red;
+				VOID_Styles.labelHud.normal.textColor = Color.red;
 				leftHUD.Append(string.Intern("-- POWER LOST --"));
 			}
 
-			GUILayout.Label(leftHUD.ToString(), VOID_Core.Instance.LabelStyles["hud"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(leftHUD.ToString(), VOID_Styles.labelHud, GUILayout.ExpandWidth(true));
 
 			if (!this.positionsLocked)
 			{
@@ -165,9 +136,9 @@
 
 			rightHUD = new StringBuilder();
 
-			VOID_Core.Instance.LabelStyles["hud"].alignment = TextAnchor.UpperLeft;
+			VOID_Styles.labelHud.alignment = TextAnchor.UpperLeft;
 
-			if (VOID_Core.Instance.powerAvailable)
+			if (this.core.powerAvailable)
 			{
 				rightHUD.AppendFormat("Biome: {0} Sit: {1}",
 					VOID_Data.currBiome.ValueUnitString(),
@@ -189,15 +160,28 @@
 					VOID_Data.vesselHeading.ValueUnitString(),
 					VOID_Data.vesselPitch.ToSIString(2)
 				);
+
+				if (
+					this.core.vessel.mainBody == this.core.HomeBody &&
+					(
+						this.core.vessel.situation == Vessel.Situations.FLYING ||
+						this.core.vessel.situation == Vessel.Situations.SUB_ORBITAL ||
+						this.core.vessel.situation == Vessel.Situations.LANDED ||
+						this.core.vessel.situation == Vessel.Situations.SPLASHED
+					)
+				)
+				{
+					rightHUD.AppendFormat("\nRange to KSC: {0}", VOID_Data.downrangeDistance.ValueUnitString(2));
+				}
 			}
 			else
 			{
-				VOID_Core.Instance.LabelStyles["hud"].normal.textColor = Color.red;
+				VOID_Styles.labelHud.normal.textColor = Color.red;
 				rightHUD.Append(string.Intern("-- POWER LOST --"));
 			}
 
 
-			GUILayout.Label(rightHUD.ToString(), VOID_Core.Instance.LabelStyles["hud"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(rightHUD.ToString(), VOID_Styles.labelHud, GUILayout.ExpandWidth(true));
 
 			if (!this.positionsLocked)
 			{
@@ -206,69 +190,6 @@
 
 			GUI.BringWindowToBack(id);
 		}
-
-		public override void DrawGUI()
-		{
-			if (!VOID_Core.Instance.LabelStyles.ContainsKey("hud"))
-			{
-				VOID_Core.Instance.LabelStyles["hud"] = new GUIStyle(GUI.skin.label);
-			}
-
-			VOID_Core.Instance.LabelStyles["hud"].normal.textColor = textColors [ColorIndex];
-
-			if ((TimeWarp.WarpMode == TimeWarp.Modes.LOW) || (TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate))
-			{
-				SimManager.RequestSimulation();
-			}
-
-			this.leftHUDPos.value = GUI.Window(
-				VOID_Core.Instance.windowID,
-				this.leftHUDPos,
-				this.leftHUDWindow,
-				GUIContent.none,
-				GUIStyle.none
-			);
-
-			this.rightHUDPos.value = GUI.Window(
-				VOID_Core.Instance.windowID,
-				this.rightHUDPos,
-				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));
-		}
-	}
-
-	public static partial class VOID_Data
-	{
-		public static readonly VOID_StrValue expSituation = new VOID_StrValue(
-			"Situation",
-			new Func<string> (() => VOID_Core.Instance.vessel.GetExperimentSituation().HumanString())
-		);
-
-		public static readonly VOID_DoubleValue vesselPitch = new VOID_DoubleValue(
-			"Pitch",
-			() => core.vessel.getSurfacePitch(),
-			"°"
-		);
 	}
 }
 

--- 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;
 			}
 		}
 
@@ -78,21 +79,25 @@
 		{
 			this._Name = "Advanced Heads-Up Display";
 
-			this._Active.value = true;
-
-			this.leftHUDdefaultPos = new Rect(
+			this.toggleActive = true;
+
+			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;
 
@@ -105,9 +110,9 @@
 
 			leftHUD = new StringBuilder();
 
-			VOID_Core.Instance.LabelStyles["hud"].alignment = TextAnchor.UpperRight;
-
-			if (VOID_Core.Instance.powerAvailable)
+			VOID_Styles.labelHud.alignment = TextAnchor.UpperRight;
+
+			if (this.core.powerAvailable)
 			{
 				leftHUD.AppendFormat(
 					string.Intern("Mass: {0}\n"),
@@ -134,7 +139,7 @@
 					VOID_Data.vesselAngularVelocity.ToSIString(2)
 				);
 
-				if (VOID_Data.stageNominalThrust != 0)
+				if (VOID_Data.stageNominalThrust != 0d)
 				{
 					leftHUD.AppendFormat(
 						string.Intern("Thrust Offset: {0}\n"),
@@ -144,11 +149,11 @@
 			}
 			else
 			{
-				VOID_Core.Instance.LabelStyles["hud"].normal.textColor = Color.red;
+				VOID_Styles.labelHud.normal.textColor = Color.red;
 				leftHUD.Append(string.Intern("-- POWER LOST --"));
 			}
 
-			GUILayout.Label(leftHUD.ToString(), VOID_Core.Instance.LabelStyles["hud"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(leftHUD.ToString(), VOID_Styles.labelHud, GUILayout.ExpandWidth(true));
 
 			if (!this.positionsLocked)
 			{
@@ -164,9 +169,9 @@
 
 			rightHUD = new StringBuilder();
 
-			VOID_Core.Instance.LabelStyles["hud"].alignment = TextAnchor.UpperLeft;
-
-			if (VOID_Core.Instance.powerAvailable)
+			VOID_Styles.labelHud.alignment = TextAnchor.UpperLeft;
+
+			if (this.core.powerAvailable)
 			{
 				rightHUD.AppendFormat(
 					"Burn Δv (Rem/Tot): {0} / {1}\n",
@@ -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)
@@ -203,11 +208,11 @@
 			}
 			else
 			{
-				VOID_Core.Instance.LabelStyles["hud"].normal.textColor = Color.red;
+				VOID_Styles.labelHud.normal.textColor = Color.red;
 				rightHUD.Append(string.Intern("-- POWER LOST --"));
 			}
 
-			GUILayout.Label(rightHUD.ToString(), VOID_Core.Instance.LabelStyles["hud"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(rightHUD.ToString(), VOID_Styles.labelHud, GUILayout.ExpandWidth(true));
 
 			if (!this.positionsLocked)
 			{
@@ -221,7 +226,7 @@
 		{
 			if (this.primaryHUD == null)
 			{
-				foreach (IVOID_Module module in VOID_Core.Instance.Modules)
+				foreach (IVOID_Module module in this.core.Modules)
 				{
 					if (module is VOID_HUD)
 					{
@@ -229,32 +234,11 @@
 					}
 				}
 			}
-			else
-			{
-				if ((TimeWarp.WarpMode == TimeWarp.Modes.LOW) || (TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate))
-				{
-					SimManager.RequestSimulation();
-				}
-
-				this.leftHUDPos.value = GUI.Window(
-					VOID_Core.Instance.windowID,
-					this.leftHUDPos,
-					this.leftHUDWindow,
-					GUIContent.none,
-					GUIStyle.none
-				);
-
-				if (VOID_Data.upcomingManeuverNodes > 0)
-				{
-					this.rightHUDPos.value = GUI.Window(
-						VOID_Core.Instance.windowID,
-						this.rightHUDPos,
-						this.rightHUDWindow,
-						GUIContent.none,
-						GUIStyle.none
-					);
-				}
-			}
+
+			base.DrawGUI();
+
+			this.leftHUDPos.value = this.leftHUD.WindowPos;
+			this.rightHUDPos.value = this.rightHUD.WindowPos;
 		}
 
 		public override void DrawConfigurables()
@@ -264,400 +248,5 @@
 				GUILayout.ExpandWidth(false));
 		}
 	}
-
-	public static partial class VOID_Data
-	{
-		public static int upcomingManeuverNodes
-		{
-			get
-			{
-				if (core.vessel == null ||
-					core.vessel.patchedConicSolver == null ||
-					core.vessel.patchedConicSolver.maneuverNodes == null
-				)
-				{
-					return 0;
-				}
-
-				return core.vessel.patchedConicSolver.maneuverNodes.Count;
-			}
-		}
-
-		public static readonly VOID_Vector3dValue vesselThrustOffset = new VOID_Vector3dValue(
-			"Thrust Offset",
-			delegate()
-		{
-			if (core.vessel == null)
-			{
-				return Vector3d.zero;
-			}
-
-			List<PartModule> engineModules = core.vessel.getModulesOfType<PartModule>();
-
-			Vector3d thrustPos = Vector3d.zero;
-			Vector3d thrustDir = Vector3d.zero;
-			float thrust = 0;
-
-			foreach (PartModule engine in engineModules)
-			{
-				float moduleThrust = 0;
-
-				switch (engine.moduleName)
-				{
-					case "ModuleEngines":
-					case "ModuleEnginesFX":
-						break;
-					default:
-						continue;
-				}
-
-				if (!engine.isEnabled)
-				{
-					continue;
-				}
-
-				CenterOfThrustQuery cotQuery = new CenterOfThrustQuery();
-
-				if (engine is ModuleEngines)
-				{
-					ModuleEngines engineModule = engine as ModuleEngines;
-
-					moduleThrust = engineModule.finalThrust;
-
-					engineModule.OnCenterOfThrustQuery(cotQuery);
-				}
-				else // engine is ModuleEnginesFX
-				{
-					ModuleEnginesFX engineFXModule = engine as ModuleEnginesFX;
-
-					moduleThrust = engineFXModule.finalThrust;
-
-					engineFXModule.OnCenterOfThrustQuery(cotQuery);
-				}
-
-				if (moduleThrust != 0d)
-				{
-					cotQuery.thrust = moduleThrust;
-				}
-
-				thrustPos += cotQuery.pos * cotQuery.thrust;
-				thrustDir += cotQuery.dir * cotQuery.thrust;
-				thrust += cotQuery.thrust;
-			}
-
-			if (thrust != 0)
-			{
-				thrustPos /= thrust;
-				thrustDir /= thrust;
-			}
-
-			Transform vesselTransform = core.vessel.transform;
-
-			thrustPos = vesselTransform.InverseTransformPoint(thrustPos);
-			thrustDir = vesselTransform.InverseTransformDirection(thrustDir);
-
-			Vector3d thrustOffset = VectorTools.PointDistanceToLine(
-				thrustPos, thrustDir.normalized, core.vessel.findLocalCenterOfMass());
-
-			Tools.PostDebugMessage(typeof(VOID_Data), "vesselThrustOffset:\n" +
-				"\tthrustPos: {0}\n" +
-				"\tthrustDir: {1}\n" +
-				"\tthrustOffset: {2}\n" +
-				"\tvessel.CoM: {3}",
-				thrustPos,
-				thrustDir.normalized,
-				thrustOffset,
-				core.vessel.findWorldCenterOfMass()
-			);
-
-			return thrustOffset;
-		},
-			"m"
-		);
-
-		public static readonly VOID_DoubleValue vesselAccel = new VOID_DoubleValue(
-			"Acceleration",
-			() => geeForce * KerbinGee,
-			"m/s"
-		);
-
-		public static readonly VOID_IntValue vesselCrewCount = new VOID_IntValue(
-			"Crew Onboard",
-			delegate()
-			{
-				if (core.vessel != null)
-				{
-					return core.vessel.GetCrewCount();
-				}
-				else
-				{
-					return 0;
-				}
-			},
-			""
-		);
-
-		public static readonly VOID_IntValue vesselCrewCapacity = new VOID_IntValue(
-			"Crew Capacity",
-			delegate()
-		{
-			if (core.vessel != null)
-			{
-				return core.vessel.GetCrewCapacity();
-			}
-			else
-			{
-				return 0;
-			}
-		},
-			""
-		);
-
-		public static readonly VOID_DoubleValue vesselAngularVelocity = new VOID_DoubleValue(
-			"Angular Velocity",
-			delegate()
-		{
-			if (core.vessel != null)
-			{
-				return core.vessel.angularVelocity.magnitude;
-			}
-			else
-			{
-				return double.NaN;
-			}
-		},
-			"rad/s"
-		);
-
-		public static readonly VOID_DoubleValue stageNominalThrust = new VOID_DoubleValue(
-			"Nominal Stage Thrust",
-			delegate()
-		{
-			if (SimManager.LastStage == null)
-			{
-				return double.NaN;
-			}
-
-			if (SimManager.LastStage.actualThrust == 0d)
-			{
-				return SimManager.LastStage.thrust;
-			}
-			else
-			{
-				return SimManager.LastStage.actualThrust;
-			}
-		},
-			"kN"
-		);
-
-		public static readonly VOID_DoubleValue stageMassFlow = new VOID_DoubleValue(
-			"Stage Mass Flow",
-			delegate()
-			{
-				if (SimManager.LastStage == null)
-				{
-					return double.NaN;
-				}
-
-			double stageIsp = SimManager.LastStage.isp;
-			double stageThrust = stageNominalThrust;
-
-			Tools.PostDebugMessage(typeof(VOID_Data), "calculating stageMassFlow from:\n" +
-				"\tstageIsp: {0}\n" +
-				"\tstageThrust: {1}\n" +
-				"\tKerbinGee: {2}\n",
-				stageIsp,
-				stageThrust,
-				KerbinGee
-			);
-
-				return stageThrust / (stageIsp * KerbinGee);
-			},
-			"Mg/s"
-		);
-
-		public static readonly VOID_DoubleValue currManeuverDeltaV = new VOID_DoubleValue(
-			"Current Maneuver Delta-V",
-			delegate()
-			{
-				if (upcomingManeuverNodes > 0)
-				{
-				return core.vessel.patchedConicSolver.maneuverNodes[0].DeltaV.magnitude;
-				}
-				else
-				{
-					return double.NaN;
-				}
-			},
-			"m/s"
-		);
-
-		public static readonly VOID_DoubleValue currManeuverDVRemaining = new VOID_DoubleValue(
-			"Remaining Maneuver Delta-V",
-			delegate()
-			{
-				if (upcomingManeuverNodes > 0)
-				{
-					return core.vessel.patchedConicSolver.maneuverNodes[0].GetBurnVector(core.vessel.orbit).magnitude;
-				}
-				else
-				{
-					return double.NaN;
-				}
-			},
-			"m/s"
-		);
-
-		public static readonly VOID_DoubleValue nextManeuverDeltaV = new VOID_DoubleValue(
-			"Current Maneuver Delta-V",
-			delegate()
-		{
-			if (upcomingManeuverNodes > 1)
-			{
-				return core.vessel.patchedConicSolver.maneuverNodes[1].DeltaV.magnitude;
-			}
-			else
-			{
-				return double.NaN;
-			}
-		},
-			"m/s"
-		);
-
-		public static readonly VOID_DoubleValue currentNodeBurnDuration = new VOID_DoubleValue(
-			"Total Burn Time",
-			delegate()
-			{
-				if (SimManager.LastStage == null || currManeuverDeltaV.Value == double.NaN)
-				{
-					return double.NaN;
-				}
-			    
-				double stageThrust = stageNominalThrust;
-
-				return burnTime(currManeuverDeltaV.Value, totalMass, stageMassFlow, stageThrust);
-			},
-			"s"
-		);
-
-		public static readonly VOID_DoubleValue currentNodeBurnRemaining = new VOID_DoubleValue(
-			"Burn Time Remaining",
-			delegate()
-			{
-				if (SimManager.LastStage == null || currManeuverDVRemaining == double.NaN)
-				{
-					return double.NaN;
-				}
-
-				double stageThrust = stageNominalThrust;
-
-				return burnTime(currManeuverDVRemaining, totalMass, stageMassFlow, stageThrust);
-			},
-			"s"
-		);
-
-		public static readonly VOID_DoubleValue currentNodeHalfBurnDuration = new VOID_DoubleValue(
-			"Half Burn Time",
-			delegate()
-		{
-			if (SimManager.LastStage == null || currManeuverDeltaV.Value == double.NaN)
-			{
-				return double.NaN;
-			}
-
-			double stageThrust = stageNominalThrust;
-
-			return burnTime(currManeuverDeltaV.Value / 2d, totalMass, stageMassFlow, stageThrust);
-		},
-			"s"
-		);
-
-		public static readonly VOID_StrValue burnTimeDoneAtNode = new VOID_StrValue(
-			"Full burn time to be half done at node",
-			delegate()
-		{
-			if (SimManager.LastStage == null && upcomingManeuverNodes < 1)
-			{
-				return "N/A";
-			}
-
-			ManeuverNode node = core.vessel.patchedConicSolver.maneuverNodes[0];
-
-			if ((node.UT - Planetarium.GetUniversalTime()) < 0)
-			{
-				return string.Empty;
-			}
-
-			double interval = (node.UT - currentNodeBurnDuration) - Planetarium.GetUniversalTime();
-
-			int sign = Math.Sign(interval);
-			interval = Math.Abs(interval);
-
-			string format;
-
-			if (sign >= 0)
-			{
-				format = string.Intern("T - {0}");
-			}
-			else
-			{
-				format = string.Intern("T + {0}");
-			}
-
-			return string.Format(format, VOID_Tools.ConvertInterval(interval));
-		}
-		);
-
-		public static readonly VOID_StrValue burnTimeHalfDoneAtNode = new VOID_StrValue(
-			"Full burn time to be half done at node",
-			delegate()
-			{
-			if (SimManager.LastStage == null && upcomingManeuverNodes < 1)
-			{
-				return "N/A";
-			}
-
-			ManeuverNode node = core.vessel.patchedConicSolver.maneuverNodes[0];
-
-			if ((node.UT - Planetarium.GetUniversalTime()) < 0)
-			{
-				return string.Empty;
-			}
-
-			double interval = (node.UT - currentNodeHalfBurnDuration) - Planetarium.GetUniversalTime();
-
-			int sign = Math.Sign(interval);
-			interval = Math.Abs(interval);
-
-			string format;
-
-			if (sign >= 0)
-			{
-				format = string.Intern("T - {0}");
-			}
-			else
-			{
-				format = string.Intern("T + {0}");
-			}
-
-			return string.Format(format, VOID_Tools.ConvertInterval(interval));
-			}
-		);
-
-		private static double burnTime(double deltaV, double initialMass, double massFlow, double thrust)
-		{
-			Tools.PostDebugMessage(typeof(VOID_Data), "calculating burnTime from:\n" +
-				"\tdeltaV: {0}\n" +
-				"\tinitialMass: {1}\n" +
-				"\tmassFlow: {2}\n" +
-				"\tthrust: {3}\n",
-				deltaV,
-				initialMass,
-				massFlow,
-				thrust
-			);
-			return initialMass / massFlow * (Math.Exp(deltaV * massFlow / thrust) - 1d);
-		}
-	}
 }
 

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
@@ -55,6 +55,11 @@
 		{
 			get
 			{
+				if (HighLogic.LoadedSceneIsEditor)
+				{
+					return VOID_EditorCore.Instance as VOID_Core;
+				}
+
 				return VOID_Core.Instance;
 			}
 		}
@@ -210,68 +215,93 @@
 	public abstract class VOID_WindowModule : VOID_Module
 	{
 		[AVOID_SaveValue("WindowPos")]
-		protected Rect WindowPos = new Rect(Screen.width / 2, Screen.height / 2, 250f, 50f);
-		protected float defWidth = 250f;
-		protected float defHeight = 50f;
-
-		public virtual void ModuleWindow(int _)
-		{
-//			if (VOID_Core.Instance.updateTimer - this.lastUpdate > VOID_Core.Instance.updatePeriod) {
-//				foreach (var fieldinfo in this.GetType().GetFields(
-//					BindingFlags.Instance |
-//					BindingFlags.NonPublic |
-//					BindingFlags.Public |
-//					BindingFlags.FlattenHierarchy
-//				))
-//				{
-//					object field = null;
-//
-//					try
-//					{
-//						field = fieldinfo.GetValue (this);
-//					}
-//					catch (NullReferenceException) {
-//						Tools.PostDebugMessage(string.Format(
-//							"{0}: caught NullReferenceException, could not get value for field {1}.",
-//							this.GetType().Name,
-//							fieldinfo.Name
-//						));
-//					}
-//
-//					if (field == null) {
-//						continue;
-//					}
-//
-//					if (typeof(IVOID_DataValue).IsAssignableFrom (field.GetType ())) {
-//						(field as IVOID_DataValue).Refresh ();
-//					}
-//				}
-//
-//				this.lastUpdate = VOID_Core.Instance.updateTimer;
-//			}
-		}
+		protected Rect WindowPos;
+		protected float defWidth;
+		protected float defHeight;
+
+		protected string inputLockName;
+
+		public VOID_WindowModule() : base()
+		{
+			this.defWidth = 250f;
+			this.defHeight = 50f;
+
+			this.inputLockName = string.Concat(this.Name, "_edlock");
+
+			this.WindowPos = new Rect(Screen.width / 2, Screen.height / 2, this.defWidth, this.defHeight);
+		}
+
+		public abstract void ModuleWindow(int _);
 
 		public override void DrawGUI()
 		{
-			GUI.skin = VOID_Core.Instance.Skin;
+			GUI.skin = this.core.Skin;
 
 			Rect _Pos = this.WindowPos;
 
 			_Pos = GUILayout.Window(
-				VOID_Core.Instance.windowID,
+				this.core.windowID,
 				_Pos,
-				this.ModuleWindow,
+				VOID_Tools.GetWindowHandler(this.ModuleWindow),
 				this.Name,
 				GUILayout.Width(this.defWidth),
 				GUILayout.Height(this.defHeight)
 			);
 
-			_Pos = Tools.ClampRectToScreen (_Pos);
+			bool cursorInWindow = _Pos.Contains(Mouse.screenPos);
+
+			switch (HighLogic.LoadedScene)
+			{
+				case GameScenes.EDITOR:
+					if (cursorInWindow)
+					{
+						InputLockManager.SetControlLock(
+							ControlTypes.EDITOR_ICON_HOVER | ControlTypes.EDITOR_ICON_PICK |
+							ControlTypes.EDITOR_PAD_PICK_COPY | ControlTypes.EDITOR_PAD_PICK_COPY,
+							this.inputLockName
+						);
+						EditorLogic.fetch.Lock(false, false, false, this.inputLockName);
+					}
+					else
+					{
+						EditorLogic.fetch.Unlock(this.inputLockName);
+					}
+					break;
+				case GameScenes.FLIGHT:
+					if (cursorInWindow)
+					{
+						InputLockManager.SetControlLock(ControlTypes.CAMERACONTROLS, this.inputLockName);
+					}
+					else if (InputLockManager.GetControlLock(this.inputLockName) != ControlTypes.None)
+					{
+						InputLockManager.RemoveControlLock(this.inputLockName);
+					}
+					break;
+				case GameScenes.SPACECENTER:
+					if (cursorInWindow)
+					{
+						InputLockManager.SetControlLock(ControlTypes.KSC_FACILITIES, this.inputLockName);
+					}
+					else if (InputLockManager.GetControlLock(this.inputLockName) != ControlTypes.None)
+					{
+						InputLockManager.RemoveControlLock(this.inputLockName);
+					}
+					break;
+			}
+
+			if (HighLogic.LoadedSceneIsEditor)
+			{
+				_Pos = Tools.ClampRectToEditorPad(_Pos);
+			}
+			else
+			{
+				_Pos = Tools.ClampRectToScreen(_Pos);
+			}
 
 			if (_Pos != this.WindowPos)
 			{
 				this.WindowPos = _Pos;
-				VOID_Core.Instance.configDirty = true;
+				this.core.configDirty = true;
 			}
 		}
 	}

--- a/VOID_Orbital.cs
+++ b/VOID_Orbital.cs
@@ -52,8 +52,6 @@
 
 		public override void ModuleWindow(int _)
 		{
-			base.ModuleWindow (_);
-
 			int idx = 0;
 
             GUILayout.BeginVertical();
@@ -123,128 +121,6 @@
 			base._SaveToConfig (config);
 		}
 	}
-
-
-	public static partial class VOID_Data
-	{
-		public static readonly VOID_StrValue primaryName = new VOID_StrValue (
-			VOID_Localization.void_primary,
-			delegate()
-		{
-			if (VOID_Core.Instance.vessel == null)
-			{
-				return string.Empty;
-			}
-			return VOID_Core.Instance.vessel.mainBody.name;
-		}
-		);
-
-		public static readonly VOID_DoubleValue orbitAltitude = new VOID_DoubleValue (
-			"Altitude (ASL)",
-			new Func<double> (() => VOID_Core.Instance.vessel.orbit.altitude),
-			"m"
-		);
-
-		public static readonly VOID_DoubleValue orbitVelocity = new VOID_DoubleValue (
-			VOID_Localization.void_velocity,
-			new Func<double> (() => VOID_Core.Instance.vessel.orbit.vel.magnitude),
-			"m/s"
-		);
-
-		public static readonly VOID_DoubleValue orbitApoAlt = new VOID_DoubleValue(
-			VOID_Localization.void_apoapsis,
-			new Func<double>(() => VOID_Core.Instance.vessel.orbit.ApA),
-			"m"
-		);
-
-		public static readonly VOID_DoubleValue oribtPeriAlt = new VOID_DoubleValue(
-			VOID_Localization.void_periapsis,
-			new Func<double>(() => VOID_Core.Instance.vessel.orbit.PeA),
-			"m"
-		);
-
-		public static readonly VOID_StrValue timeToApo = new VOID_StrValue(
-			"Time to Apoapsis",
-			new Func<string>(() => VOID_Tools.ConvertInterval(VOID_Core.Instance.vessel.orbit.timeToAp))
-		);
-
-		public static readonly VOID_StrValue timeToPeri = new VOID_StrValue(
-			"Time to Periapsis",
-			new Func<string>(() => VOID_Tools.ConvertInterval(VOID_Core.Instance.vessel.orbit.timeToPe))
-		);
-
-		public static readonly VOID_DoubleValue orbitInclination = new VOID_DoubleValue(
-			"Inclination",
-			new Func<double>(() => VOID_Core.Instance.vessel.orbit.inclination),
-			"°"
-		);
-
-		public static readonly VOID_DoubleValue gravityAccel = new VOID_DoubleValue(
-			"Gravity",
-			delegate()
-		{
-			double orbitRadius = VOID_Core.Instance.vessel.mainBody.Radius +
-				VOID_Core.Instance.vessel.mainBody.GetAltitude(VOID_Core.Instance.vessel.findWorldCenterOfMass());
-			return (VOID_Core.Constant_G * VOID_Core.Instance.vessel.mainBody.Mass) /
-				(orbitRadius * orbitRadius);
-		},
-			"m/s²"
-		);
-
-		public static readonly VOID_StrValue orbitPeriod = new VOID_StrValue(
-			"Period",
-			new Func<string>(() => VOID_Tools.ConvertInterval(VOID_Core.Instance.vessel.orbit.period))
-		);
-
-		public static readonly VOID_DoubleValue semiMajorAxis = new VOID_DoubleValue(
-			"Semi-Major Axis",
-			new Func<double>(() => VOID_Core.Instance.vessel.orbit.semiMajorAxis),
-			"m"
-		);
-
-		public static readonly VOID_DoubleValue eccentricity = new VOID_DoubleValue(
-			"Eccentricity",
-			new Func<double>(() => VOID_Core.Instance.vessel.orbit.eccentricity),
-			""
-		);
-
-		public static readonly VOID_DoubleValue meanAnomaly = new VOID_DoubleValue(
-			"Mean Anomaly",
-			new Func<double>(() => VOID_Core.Instance.vessel.orbit.meanAnomaly * 180d / Math.PI),
-			"°"
-		);
-
-		public static readonly VOID_DoubleValue trueAnomaly = new VOID_DoubleValue(
-			"True Anomaly",
-			new Func<double>(() => VOID_Core.Instance.vessel.orbit.trueAnomaly),
-			"°"
-		);
-
-		public static readonly VOID_DoubleValue eccAnomaly = new VOID_DoubleValue(
-			"Eccentric Anomaly",
-			new Func<double>(() => VOID_Core.Instance.vessel.orbit.eccentricAnomaly * 180d / Math.PI),
-			"°"
-		);
-
-		public static readonly VOID_DoubleValue longitudeAscNode = new VOID_DoubleValue(
-			"Long. Ascending Node",
-			new Func<double>(() => VOID_Core.Instance.vessel.orbit.LAN),
-			"°"
-		);
-
-		public static readonly VOID_DoubleValue argumentPeriapsis = new VOID_DoubleValue(
-			"Argument of Periapsis",
-			new Func<double>(() => VOID_Core.Instance.vessel.orbit.argumentOfPeriapsis),
-			"°"
-		);
-
-		public static readonly VOID_DoubleValue localSiderealLongitude = new VOID_DoubleValue(
-			"Local Sidereal Longitude",
-			new Func<double>(() => VOID_Tools.FixDegreeDomain(
-				VOID_Core.Instance.vessel.longitude + VOID_Core.Instance.vessel.orbit.referenceBody.rotationAngle)),
-			"°"
-		);
-	}
 }
 
 

--- a/VOID_Rendezvous.cs
+++ b/VOID_Rendezvous.cs
@@ -60,14 +60,14 @@
 
 			if (this.RegisterModule == null)
 			{
-				this.RegisterModule = VOID_Core.Instance.Modules.Where(m => typeof(VOID_VesselRegister).IsAssignableFrom(m.GetType())).FirstOrDefault() as VOID_VesselRegister;
+				this.RegisterModule = this.core.Modules.Where(m => typeof(VOID_VesselRegister).IsAssignableFrom(m.GetType())).FirstOrDefault() as VOID_VesselRegister;
 			}
 
 			GUILayout.BeginVertical();
 
 			//display both
 			//Show Target Info
-			GUILayout.Label("Target:", VOID_Core.Instance.LabelStyles["center_bold"]);
+			GUILayout.Label("Target:", VOID_Styles.labelCenterBold);
 			if (FlightGlobals.fetch.VesselTarget != null)
 			{
 				//a KSP Target (body or vessel) is selected
@@ -94,13 +94,13 @@
 			else
 			{
 				//no KSP Target selected
-				GUILayout.Label("No Target Selected", VOID_Core.Instance.LabelStyles["center_bold"]);
+				GUILayout.Label("No Target Selected", VOID_Styles.labelCenterBold);
 			}
 
 			//Show Vessel Register vessel info
 			if (untoggleRegisterInfo == false && this.RegisterModule != default(IVOID_Module))
 			{
-				GUILayout.Label("Vessel Register:", VOID_Core.Instance.LabelStyles["center_bold"]);
+				GUILayout.Label("Vessel Register:", VOID_Styles.labelCenterBold);
 				if (this.RegisterModule.selectedVessel != null)
 				{
 					rendezvessel = this.RegisterModule.selectedVessel;
@@ -122,7 +122,7 @@
 				{
 					//vesreg Vessel is null
 					//targ = null;
-					GUILayout.Label("No Vessel Selected", VOID_Core.Instance.LabelStyles["center_bold"]);
+					GUILayout.Label("No Vessel Selected", VOID_Styles.labelCenterBold);
 				}
 			}
 
@@ -130,7 +130,7 @@
 
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 			GUILayout.Label(" ", GUILayout.ExpandWidth(true));
-			if (GUILayout.Button("Close", GUILayout.ExpandWidth(false))) this._Active = false;
+			if (GUILayout.Button("Close", GUILayout.ExpandWidth(false))) this.toggleActive = false;
 			GUILayout.EndHorizontal();
 
 			GUILayout.EndVertical();
@@ -143,7 +143,7 @@
 			{
 				//Display vessel rendezvous info
 				GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
-				GUILayout.Label(v.vesselName, VOID_Core.Instance.LabelStyles["center_bold"], GUILayout.ExpandWidth(true));
+				GUILayout.Label(v.vesselName, VOID_Styles.labelCenterBold, GUILayout.ExpandWidth(true));
 				GUILayout.EndHorizontal();
 
 				if (v.situation == Vessel.Situations.ESCAPING || v.situation == Vessel.Situations.FLYING || v.situation == Vessel.Situations.ORBITING || v.situation == Vessel.Situations.SUB_ORBITAL)
@@ -196,7 +196,7 @@
 					// Toadicus edit: added local sidereal longitude.
 					GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 					GUILayout.Label("Local Sidereal Longitude:");
-					GUILayout.Label(LSL.ToString("F3") + "°", VOID_Core.Instance.LabelStyles["right"]);
+					GUILayout.Label(LSL.ToString("F3") + "°", VOID_Styles.labelRight);
 					GUILayout.EndHorizontal();
 
 					toggleExtendedOrbital.value = GUILayout.Toggle(toggleExtendedOrbital, "Extended info");
@@ -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));
@@ -267,7 +267,7 @@
 			else if (cb != null && v == null)
 			{
 				//Display CelstialBody rendezvous info
-				GUILayout.Label(cb.bodyName, VOID_Core.Instance.LabelStyles["center_bold"]);
+				GUILayout.Label(cb.bodyName, VOID_Styles.labelCenterBold);
 
 				GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 				GUILayout.Label("Ap/Pe:");

file:b/VOID_StageInfo.cs (new)
--- /dev/null
+++ b/VOID_StageInfo.cs
@@ -1,1 +1,218 @@
-
+// VOID © 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/
+
+using KerbalEngineer.VesselSimulator;
+using KSP;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using ToadicusTools;
+using UnityEngine;
+
+namespace VOID
+{
+	public class VOID_StageInfo : VOID_WindowModule
+	{
+		private Table stageTable;
+
+		private Table.Column<int> stageNumberCol;
+		private Table.Column<double> stageDeltaVCol;
+		private Table.Column<double> stageTotalDVCol;
+		private Table.Column<double> stageInvertDVCol;
+		private Table.Column<double> stageMassCol;
+		private Table.Column<double> stageTotalMassCol;
+		private Table.Column<double> stageThrustCol;
+		private Table.Column<double> stageTWRCol;
+
+		private bool stylesApplied;
+		private bool showBodyList;
+
+		private Rect bodyListPos;
+
+		private CelestialBody selectedBody;
+		[AVOID_SaveValue("bodyIdx")]
+		private VOID_SaveValue<int> bodyIdx;
+		private int lastIdx;
+
+		public VOID_StageInfo() : base()
+		{
+			this._Name = "Stage Information";
+			this.defWidth = 200f;
+			this.bodyIdx = 4;
+
+			this.stylesApplied = false;
+			this.showBodyList = false;
+
+			this.bodyListPos = new Rect();
+
+			this.stageTable = new Table();
+
+			this.stageNumberCol = new Table.Column<int>("Stage", 40f);
+			this.stageTable.Add(this.stageNumberCol);
+
+			this.stageDeltaVCol = new Table.Column<double>("DeltaV [m/s]", 60f);
+			this.stageDeltaVCol.Format = "S2";
+			this.stageTable.Add(this.stageDeltaVCol);
+
+			this.stageTotalDVCol = new Table.Column<double>("Total ΔV [m/s]", 60f);
+			this.stageTotalDVCol.Format = "S2";
+			this.stageTable.Add(this.stageTotalDVCol);
+
+			this.stageInvertDVCol = new Table.Column<double>("Invert ΔV [m/s]", 60f);
+			this.stageInvertDVCol.Format = "S2";
+			this.stageTable.Add(this.stageInvertDVCol);
+
+			this.stageMassCol = new Table.Column<double>("Mass [Mg]", 60f);
+			this.stageMassCol.Format = "#.#";
+			this.stageTable.Add(this.stageMassCol);
+
+			this.stageTotalMassCol = new Table.Column<double>("Total [Mg]", 60f);
+			this.stageTotalMassCol.Format = "#.#";
+			this.stageTable.Add(this.stageTotalMassCol);
+
+			this.stageThrustCol = new Table.Column<double>("Thrust [N]", 60f);
+			this.stageThrustCol.Format = "S2";
+			this.stageTable.Add(this.stageThrustCol);
+
+			this.stageTWRCol = new Table.Column<double>("T/W Ratio", 60f);
+			this.stageTWRCol.Format = "#.#";
+			this.stageTable.Add(this.stageTWRCol);
+		}
+
+		public override void DrawGUI()
+		{
+			base.DrawGUI();
+
+			if (this.showBodyList)
+			{
+				GUILayout.Window(core.windowID, this.bodyListPos, this.BodyPickerWindow, string.Empty);
+			}
+		}
+
+		public override void ModuleWindow(int _)
+		{
+			if (
+				HighLogic.LoadedSceneIsEditor ||
+				(TimeWarp.WarpMode == TimeWarp.Modes.LOW) ||
+				(TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate)
+			)
+			{
+				KerbalEngineer.VesselSimulator.SimManager.RequestSimulation();
+			}
+
+			if (!this.stylesApplied)
+			{
+				this.stageTable.ApplyCellStyle(VOID_Styles.labelCenter);
+				this.stageTable.ApplyHeaderStyle(VOID_Styles.labelCenterBold);
+			}
+
+			this.stageTable.ClearColumns();
+
+			if (core.Stages == null || core.Stages.Length == 0)
+			{
+				GUILayout.BeginVertical();
+
+				GUILayout.Label("No stage data!");
+
+				GUILayout.EndVertical();
+
+				return;
+			}
+
+			foreach (Stage stage in core.Stages)
+			{
+				if (stage.deltaV == 0 && stage.mass == 0)
+				{
+					continue;
+				}
+
+				this.stageNumberCol.Add(stage.number);
+
+				this.stageDeltaVCol.Add(stage.deltaV);
+				this.stageTotalDVCol.Add(stage.totalDeltaV);
+				this.stageInvertDVCol.Add(stage.inverseTotalDeltaV);
+
+				this.stageMassCol.Add(stage.mass);
+				this.stageTotalMassCol.Add(stage.totalMass);      
+
+				this.stageThrustCol.Add(stage.thrust * 1000f);
+				this.stageTWRCol.Add(stage.thrustToWeight / (this.selectedBody ?? core.HomeBody).GeeASL);
+			}
+
+			this.stageTable.Render();
+
+			if (core.sortedBodyList != null)
+			{
+				GUILayout.BeginHorizontal();
+
+				if (GUILayout.Button("◄"))
+				{
+					this.bodyIdx--;
+				}
+
+				this.showBodyList = GUILayout.Toggle(this.showBodyList, (this.selectedBody ?? core.HomeBody).bodyName, GUI.skin.button);
+				Rect bodyButtonPos = GUILayoutUtility.GetLastRect();
+
+				if (Event.current.type == EventType.Repaint)
+				{
+					this.bodyListPos.width = bodyButtonPos.width;
+					this.bodyListPos.x = bodyButtonPos.xMin + this.WindowPos.xMin;
+					this.bodyListPos.y = bodyButtonPos.yMax + this.WindowPos.yMin;
+				}
+
+				if (GUILayout.Button("►"))
+				{
+					this.bodyIdx++;
+				}
+
+				this.bodyIdx %= core.sortedBodyList.Count;
+
+				if (this.bodyIdx < 0)
+				{
+					this.bodyIdx += core.sortedBodyList.Count;
+				}
+
+				if (this.lastIdx != this.bodyIdx)
+				{
+					this.lastIdx = this.bodyIdx;
+					this.selectedBody = core.sortedBodyList[this.bodyIdx];
+				}
+
+				GUILayout.EndHorizontal();
+			}
+
+			GUILayout.BeginHorizontal();
+
+			if (
+				GUILayout.Button("Engineering data powered by <i>VesselSimulator from KER</i>.",
+					VOID_Styles.labelLink)
+			)
+			{
+				Application.OpenURL("http://forum.kerbalspaceprogram.com/threads/18230");
+			}
+
+			GUILayout.EndHorizontal();
+
+			GUI.DragWindow();
+		}
+
+		private void BodyPickerWindow(int _)
+		{
+			foreach (CelestialBody body in core.sortedBodyList)
+			{
+				if (GUILayout.Button(body.bodyName, VOID_Styles.labelDefault))
+				{
+					Debug.Log("Picked new body focus: " + body.bodyName);
+					this.bodyIdx = core.sortedBodyList.IndexOf(body);
+					this.showBodyList = false;
+				}
+			}
+		}
+	}
+
+	public class VOID_StageInfoEditor : VOID_StageInfo, IVOID_EditorModule {}
+}
+
+

file:b/VOID_Styles.cs (new)
--- /dev/null
+++ b/VOID_Styles.cs
@@ -1,1 +1,129 @@
+// VOID
+//
+// 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 System;
+using UnityEngine;
+
+namespace VOID
+{
+	public static class VOID_Styles
+	{
+		public static bool Ready
+		{
+			get;
+			private set;
+		}
+
+		public static GUIStyle labelDefault
+		{
+			get;
+			private set;
+		}
+
+		public static GUIStyle labelLink
+		{
+			get;
+			private set;
+		}
+
+		public static GUIStyle labelCenter
+		{
+			get;
+			private set;
+		}
+
+		public static GUIStyle labelCenterBold
+		{
+			get;
+			private set;
+		}
+
+		public static GUIStyle labelGreen
+		{
+			get;
+			private set;
+		}
+
+		public static GUIStyle labelHud
+		{
+			get;
+			private set;
+		}
+
+		public static GUIStyle labelRight
+		{
+			get;
+			private set;
+		}
+
+		public static GUIStyle labelRed
+		{
+			get;
+			private set;
+		}
+
+		public static void OnSkinChanged()
+		{
+			labelDefault = new GUIStyle(GUI.skin.label);
+
+			labelLink = new GUIStyle(GUI.skin.label);
+			labelLink.fontStyle = FontStyle.Italic;
+			labelLink.fontSize = (int)((float)labelLink.fontSize * .8f);
+
+			labelCenter = new GUIStyle(GUI.skin.label);
+			labelCenter.normal.textColor = Color.white;
+			labelCenter.alignment = TextAnchor.UpperCenter;
+
+			labelCenterBold = new GUIStyle(GUI.skin.label);
+			labelCenterBold.normal.textColor = Color.white;
+			labelCenterBold.alignment = TextAnchor.UpperCenter;
+			labelCenterBold.fontStyle = FontStyle.Bold;
+
+			labelHud = new GUIStyle(labelDefault);
+
+			labelRight = new GUIStyle(GUI.skin.label);
+			labelRight.normal.textColor = Color.white;
+			labelRight.alignment = TextAnchor.UpperRight;
+
+			labelRed = new GUIStyle(GUI.skin.label);
+			labelRed.normal.textColor = Color.red;
+
+			labelGreen = new GUIStyle(GUI.skin.label);
+			labelGreen.normal.textColor = Color.green;
+
+			Ready = true;
+		}
+
+		static VOID_Styles()
+		{
+			Ready = false;
+		}
+	}
+}
+
+

--- a/VOID_SurfAtmo.cs
+++ b/VOID_SurfAtmo.cs
@@ -49,8 +49,6 @@
 
 		public override void ModuleWindow(int _)
 		{
-			base.ModuleWindow (_);
-
 			int idx = 0;
 
 			GUILayout.BeginVertical();
@@ -67,6 +65,9 @@
 			this.precisionValues [idx]= (ushort)VOID_Data.terrainElevation.DoGUIHorizontal (this.precisionValues [idx]);
 			idx++;
 
+			this.precisionValues[idx] = (ushort)VOID_Data.downrangeDistance.DoGUIHorizontal(this.precisionValues[idx]);
+			idx++;
+
 			this.precisionValues [idx]= (ushort)VOID_Data.surfVelocity.DoGUIHorizontal (this.precisionValues [idx]);
 			idx++;
 
@@ -78,7 +79,8 @@
 
 			VOID_Data.temperature.DoGUIHorizontal ("F2");
 
-			VOID_Data.atmDensity.DoGUIHorizontal (3);
+			this.precisionValues [idx]= (ushort)VOID_Data.atmDensity.DoGUIHorizontal (this.precisionValues [idx]);
+			idx++;
 
 			VOID_Data.atmPressure.DoGUIHorizontal ("F2");
 
@@ -106,100 +108,4 @@
 			base._SaveToConfig (config);
 		}
 	}
-
-	public static partial class VOID_Data
-	{
-		public static readonly VOID_DoubleValue trueAltitude = new VOID_DoubleValue(
-			"Altitude (true)",
-			delegate()
-			{
-				double alt_true = VOID_Core.Instance.vessel.orbit.altitude - VOID_Core.Instance.vessel.terrainAltitude;
-				// HACK: This assumes that on worlds with oceans, all water is fixed at 0 m,
-				// and water covers the whole surface at 0 m.
-				if (VOID_Core.Instance.vessel.terrainAltitude < 0 && VOID_Core.Instance.vessel.mainBody.ocean )
-					alt_true = VOID_Core.Instance.vessel.orbit.altitude;
-				return alt_true;
-			},
-			"m"
-		);
-
-		public static readonly VOID_StrValue surfLatitude = new VOID_StrValue(
-			"Latitude",
-			new Func<string> (() => VOID_Tools.GetLatitudeString(VOID_Core.Instance.vessel))
-		);
-
-		public static readonly VOID_StrValue surfLongitude = new VOID_StrValue(
-			"Longitude",
-			new Func<string> (() => VOID_Tools.GetLongitudeString(VOID_Core.Instance.vessel))
-		);
-
-		public static readonly VOID_StrValue vesselHeading = new VOID_StrValue(
-			"Heading",
-			delegate()
-		{
-			double heading = core.vessel.getSurfaceHeading();
-			string cardinal = VOID_Tools.get_heading_text(heading);
-
-			return string.Format(
-				"{0}° {1}",
-				heading.ToString("F2"),
-				cardinal
-			);
-		}
-		);
-
-		public static readonly VOID_DoubleValue terrainElevation = new VOID_DoubleValue(
-			"Terrain elevation",
-			new Func<double> (() => VOID_Core.Instance.vessel.terrainAltitude),
-			"m"
-		);
-
-		public static readonly VOID_DoubleValue surfVelocity = new VOID_DoubleValue(
-			"Surface velocity",
-			new Func<double> (() => VOID_Core.Instance.vessel.srf_velocity.magnitude),
-			"m/s"
-		);
-
-		public static readonly VOID_DoubleValue vertVelocity = new VOID_DoubleValue(
-			"Vertical speed",
-			new Func<double> (() => VOID_Core.Instance.vessel.verticalSpeed),
-			"m/s"
-		);
-
-		public static readonly VOID_DoubleValue horzVelocity = new VOID_DoubleValue(
-			"Horizontal speed",
-			new Func<double> (() => VOID_Core.Instance.vessel.horizontalSrfSpeed),
-			"m/s"
-		);
-
-		public static readonly VOID_FloatValue temperature = new VOID_FloatValue(
-			"Temperature",
-			new Func<float> (() => VOID_Core.Instance.vessel.flightIntegrator.getExternalTemperature()),
-			"°C"
-		);
-
-		public static readonly VOID_DoubleValue atmDensity = new VOID_DoubleValue (
-			"Atmosphere Density",
-			new Func<double> (() => VOID_Core.Instance.vessel.atmDensity * 1000f),
-			"g/m³"
-		);
-
-		public static readonly VOID_DoubleValue atmPressure = new VOID_DoubleValue (
-			"Pressure",
-			new Func<double> (() => VOID_Core.Instance.vessel.staticPressure),
-			"atm"
-		);
-
-		public static readonly VOID_FloatValue atmLimit = new VOID_FloatValue(
-			"Atmosphere Limit",
-			new Func<float> (() => VOID_Core.Instance.vessel.mainBody.maxAtmosphereAltitude),
-			"m"
-		);
-
-		public static readonly VOID_StrValue currBiome = new VOID_StrValue(
-			"Biome",
-			new Func<string> (() => VOID_Tools.GetBiome(VOID_Core.Instance.vessel).name)
-		);
-
-	}
 }

file:b/VOID_TWR.cs (new)
--- /dev/null
+++ b/VOID_TWR.cs
@@ -1,1 +1,69 @@
+// VOID © 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/
 
+using KSP;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using ToadicusTools;
+using UnityEngine;
+
+namespace VOID
+{
+	public class VOID_TWR : VOID_WindowModule
+	{
+		public VOID_TWR() : base()
+		{
+			this._Name = "IP Thrust-to-Weight Ratios";
+		}
+
+		public override void ModuleWindow(int _)
+		{
+			if (
+				HighLogic.LoadedSceneIsEditor ||
+				(TimeWarp.WarpMode == TimeWarp.Modes.LOW) ||
+				(TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate)
+			)
+			{
+				KerbalEngineer.VesselSimulator.SimManager.RequestSimulation();
+			}
+
+			GUILayout.BeginVertical();
+
+			if (core.sortedBodyList == null)
+			{
+				GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
+
+				GUILayout.Label("Unavailable");
+
+				GUILayout.EndHorizontal();
+			}
+			else
+			{
+				foreach (CelestialBody body in core.sortedBodyList)
+				{
+					GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
+
+					GUILayout.Label(body.bodyName);
+					GUILayout.FlexibleSpace();
+					GUILayout.Label(
+						(VOID_Data.nominalThrustWeight.Value / body.GeeASL).ToString("0.0##"),
+						GUILayout.ExpandWidth(true)
+					);
+
+					GUILayout.EndHorizontal();
+				}
+			}
+
+			GUILayout.EndVertical();
+
+			GUI.DragWindow();
+		}
+	}
+
+	public class VOID_EditorTWR : VOID_TWR, IVOID_EditorModule {}
+}
+
+

--- a/VOID_Tools.cs
+++ b/VOID_Tools.cs
@@ -28,20 +28,80 @@
 
 using KSP;
 using System;
+using System.Collections.Generic;
+using ToadicusTools;
 using UnityEngine;
 
 namespace VOID
 {
 	public static partial class VOID_Tools
 	{
+		#region CelestialBody Utilities
+		public static bool hasAncestor(this CelestialBody bodyA, CelestialBody bodyB)
+		{
+			if (bodyA == null || bodyB == null)
+			{
+				return false;
+			}
+
+			while (bodyA.orbitDriver != null)
+			{
+				if (bodyA.orbit.referenceBody == bodyB)
+				{
+					return true;
+				}
+
+				bodyA = bodyA.orbit.referenceBody;
+			} 
+
+			return false;
+		}
+
+		public static bool NearestRelatedParents(ref CelestialBody bodyA, ref CelestialBody bodyB)
+		{
+			if (bodyA == null || bodyB == null || bodyA.orbitDriver == null || bodyB.orbitDriver == null)
+			{
+				throw new ArgumentException(string.Concat(
+					"CelestialBody::FindRelatedParents: ",
+					"Neither body may be null, and both bodies must have orbits."
+				));
+			}
+
+			CelestialBody a, b;
+
+			a = bodyA;
+
+			while (bodyA.orbitDriver != null)
+			{
+				b = bodyB;
+
+				while (b.orbitDriver != null)
+				{
+					if (a.orbit.referenceBody == b.orbit.referenceBody)
+					{
+						bodyA = a;
+						bodyB = b;
+						return true;
+					}
+
+					b = b.orbit.referenceBody;
+				}
+
+				a = a.orbit.referenceBody;
+			}
+
+			return false;
+		}
+		#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;
@@ -102,7 +162,7 @@
 			}
 			catch (NullReferenceException)
 			{
-				mapAttribute = new CBAttributeMap.MapAttribute();
+				mapAttribute = new CBAttributeMapSO.MapAttribute();
 				mapAttribute.name = "N/A";
 			}
 
@@ -256,79 +316,221 @@
 		}
 		#endregion
 
+		private static Dictionary<int, GUI.WindowFunction> functionCache;
+		public static UnityEngine.GUI.WindowFunction GetWindowHandler(Action<int> func)
+		{
+			if (functionCache == null)
+			{
+				functionCache = new Dictionary<int, GUI.WindowFunction>();
+			}
+
+			int hashCode = func.GetHashCode();
+
+			if (!functionCache.ContainsKey(hashCode))
+			{
+				functionCache[hashCode] = delegate (int id)
+				{
+					try
+					{
+						func(id);
+					}
+					#if DEBUG
+					catch (ArgumentException)
+					#else
+					catch (ArgumentException)
+					#endif
+					{
+						Debug.LogWarning(
+							string.Format("[{0}]: ArgumentException caught during window call.  This is not a bug.",
+								func.Target.GetType().Name
+							));
+
+						/*#if DEBUG
+						Debug.LogException(ex);
+						#endif*/
+					}
+					catch (Exception ex)
+					{
+						Debug.LogError(
+							string.Format("[{0}]: {1} caught during window call.\nMessage:\n{2}\nStackTrace:\n{3}",
+								func.Target.GetType().Name,
+								ex.GetType().Name,
+								ex.Message,
+								ex.StackTrace
+							));
+					}
+				};
+			}
+
+			return functionCache[hashCode];
+		}
+
 		/// <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)
@@ -771,6 +973,83 @@
 				return "";
 		}
 	}
+
+	public class CBListComparer : IComparer<CelestialBody>
+	{
+		public int Compare(CelestialBody bodyA, CelestialBody bodyB)
+		{
+			Tools.PostDebugMessage(this, "got bodyA: {0} & bodyB: {1}", bodyA, bodyB);
+
+			if (bodyA == null && bodyB == null)
+			{
+				Tools.PostDebugMessage(this, "both bodies are null, returning 0");
+				return 0;
+			}
+			if (bodyA == null)
+			{
+				Tools.PostDebugMessage(this, "bodyA is null, returning -1");
+				return -1;
+			}
+			if (bodyB == null)
+			{
+				Tools.PostDebugMessage(this, "bodyB is null, returning 1");
+				return 1;
+			}
+
+			Tools.PostDebugMessage(this, "bodies are not null, carrying on");
+
+			if (object.ReferenceEquals(bodyA, bodyB))
+			{
+				Tools.PostDebugMessage(this, "bodies are equal, returning 0");
+				return 0;
+			}
+
+			Tools.PostDebugMessage(this, "bodies are not equal, carrying on");
+
+			if (bodyA.orbitDriver == null)
+			{
+				Tools.PostDebugMessage(this, "bodyA.orbit is null (bodyA is the sun, returning 1");
+				return 1;
+			}
+			if (bodyB.orbitDriver == null)
+			{
+				Tools.PostDebugMessage(this, "bodyB.orbit is null (bodyB is the sun, returning -1");
+				return -1;
+			}
+
+			Tools.PostDebugMessage(this, "orbits are not null, carrying on");
+
+			if (bodyA.orbit.referenceBody == bodyB.orbit.referenceBody)
+			{
+				Tools.PostDebugMessage(this, "bodies share a parent, comparing SMAs");
+				return -bodyA.orbit.semiMajorAxis.CompareTo(bodyB.orbit.semiMajorAxis);
+			}
+
+			Tools.PostDebugMessage(this, "orbits do not share a parent, carrying on");
+
+			if (bodyA.hasAncestor(bodyB))
+			{
+				Tools.PostDebugMessage(this, "bodyA is a moon or sub-moon of bodyB, returning -1");
+				return -1;
+			}
+			if (bodyB.hasAncestor(bodyA))
+			{
+				Tools.PostDebugMessage(this, "bodyA is a moon or sub-moon of bodyB, returning 1");
+				return 1;
+			}
+
+			Tools.PostDebugMessage(this, "bodies do not have an obvious relationship, searching for one");
+
+			if (VOID_Tools.NearestRelatedParents(ref bodyA, ref bodyB))
+			{
+				Tools.PostDebugMessage(this, "good relation {0} and {1}, comparing", bodyA.bodyName, bodyB.bodyName);
+				return this.Compare(bodyA, bodyB);
+			}
+
+			Tools.PostDebugMessage(this, "bad relation {0} and {1}, giving up", bodyA.bodyName, bodyB.bodyName);
+
+			return 0;
+		}
+	}
 }
 
-

--- a/VOID_Transfer.cs
+++ b/VOID_Transfer.cs
@@ -39,7 +39,7 @@
 	{
 		protected List<CelestialBody> selectedBodies = new List<CelestialBody>();
 
-		public VOID_Transfer()
+		public VOID_Transfer() : base()
 		{
 			this._Name = "Transfer Angle Information";
 

--- 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;
@@ -48,8 +48,6 @@
 
 		public override void ModuleWindow(int _)
 		{
-			base.ModuleWindow (_);
-
 			if ((TimeWarp.WarpMode == TimeWarp.Modes.LOW) || (TimeWarp.CurrentRate <= TimeWarp.MaxPhysicsRate))
 			{
 				SimManager.RequestSimulation();
@@ -59,7 +57,7 @@
 
 			GUILayout.Label(
 				vessel.vesselName,
-				VOID_Core.Instance.LabelStyles["center_bold"],
+				VOID_Styles.labelCenterBold,
 				GUILayout.ExpandWidth(true));
 
 			VOID_Data.geeForce.DoGUIHorizontal ("F2");
@@ -68,7 +66,9 @@
 
 			VOID_Data.totalMass.DoGUIHorizontal ("F3");
 
-			VOID_Data.resourceMass.DoGUIHorizontal ("F3");
+			VOID_Data.stageResourceMass.DoGUIHorizontal("F2");
+
+			VOID_Data.resourceMass.DoGUIHorizontal("F2");
 
 			VOID_Data.stageDeltaV.DoGUIHorizontal (3, false);
 
@@ -89,215 +89,5 @@
 			GUI.DragWindow();
 		}
 	}
-
-	public static partial class VOID_Data
-	{
-		public static readonly VOID_DoubleValue geeForce = new VOID_DoubleValue(
-			"G-force",
-			new Func<double>(() => VOID_Core.Instance.vessel.geeForce),
-			"gees"
-		);
-
-		public static readonly VOID_IntValue partCount = new VOID_IntValue(
-			"Parts",
-			new Func<int>(() => VOID_Core.Instance.vessel.Parts.Count),
-			""
-		);
-
-		public static readonly VOID_DoubleValue totalMass = new VOID_DoubleValue(
-			"Total Mass",
-			delegate()
-		{
-			if (SimManager.Stages == null || SimManager.LastStage == null)
-			{
-				return double.NaN;
-			}
-
-			return SimManager.LastStage.totalMass;
-		},
-			"tons"
-		);
-
-		public static readonly VOID_DoubleValue resourceMass = new VOID_DoubleValue(
-			"Resource Mass",
-			delegate()
-			{
-				if (SimManager.Stages == null || SimManager.LastStage == null)
-				{
-					return double.NaN;
-				}
-
-				return SimManager.LastStage.totalMass - SimManager.LastStage.totalBaseMass;
-			},
-			"tons"
-		);
-
-		public static readonly VOID_DoubleValue stageDeltaV = new VOID_DoubleValue(
-			"DeltaV (Current Stage)",
-			delegate()
-			{
-				if (SimManager.Stages == null || SimManager.LastStage == null)
-					return double.NaN;
-				return SimManager.LastStage.deltaV;
-			},
-			"m/s"
-		);
-
-		public static readonly VOID_DoubleValue totalDeltaV = new VOID_DoubleValue(
-			"DeltaV (Total)",
-			delegate()
-			{
-				if (SimManager.Stages == null || SimManager.LastStage == null)
-					return double.NaN;
-				return SimManager.LastStage.totalDeltaV;
-			},
-			"m/s"
-		);
-
-		public static readonly VOID_FloatValue mainThrottle = new VOID_FloatValue(
-			"Throttle",
-			new Func<float>(() => VOID_Core.Instance.vessel.ctrlState.mainThrottle * 100f),
-			"%"
-		);
-
-		public static readonly VOID_StrValue currmaxThrust = new VOID_StrValue(
-			"Thrust (curr/max)",
-			delegate()
-			{
-				if (SimManager.Stages == null || SimManager.LastStage == null)
-					return "N/A";
-
-				double currThrust = SimManager.LastStage.actualThrust;
-				double maxThrust = SimManager.LastStage.thrust;
-
-				return string.Format(
-					"{0} / {1}",
-					currThrust.ToString("F1"),
-					maxThrust.ToString("F1")
-				);
-			}
-		);
-
-		public static readonly VOID_DoubleValue currThrustWeight = new VOID_DoubleValue(
-			"T:W Ratio",
-			delegate()
-		{
-			if (SimManager.LastStage == null)
-			{
-				return double.NaN;
-			}
-
-			return SimManager.LastStage.actualThrustToWeight;
-		},
-			""
-		);
-
-		public static readonly VOID_DoubleValue maxThrustWeight = new VOID_DoubleValue(
-			"T:W Ratio",
-			delegate()
-		{
-			if (SimManager.LastStage == null)
-			{
-				return double.NaN;
-			}
-
-			return SimManager.LastStage.maxThrustToWeight;
-		},
-			""
-		);
-
-		public static readonly VOID_StrValue currmaxThrustWeight = new VOID_StrValue(
-			"T:W (curr/max)",
-			delegate()
-			{
-				if (SimManager.Stages == null || SimManager.LastStage == null)
-					return "N/A";
-
-				return string.Format(
-					"{0} / {1}",
-					(VOID_Data.currThrustWeight.Value).ToString("F2"),
-					(VOID_Data.maxThrustWeight.Value).ToString("F2")
-				);
-			}
-		);
-
-		public static readonly VOID_DoubleValue surfaceThrustWeight = new VOID_DoubleValue(
-			"Max T:W @ surface",
-			delegate()
-			{
-			if (SimManager.Stages == null || SimManager.LastStage == null)
-					return double.NaN;
-
-				double maxThrust = SimManager.LastStage.thrust;
-				double mass = SimManager.LastStage.totalMass;
-				double gravity = (VOID_Core.Constant_G * VOID_Core.Instance.vessel.mainBody.Mass) /
-				(VOID_Core.Instance.vessel.mainBody.Radius * VOID_Core.Instance.vessel.mainBody.Radius);
-				double weight = mass * gravity;
-
-				return maxThrust / weight;
-			},
-			""
-		);
-
-		public static readonly VOID_StrValue intakeAirStatus = new VOID_StrValue(
-			"Intake Air (Curr / Req)",
-			delegate()
-			{
-				double currentAmount;
-				double currentRequirement;
-
-				currentAmount = 0d;
-				currentRequirement = 0d;
-
-				foreach (Part part in VOID_Core.Instance.vessel.Parts)
-				{
-					if (part.enabled)
-					{
-						ModuleEngines engineModule;
-						ModuleEnginesFX enginesFXModule;
-						List<Propellant> propellantList = null;
-
-						if (part.tryGetFirstModuleOfType<ModuleEngines>(out engineModule))
-						{
-							propellantList = engineModule.propellants;
-						}
-						else if (part.tryGetFirstModuleOfType<ModuleEnginesFX>(out enginesFXModule))
-						{
-							propellantList = enginesFXModule.propellants;
-						}
-							
-						if (propellantList != null)
-						{
-							foreach (Propellant propellant in propellantList)
-							{
-								if (propellant.name == "IntakeAir")
-								{
-									currentRequirement += propellant.currentRequirement / TimeWarp.fixedDeltaTime;
-									break;
-								}
-							}
-						}
-					}
-
-					ModuleResourceIntake intakeModule;
-
-					if (part.enabled && part.tryGetFirstModuleOfType<ModuleResourceIntake>(out intakeModule))
-					{
-						if (intakeModule.resourceName == "IntakeAir")
-						{
-							currentAmount += intakeModule.airFlow;
-						}
-					}
-				}
-
-				if (currentAmount == 0 && currentRequirement == 0)
-				{
-					return "N/A";
-				}
-
-				return string.Format("{0:F3} / {1:F3}", currentAmount, currentRequirement);
-			}
-		);
-	}
 }
 

--- a/VOID_VesselRegister.cs
+++ b/VOID_VesselRegister.cs
@@ -58,7 +58,7 @@
 			}
 		}
 
-		public VOID_VesselRegister()
+		public VOID_VesselRegister() : base()
 		{
 			this._Name = "Vessel Register";
 
@@ -69,7 +69,7 @@
 
 		public override void ModuleWindow(int _)
 		{
-			if (!VOID_Core.Instance.allVesselTypes.Any())
+			if (!this.core.allVesselTypes.Any())
 			{
 				return;
 			}
@@ -80,33 +80,33 @@
 			if (GUILayout.Button("<"))
 			{
 				selectedBodyIdx--;
-				if (selectedBodyIdx < 0) selectedBodyIdx = VOID_Core.Instance.allBodies.Count - 1;
+				if (selectedBodyIdx < 0) selectedBodyIdx = this.core.allBodies.Count - 1;
 			}
-			GUILayout.Label(VOID_Core.Instance.allBodies[selectedBodyIdx].bodyName, VOID_Core.Instance.LabelStyles["center_bold"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(this.core.allBodies[selectedBodyIdx].bodyName, VOID_Styles.labelCenterBold, GUILayout.ExpandWidth(true));
 			if (GUILayout.Button(">"))
 			{
 				selectedBodyIdx++;
-				if (selectedBodyIdx > VOID_Core.Instance.allBodies.Count - 1) selectedBodyIdx = 0;
+				if (selectedBodyIdx > this.core.allBodies.Count - 1) selectedBodyIdx = 0;
 			}
 			GUILayout.EndHorizontal();
 
-			seletedBody = VOID_Core.Instance.allBodies[selectedBodyIdx];
+			seletedBody = this.core.allBodies[selectedBodyIdx];
 
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 			if (GUILayout.Button("<"))
 			{
 				selectedVesselTypeIdx--;
-				if (selectedVesselTypeIdx < 0) selectedVesselTypeIdx = VOID_Core.Instance.allVesselTypes.Count - 1;
+				if (selectedVesselTypeIdx < 0) selectedVesselTypeIdx = this.core.allVesselTypes.Count - 1;
 			}
-			GUILayout.Label(VOID_Core.Instance.allVesselTypes[selectedVesselTypeIdx].ToString(), VOID_Core.Instance.LabelStyles["center_bold"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(this.core.allVesselTypes[selectedVesselTypeIdx].ToString(), VOID_Styles.labelCenterBold, GUILayout.ExpandWidth(true));
 			if (GUILayout.Button(">"))
 			{
 				selectedVesselTypeIdx++;
-				if (selectedVesselTypeIdx > VOID_Core.Instance.allVesselTypes.Count - 1) selectedVesselTypeIdx = 0;
+				if (selectedVesselTypeIdx > this.core.allVesselTypes.Count - 1) selectedVesselTypeIdx = 0;
 			}
 			GUILayout.EndHorizontal();
 
-			selectedVesselType = VOID_Core.Instance.allVesselTypes[selectedVesselTypeIdx];
+			selectedVesselType = this.core.allVesselTypes[selectedVesselTypeIdx];
 
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 			if (GUILayout.Button("Landed", GUILayout.ExpandWidth(true))) vesselSituation = "Landed";
@@ -116,7 +116,7 @@
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 			GUILayout.Label(
 				VOID_Tools.UppercaseFirst(vesselSituation) + " " + selectedVesselType.ToString() + "s  @ " + seletedBody.bodyName,
-				VOID_Core.Instance.LabelStyles["center"],
+				VOID_Styles.labelCenter,
 				GUILayout.ExpandWidth(true));
 			GUILayout.EndHorizontal();
 
@@ -142,7 +142,7 @@
 							if (_selectedVessel != v)
 							{
 								_selectedVessel = v; //set clicked vessel as selected_vessel
-								this._Active.value = true;    //turn bool on to open the window if closed
+								this.toggleActive = true;    //turn bool on to open the window if closed
 							}
 							else
 							{