Included 0.6.2.4 vessel simulation.
Included 0.6.2.4 vessel simulation.

--- a/KerbalEngineer/Editor/BuildAdvanced.cs
+++ b/KerbalEngineer/Editor/BuildAdvanced.cs
@@ -223,7 +223,7 @@
                 }

 

                 // Reset the window size when the staging or something else has changed.

-                int stageCount = SimulationManager.Instance.Stages.Count(stage => this.showAllStages || stage.DeltaV > 0);

+                int stageCount = SimulationManager.Instance.Stages.Count(stage => this.showAllStages || stage.deltaV > 0);

                 if (this.hasChanged || stageCount != this.numberOfStages)

                 {

                     this.hasChanged = false;

@@ -297,7 +297,7 @@
             {

                 GUILayout.BeginHorizontal(this.areaStyle);

                 this.DrawStageNumbers();

-                this.DrawPartCount();

+                //this.DrawPartCount();

                 this.DrawCost();

                 this.DrawMass();

                 this.DrawIsp();

@@ -361,9 +361,9 @@
             GUILayout.Label(string.Empty, this.titleStyle);

             foreach (var stage in SimulationManager.Instance.Stages)

             {

-                if (this.showAllStages || stage.DeltaV > 0)

-                {

-                    GUILayout.Label("S" + stage.Number, this.titleStyle);

+                if (this.showAllStages || stage.deltaV > 0)

+                {

+                    GUILayout.Label("S" + stage.number, this.titleStyle);

                 }

             }

             GUILayout.EndVertical();

@@ -378,9 +378,9 @@
             GUILayout.Label("PARTS", this.titleStyle);

             foreach (var stage in SimulationManager.Instance.Stages)

             {

-                if (this.showAllStages || stage.DeltaV > 0)

-                {

-                    GUILayout.Label(stage.PartCount.ToString("N0"), this.infoStyle);

+                if (this.showAllStages || stage.deltaV > 0)

+                {

+                    //GUILayout.Label(stage.PartCount.ToString("N0"), this.infoStyle);

                 }

             }

             GUILayout.EndVertical();

@@ -395,9 +395,9 @@
             GUILayout.Label("COST", this.titleStyle);

             foreach (var stage in SimulationManager.Instance.Stages)

             {

-                if (this.showAllStages || stage.DeltaV > 0)

-                {

-                    GUILayout.Label(stage.Cost.ToString("N0") + " / " + stage.TotalCost.ToString("N0"), this.infoStyle);

+                if (this.showAllStages || stage.deltaV > 0)

+                {

+                    GUILayout.Label(stage.cost.ToString("N0") + " / " + stage.totalCost.ToString("N0"), this.infoStyle);

                 }

             }

             GUILayout.EndVertical();

@@ -412,9 +412,9 @@
             GUILayout.Label("MASS", this.titleStyle);

             foreach (var stage in SimulationManager.Instance.Stages)

             {

-                if (this.showAllStages || stage.DeltaV > 0)

-                {

-                    GUILayout.Label(stage.Mass.ToMass(false) + " / " + stage.TotalMass.ToMass(), this.infoStyle);

+                if (this.showAllStages || stage.deltaV > 0)

+                {

+                    GUILayout.Label(stage.mass.ToMass(false) + " / " + stage.totalMass.ToMass(), this.infoStyle);

                 }

             }

             GUILayout.EndVertical();

@@ -429,9 +429,9 @@
             GUILayout.Label("ISP", this.titleStyle);

             foreach (var stage in SimulationManager.Instance.Stages)

             {

-                if (this.showAllStages || stage.DeltaV > 0)

-                {

-                    GUILayout.Label(stage.Isp.ToString("F1") + "s", this.infoStyle);

+                if (this.showAllStages || stage.deltaV > 0)

+                {

+                    GUILayout.Label(stage.isp.ToString("F1") + "s", this.infoStyle);

                 }

             }

             GUILayout.EndVertical();

@@ -446,9 +446,9 @@
             GUILayout.Label("THRUST", this.titleStyle);

             foreach (var stage in SimulationManager.Instance.Stages)

             {

-                if (this.showAllStages || stage.DeltaV > 0)

-                {

-                    GUILayout.Label(stage.Thrust.ToForce(), this.infoStyle);

+                if (this.showAllStages || stage.deltaV > 0)

+                {

+                    GUILayout.Label(stage.thrust.ToForce(), this.infoStyle);

                 }

             }

             GUILayout.EndVertical();

@@ -463,9 +463,9 @@
             GUILayout.Label("TWR", this.titleStyle);

             foreach (var stage in SimulationManager.Instance.Stages)

             {

-                if (this.showAllStages || stage.DeltaV > 0)

-                {

-                    GUILayout.Label(stage.ThrustToWeight.ToString("F2"), this.infoStyle);

+                if (this.showAllStages || stage.deltaV > 0)

+                {

+                    GUILayout.Label(stage.thrustToWeight.ToString("F2"), this.infoStyle);

                 }

             }

             GUILayout.EndVertical();

@@ -480,9 +480,9 @@
             GUILayout.Label("DELTA-V", this.titleStyle);

             foreach (var stage in SimulationManager.Instance.Stages)

             {

-                if (this.showAllStages || stage.DeltaV > 0)

-                {

-                    GUILayout.Label(stage.DeltaV.ToString("N0") + " / " + stage.InverseTotalDeltaV.ToString("N0") + "m/s", this.infoStyle);

+                if (this.showAllStages || stage.deltaV > 0)

+                {

+                    GUILayout.Label(stage.deltaV.ToString("N0") + " / " + stage.inverseTotalDeltaV.ToString("N0") + "m/s", this.infoStyle);

                 }

             }

             GUILayout.EndVertical();

@@ -497,9 +497,9 @@
             GUILayout.Label("BURN", this.titleStyle);

             foreach (var stage in SimulationManager.Instance.Stages)

             {

-                if (this.showAllStages || stage.DeltaV > 0)

-                {

-                    GUILayout.Label(stage.Time.ToTime(), this.infoStyle);

+                if (this.showAllStages || stage.deltaV > 0)

+                {

+                    GUILayout.Label(stage.time.ToTime(), this.infoStyle);

                 }

             }

             GUILayout.EndVertical();


--- a/KerbalEngineer/Editor/BuildOverlay.cs
+++ b/KerbalEngineer/Editor/BuildOverlay.cs
@@ -244,9 +244,9 @@
 

             // Details

             GUILayout.BeginVertical(GUILayout.Width(100.0f));

-            GUILayout.Label(SimulationManager.Instance.LastStage.PartCount.ToString("N0"), this.infoStyle);

-            GUILayout.Label(SimulationManager.Instance.LastStage.TotalDeltaV.ToString("N0") + " m/s", this.infoStyle);

-            GUILayout.Label(SimulationManager.Instance.LastStage.ThrustToWeight.ToString("F2"), this.infoStyle);

+            //GUILayout.Label(SimulationManager.Instance.LastStage.partCount.ToString("N0"), this.infoStyle);

+            GUILayout.Label(SimulationManager.Instance.LastStage.totalDeltaV.ToString("N0") + " m/s", this.infoStyle);

+            GUILayout.Label(SimulationManager.Instance.LastStage.thrustToWeight.ToString("F2"), this.infoStyle);

             GUILayout.EndVertical();

 

             GUILayout.EndHorizontal();


--- a/KerbalEngineer/Flight/FlightEngineerModule.cs
+++ b/KerbalEngineer/Flight/FlightEngineerModule.cs
@@ -6,7 +6,7 @@
 

 using System.Linq;

 

-using KerbalEngineer.Flight.Readouts;

+using KerbalEngineer.Simulation;

 

 #endregion

 

@@ -17,6 +17,16 @@
     /// </summary>

     public sealed class FlightEngineerModule : PartModule

     {

+        #region KSP Fields

+

+        /// <summary>

+        ///     The minimum time in ms from the start of one simulation to the start of the next.

+        /// </summary>

+        [KSPField(isPersistant = true, guiActive = true, guiActiveEditor = false, guiName = "Sim time limit"),

+         UI_FloatRange(minValue = 0.0f, maxValue = 1000.0f, stepIncrement = 10.0f, scene = UI_Scene.Flight)] public float minFESimTime = 200.0f;

+

+        #endregion

+

         #region Fields

 

         /// <summary>

@@ -37,6 +47,8 @@
             {

                 return;

             }

+

+            SimManager.minSimTime = (long)this.minFESimTime;

 

             if (this.vessel == FlightGlobals.ActiveVessel)

             {


--- a/KerbalEngineer/Flight/Readouts/Vessel/DeltaVStaged.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/DeltaVStaged.cs
@@ -34,9 +34,9 @@
 

             foreach (var stage in SimulationManager.Instance.Stages)

             {

-                if (stage.DeltaV > 0 || stage.Number == Staging.CurrentStage)

+                if (stage.deltaV > 0 || stage.number == Staging.CurrentStage)

                 {

-                    this.DrawLine("DeltaV (S" + stage.Number + ")", stage.DeltaV.ToString("N0") + "m/s");

+                    this.DrawLine("DeltaV (S" + stage.number + ")", stage.deltaV.ToString("N0") + "m/s");

                     newNumberOfStages++;

                 }

             }


--- a/KerbalEngineer/Flight/Readouts/Vessel/DeltaVTotal.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/DeltaVTotal.cs
@@ -26,7 +26,7 @@
 

         public override void Draw()

         {

-            this.DrawLine(SimulationManager.Instance.LastStage.TotalDeltaV.ToString("N0") + "m/s");

+            this.DrawLine(SimulationManager.Instance.LastStage.totalDeltaV.ToString("N0") + "m/s");

         }

 

         public override void Reset()


--- a/KerbalEngineer/Flight/Readouts/Vessel/Mass.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/Mass.cs
@@ -26,7 +26,7 @@
 

         public override void Draw()

         {

-            this.DrawLine(SimulationManager.Instance.LastStage.Mass.ToMass(false) + " / " + SimulationManager.Instance.LastStage.TotalMass.ToMass());

+            this.DrawLine(SimulationManager.Instance.LastStage.mass.ToMass(false) + " / " + SimulationManager.Instance.LastStage.totalMass.ToMass());

         }

 

         public override void Reset()


--- a/KerbalEngineer/Flight/Readouts/Vessel/SpecificImpulse.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/SpecificImpulse.cs
@@ -25,7 +25,7 @@
 

         public override void Draw()

         {

-            this.DrawLine(SimulationManager.Instance.LastStage.Isp.ToString("F1") + "s");

+            this.DrawLine(SimulationManager.Instance.LastStage.isp.ToString("F1") + "s");

         }

 

         public override void Reset()


--- a/KerbalEngineer/Flight/Readouts/Vessel/Thrust.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/Thrust.cs
@@ -26,7 +26,7 @@
 

         public override void Draw()

         {

-            this.DrawLine(SimulationManager.Instance.LastStage.ActualThrust.ToForce(false) + " / " + SimulationManager.Instance.LastStage.Thrust.ToForce());

+            this.DrawLine(SimulationManager.Instance.LastStage.actualThrust.ToForce(false) + " / " + SimulationManager.Instance.LastStage.thrust.ToForce());

         }

 

         public override void Reset()


--- a/KerbalEngineer/Flight/Readouts/Vessel/ThrustToWeight.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/ThrustToWeight.cs
@@ -29,8 +29,8 @@
 

         public override void Draw()

         {

-            this.actual = (SimulationManager.Instance.LastStage.ActualThrust / (SimulationManager.Instance.LastStage.TotalMass * FlightGlobals.getGeeForceAtPosition(FlightGlobals.ship_position).magnitude)).ToString("F2");

-            this.total = (SimulationManager.Instance.LastStage.Thrust / (SimulationManager.Instance.LastStage.TotalMass * FlightGlobals.getGeeForceAtPosition(FlightGlobals.ship_position).magnitude)).ToString("F2");

+            this.actual = (SimulationManager.Instance.LastStage.actualThrust / (SimulationManager.Instance.LastStage.totalMass * FlightGlobals.getGeeForceAtPosition(FlightGlobals.ship_position).magnitude)).ToString("F2");

+            this.total = (SimulationManager.Instance.LastStage.thrust / (SimulationManager.Instance.LastStage.totalMass * FlightGlobals.getGeeForceAtPosition(FlightGlobals.ship_position).magnitude)).ToString("F2");

             this.DrawLine("TWR", this.actual + " / " + this.total);

         }

 


--- a/KerbalEngineer/KerbalEngineer.csproj
+++ b/KerbalEngineer/KerbalEngineer.csproj
@@ -112,6 +112,7 @@
     <Compile Include="Simulation\EngineSim.cs" />

     <Compile Include="Simulation\PartSim.cs" />

     <Compile Include="Simulation\ResourceContainer.cs" />

+    <Compile Include="Simulation\SimManager.cs" />

     <Compile Include="Simulation\SimulationManager.cs" />

     <Compile Include="Simulation\Simulation.cs" />

     <Compile Include="Simulation\Stage.cs" />


--- a/KerbalEngineer/Simulation/AttachNodeSim.cs
+++ b/KerbalEngineer/Simulation/AttachNodeSim.cs
@@ -1,4 +1,5 @@
 using System;

+using System.Text;

 

 namespace KerbalEngineer.Simulation
 {
@@ -15,23 +16,23 @@
             this.id = newId;
         }
 
-#if LOG
+#if LOG || true
         public void DumpToBuffer(StringBuilder buffer)
         {
-            if (attachedPartSim == null)
+            if (this.attachedPartSim == null)
             {
                 buffer.Append("<staged>:<n>");
             }
             else
             {
-                buffer.Append(attachedPartSim.name);
+                buffer.Append(this.attachedPartSim.name);
                 buffer.Append(":");
-                buffer.Append(attachedPartSim.partId);
+                buffer.Append(this.attachedPartSim.partId);
             }
             buffer.Append("#");
-            buffer.Append(nodeType);
+            buffer.Append(this.nodeType);
             buffer.Append(":");
-            buffer.Append(id);
+            buffer.Append(this.id);
         }
 #endif
     }

--- a/KerbalEngineer/Simulation/EngineSim.cs
+++ b/KerbalEngineer/Simulation/EngineSim.cs
@@ -4,7 +4,7 @@
 

 using System;

 using System.Collections.Generic;

-using System.Text;

+using System.Linq;

 

 using UnityEngine;

 

@@ -27,66 +27,98 @@
                             float maxThrust,
                             float thrustPercentage,
                             float requestedThrust,
+                            float realIsp,
                             FloatCurve atmosphereCurve,
                             bool throttleLocked,
-                            List<Propellant> propellants)
-        {
+                            List<Propellant> propellants,
+                            bool correctThrust)
+        {
+            //MonoBehaviour.print("Create EngineSim for " + theEngine.name);
+            //MonoBehaviour.print("maxThrust = " + maxThrust);
+            //MonoBehaviour.print("thrustPercentage = " + thrustPercentage);
+            //MonoBehaviour.print("requestedThrust = " + requestedThrust);
+
             this.partSim = theEngine;
 
             this.thrust = maxThrust * (thrustPercentage / 100f);
+            //MonoBehaviour.print("thrust = " + thrust);
 
             double flowRate = 0d;
             if (this.partSim.hasVessel)
             {
+                //MonoBehaviour.print("hasVessel is true");
                 this.actualThrust = requestedThrust;
                 this.isp = atmosphereCurve.Evaluate((float)this.partSim.part.staticPressureAtm);
 
+                if (correctThrust && realIsp == 0)
+                {
+                    this.thrust = this.thrust * this.isp / atmosphereCurve.Evaluate(0);
+                    //MonoBehaviour.print("corrected thrust = " + thrust);
+                }
+
                 if (throttleLocked)
                 {
+                    //MonoBehaviour.print("throttleLocked is true");
                     flowRate = this.thrust / (this.isp * 9.81d);
                 }
                 else
                 {
                     if (this.partSim.isLanded)
                     {
-                        // Why does it force a non-zero flow rate when landed?
+                        //MonoBehaviour.print("partSim.isLanded is true, mainThrottle = " + FlightInputHandler.state.mainThrottle);
                         flowRate = Math.Max(0.000001d, this.thrust * FlightInputHandler.state.mainThrottle) / (this.isp * 9.81d);
                     }
                     else
                     {
                         if (requestedThrust > 0)
+                        {
+                            //MonoBehaviour.print("requestedThrust > 0");
                             flowRate = requestedThrust / (this.isp * 9.81d);
+                        }
                         else
+                        {
+                            //MonoBehaviour.print("requestedThrust <= 0");
                             flowRate = this.thrust / (this.isp * 9.81d);
+                        }
                     }
                 }
             }
             else
             {
+                //MonoBehaviour.print("hasVessel is false");
                 this.isp = atmosphereCurve.Evaluate((float)atmosphere);
+                if (correctThrust)
+                {
+                    this.thrust = this.thrust * this.isp / atmosphereCurve.Evaluate(0);
+                    //MonoBehaviour.print("corrected thrust = " + thrust);
+                }
                 flowRate = this.thrust / (this.isp * 9.81d);
             }
-
+#if LOG
             StringBuilder buffer = new StringBuilder(1024);
             buffer.AppendFormat("flowRate = {0:g6}\n", flowRate);
-
+#endif
             float flowMass = 0f;
 
             foreach (Propellant propellant in propellants)
                 flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);
-
+#if LOG
             buffer.AppendFormat("flowMass = {0:g6}\n", flowMass);
-
+#endif
             foreach (Propellant propellant in propellants)
             {
                 if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir")
                     continue;
 
                 double consumptionRate = propellant.ratio * flowRate / flowMass;
+#if LOG
                 buffer.AppendFormat("Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate);
+#endif
                 this.resourceConsumptions.Add(propellant.id, consumptionRate);
             }
+#if LOG
             MonoBehaviour.print(buffer);
+#endif
         }
 
 
@@ -101,7 +133,7 @@
                 switch (ResourceContainer.GetResourceFlowMode(type))
                 {
                     case ResourceFlowMode.NO_FLOW:
-                        if (this.partSim.resources[type] > 1f)
+                        if (this.partSim.resources[type] > SimManager.RESOURCE_MIN)
                         {
                             sourcePartSet = new HashSet<PartSim>();
                             //MonoBehaviour.print("SetResourceDrains(" + name + ":" + partId + ") setting sources to just this");
@@ -112,7 +144,7 @@
                     case ResourceFlowMode.ALL_VESSEL:
                         foreach (PartSim aPartSim in allParts)
                         {
-                            if (aPartSim.resources[type] > 1f)
+                            if (aPartSim.resources[type] > SimManager.RESOURCE_MIN)
                             {
                                 if (sourcePartSet == null)
                                     sourcePartSet = new HashSet<PartSim>();
@@ -122,25 +154,91 @@
                         }
                         break;
 
+                    case ResourceFlowMode.STAGE_PRIORITY_FLOW:
+                        {
+                            Dictionary<int, HashSet<PartSim>> stagePartSets = new Dictionary<int, HashSet<PartSim>>();
+                            int maxStage = -1;
+                            foreach (PartSim aPartSim in allParts)
+                            {
+                                if (aPartSim.resources[type] > SimManager.RESOURCE_MIN)
+                                {
+                                    //int stage = aPartSim.decoupledInStage;            // Use the number of the stage the tank is decoupled in
+                                    int stage = aPartSim.DecouplerCount();              // Use the count of decouplers between tank and root
+                                    if (stage > maxStage)
+                                        maxStage = stage;
+                                    if (stagePartSets.ContainsKey(stage))
+                                    {
+                                        sourcePartSet = stagePartSets[stage];
+                                    }
+                                    else
+                                    {
+                                        sourcePartSet = new HashSet<PartSim>();
+                                        stagePartSets.Add(stage, sourcePartSet);
+                                    }
+                                    
+                                    sourcePartSet.Add(aPartSim);
+                                }
+                            }
+
+                            while (maxStage >= 0)
+                            {
+                                if (stagePartSets.ContainsKey(maxStage))
+                                {
+                                    if (stagePartSets[maxStage].Count() > 0)
+                                    {
+                                        sourcePartSet = stagePartSets[maxStage];
+                                        break;
+                                    }
+                                }
+                                maxStage--;
+                            }
+                        }
+                        break;
+
                     case ResourceFlowMode.STACK_PRIORITY_SEARCH:
                         HashSet<PartSim> visited = new HashSet<PartSim>();
-                        sourcePartSet = this.partSim.GetSourceSet(type, allParts, allFuelLines, visited);
+#if LOG
+                        LogMsg log = new LogMsg();
+                        log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + partSim.name + ":" + partSim.partId);
+#else
+                        LogMsg log = null;
+#endif
+                        sourcePartSet = this.partSim.GetSourceSet(type, allParts, allFuelLines, visited, log, "");
+#if LOG
+                        MonoBehaviour.print(log.buf);
+#endif
                         break;
 
                     default:
-                        MonoBehaviour.print("SetResourceDrains(" + this.partSim.name + ":" + this.partSim.partId + ") No flow type for " + ResourceContainer.GetResourceName(type) + ")");
+                        MonoBehaviour.print("SetResourceDrains(" + this.partSim.name + ":" + this.partSim.partId + ") Unexpected flow type for " + ResourceContainer.GetResourceName(type) + ")");
                         break;
                 }
 
                 if (sourcePartSet != null && sourcePartSet.Count > 0)
+                {
                     sourcePartSets[type] = sourcePartSet;
+#if LOG
+                    LogMsg log = new LogMsg();
+                    log.buf.AppendLine("Source parts for " + ResourceContainer.GetResourceName(type) + ":");
+                    foreach (PartSim partSim in sourcePartSet)
+                    {
+                        log.buf.AppendLine(partSim.name + ":" + partSim.partId);
+                    }
+                    MonoBehaviour.print(log.buf);
+#endif
+                }
             }
 
             // If we don't have sources for all the needed resources then return false without setting up any drains
             foreach (int type in this.resourceConsumptions.Types)
             {
                 if (!sourcePartSets.ContainsKey(type))
+                {
+#if LOG
+                    MonoBehaviour.print("No source of " + ResourceContainer.GetResourceName(type));
+#endif
                     return false;
+                }
             }
 
             // Now we set the drains on the members of the sets and update the draining parts set

--- a/KerbalEngineer/Simulation/PartSim.cs
+++ b/KerbalEngineer/Simulation/PartSim.cs
@@ -1,537 +1,630 @@
-// Kerbal Engineer Redux

-// Author:  CYBUTEK

-// License: Attribution-NonCommercial-ShareAlike 3.0 Unported

-//

-// This class has taken a lot of inspiration from r4m0n's MuMech FuelFlowSimulator.  Although extremely

-// similar to the code used within MechJeb, it is a clean re-write.  The similarities are a testiment

+// Kerbal Engineer Redux
+// Author:  CYBUTEK
+// License: Attribution-NonCommercial-ShareAlike 3.0 Unported
+//
+// This class has taken a lot of inspiration from r4m0n's MuMech FuelFlowSimulator.  Although extremely
+// similar to the code used within MechJeb, it is a clean re-write.  The similarities are a testiment
 // to how well the MuMech code works and the robustness of the simulation algorithem used.

 

 using System;

 using System.Collections.Generic;

 using System.Linq;

+using System.Text;

 

 using KerbalEngineer.Extensions;

 

 using UnityEngine;

 

-namespace KerbalEngineer.Simulation

-{

-    public class PartSim

-    {

-        public ResourceContainer resources = new ResourceContainer();

-        public ResourceContainer resourceDrains = new ResourceContainer();

-        ResourceContainer resourceFlowStates = new ResourceContainer();

-        ResourceContainer resourceConsumptions = new ResourceContainer();

-

-        Dictionary<int, bool> resourceCanSupply = new Dictionary<int, bool>();

-

-        List<AttachNodeSim> attachNodes = new List<AttachNodeSim>();

-

-        public Part part;              // This is only set while the data structures are being initialised

-        public int partId = 0;

-        public String name;

-        public PartSim parent;

-        public PartSim fuelLineTarget;

-        public bool hasVessel;

-        public bool isLanded;

-        public int decoupledInStage;

-        public int inverseStage;

-        public int cost;

-        double baseMass = 0d;

-        double startMass = 0d;

-        public double thrust = 0;

-        public double actualThrust = 0;

-        public double isp = 0;

-        public String noCrossFeedNodeKey;

-        public bool fuelCrossFeed;

-        public bool isEngine;

-        public bool isFuelLine;

-        public bool isFuelTank;

-        public bool isDecoupler;

-        public bool isDockingNode;

-        public bool isStrutOrFuelLine;

-        public bool isSolidMotor;

-        public bool isSepratron;

-        public bool hasMultiModeEngine;

-        public bool hasModuleEnginesFX;

-        public bool hasModuleEngines;

-

-        public PartSim(Part thePart, int id, double atmosphere)

-        {

-            this.part = thePart;

-            this.partId = id;

-            this.name = this.part.partInfo.name;

-            //MonoBehaviour.print("Create PartSim for " + name);

-            

-            this.parent = null;

-            this.fuelCrossFeed = this.part.fuelCrossFeed;

-            this.noCrossFeedNodeKey = this.part.NoCrossFeedNodeKey;

-            this.decoupledInStage = this.DecoupledInStage(this.part);

-            this.isDecoupler = this.IsDecoupler(this.part);

-            this.isDockingNode = this.IsDockingNode();

-            this.isFuelLine = this.part is FuelLine;

-            this.isFuelTank = this.part is FuelTank;

-            this.isStrutOrFuelLine = this.IsStrutOrFuelLine();

-            this.isSolidMotor = this.IsSolidMotor();

-            this.isSepratron = this.IsSepratron();

-            this.inverseStage = this.part.inverseStage;

-            //MonoBehaviour.print("inverseStage = " + inverseStage);

-

-            this.cost = this.part.partInfo.cost;

-

-            if (!this.part.Modules.Contains("LaunchClamp") && this.part.physicalSignificance == Part.PhysicalSignificance.FULL)

-                this.baseMass = this.part.mass;

-

-            foreach (PartResource resource in this.part.Resources)

-            {

-                // Make sure it isn't NaN as this messes up the part mass and hence most of the values

-                // This can happen if a resource capacity is 0 and tweakable

-                if (!Double.IsNaN(resource.amount))

-                {

-                    //MonoBehaviour.print(resource.resourceName + " = " + resource.amount);

-                    this.resources.Add(resource.info.id, resource.amount);

-                    this.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0);

-                }

-                else

-                {

-                    MonoBehaviour.print(resource.resourceName + " is NaN. Skipping.");

-                }

-            }

-

-            this.startMass = this.GetMass();

-

-            this.hasVessel = (this.part.vessel != null);

-            this.isLanded = this.hasVessel && this.part.vessel.Landed;

-

-            this.hasMultiModeEngine = this.part.HasModule<MultiModeEngine>();

-            this.hasModuleEnginesFX = this.part.HasModule<ModuleEnginesFX>();

-            this.hasModuleEngines = this.part.HasModule<ModuleEngines>();

-

-            this.isEngine = this.hasMultiModeEngine || this.hasModuleEnginesFX || this.hasModuleEngines;

-

-            //MonoBehaviour.print("Created " + name + ". Decoupled in stage " + decoupledInStage);

-        }

-

-        public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere)

-        {

-            LogMsg log = new LogMsg();

-            log.buf.AppendLine("CreateEngineSims for " + this.name);

-

-            foreach (PartModule partMod in this.part.Modules)

-            {

-                log.buf.AppendLine("Module: " + partMod.moduleName);

-            }

-

-            if (this.hasMultiModeEngine)

-            {

-                // A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point

-                // The mode of the engine is the engineID of the ModuleEnginesFX that is active

-                string mode = this.part.GetModule<MultiModeEngine>().mode;

-

-                foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())

-                {

-                    if (engine.engineID == mode)

-                    {

-                        EngineSim engineSim = new EngineSim(this, atmosphere,

-                                                            engine.maxThrust,

-                                                            engine.thrustPercentage,

-                                                            engine.requestedThrust,

-                                                            engine.atmosphereCurve,

-                                                            engine.throttleLocked,

-                                                            engine.propellants);

-                        allEngines.Add(engineSim);

-                    }

-                }

-            }

-            else

-            {

-

-                if (this.hasModuleEnginesFX)

-                {

-                    foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())

-                    {

-                        EngineSim engineSim = new EngineSim(this, atmosphere,

-                                                            engine.maxThrust,

-                                                            engine.thrustPercentage,

-                                                            engine.requestedThrust,

-                                                            engine.atmosphereCurve,

-                                                            engine.throttleLocked,

-                                                            engine.propellants);

-                        allEngines.Add(engineSim);

-                    }

-                }

-

-                if (this.hasModuleEngines)

-                {

-                    foreach (ModuleEngines engine in this.part.GetModules<ModuleEngines>())

-                    {

-                        EngineSim engineSim = new EngineSim(this, atmosphere,

-                                                            engine.maxThrust,

-                                                            engine.thrustPercentage,

-                                                            engine.requestedThrust,

-                                                            engine.atmosphereCurve,

-                                                            engine.throttleLocked,

-                                                            engine.propellants);

-                        allEngines.Add(engineSim);

-                    }

-                }

-            }

-

-            log.Flush();

-        }

-

-

-        public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup)

-        {

-            this.attachNodes.Clear();

-            foreach (AttachNode attachNode in this.part.attachNodes)

-            {

-                if (attachNode.attachedPart != null)

-                {

-                    PartSim attachedSim;

-                    if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim))

-                    {

-                        this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType));

-                    }

-                    else

-                    {

-                        MonoBehaviour.print("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");

-                    }

-                }

-            }

-

-            if (this.isFuelLine)

-            {

-                if ((this.part as FuelLine).target != null)

-                {

-                    PartSim targetSim;

-                    if (partSimLookup.TryGetValue((this.part as FuelLine).target, out targetSim))

-                    {

-                        this.fuelLineTarget = targetSim;

-                    }

-                }

-                else

-                {

-                    this.fuelLineTarget = null;

-                }

-            }

-

-            if (this.part.parent != null)

-            {

-                this.parent = null;

-                if (!partSimLookup.TryGetValue(this.part.parent, out this.parent))

-                {

-                    MonoBehaviour.print("No PartSim for parent part (" + this.part.parent.partInfo.name + ")");

-                }

-            }

-        }

-

-        public int DecoupledInStage(Part thePart, int stage = -1)

-        {

-            if (this.IsDecoupler(thePart))

-            {

-                if (thePart.inverseStage > stage)

-                {

-                    stage = thePart.inverseStage;

-                }

-            }

-

-            if (thePart.parent != null)

-            {

-                stage = this.DecoupledInStage(thePart.parent, stage);

-            }

-

-            return stage;

-        }

-

-        private bool IsDecoupler(Part thePart)

-        {

-            return thePart is Decoupler || thePart is RadialDecoupler || thePart.Modules.OfType<ModuleDecouple>().Count() > 0 || thePart.Modules.OfType<ModuleAnchoredDecoupler>().Count() > 0;

-        }

-

-        private bool IsDockingNode()

-        {

-            return this.part.Modules.OfType<ModuleDockingNode>().Count() > 0;

-        }

-

-        private bool IsStrutOrFuelLine()

-        {

-            return (this.part is StrutConnector || this.part is FuelLine) ? true : false;

-        }

-

-        private bool IsSolidMotor()

-        {

-            foreach (ModuleEngines engine in this.part.Modules.OfType<ModuleEngines>())

-            {

-                if (engine.throttleLocked)

-                    return true;

-            }

-

-            return false;

-        }

-

-        private bool IsSepratron()

-        {

-            if (!this.part.ActivatesEvenIfDisconnected)

-                return false;

-

-            if (this.part is SolidRocket)

-                return true;

-

-            if (this.part.Modules.OfType<ModuleEngines>().Count() == 0)

-                return false;

-

-            if (this.part.Modules.OfType<ModuleEngines>().First().throttleLocked == true)

-                return true;

-

-            return false;

-        }

-

-        public void ReleasePart()

-        {

-            this.part = null;

-        }

-

-

-        // All functions below this point must not rely on the part member (it may be null)

-        //

-

-        public HashSet<PartSim> GetSourceSet(int type, List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> visited)

-        {

-            //MonoBehaviour.print("GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + name + ":" + partId);

-

-            HashSet<PartSim> allSources = new HashSet<PartSim>();

-            HashSet<PartSim> partSources = new HashSet<PartSim>();

-

-            // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns empty list.

-            if (visited.Contains(this))

-            {

-                //MonoBehaviour.print("Returning empty set, already visited (" + name + ":" + partId + ")");

-                return allSources;

-            }

-

-            //MonoBehaviour.print("Adding this to visited");

-            visited.Add(this);

-

-            // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result.

-            //MonoBehaviour.print("foreach fuel line");

-            foreach (PartSim partSim in allFuelLines)

-            {

-                if (partSim.fuelLineTarget == this)

-                {

-                    //MonoBehaviour.print("Adding fuel line as source (" + partSim.name + ":" + partSim.partId + ")");

-                    partSources = partSim.GetSourceSet(type, allParts, allFuelLines, visited);

-                    if (partSources.Count > 0)

-                    {

-                        allSources.UnionWith(partSources);

-                        partSources.Clear();

-                    }

-                }

-            }

-

-            if (allSources.Count > 0)

-            {

-                //MonoBehaviour.print("Returning " + allSources.Count + " fuel line sources (" + name + ":" + partId + ")");

-                return allSources;

-            }

-

-            // Rule 3: If the part is not crossfeed capable, it returns empty list.

-            //MonoBehaviour.print("Test crossfeed");

-            if (!this.fuelCrossFeed)

-            {

-                //MonoBehaviour.print("Returning empty set, no cross feed (" + name + ":" + partId + ")");

-                return allSources;

-            }

-

-            // Rule 4: Part performs scan on each of its axially mounted neighbors. 

-            //  Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side, skip the points on the side where multiple points are. [Experiment]

-            //  Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list. 

-            //  The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment]

-            //MonoBehaviour.print("foreach attach node");

-            foreach (AttachNodeSim attachSim in this.attachNodes)

-            {

-                if (attachSim.attachedPartSim != null)

-                {

-                    if (attachSim.nodeType == AttachNode.NodeType.Stack &&

-                        (attachSim.attachedPartSim.fuelCrossFeed || attachSim.attachedPartSim.isFuelTank) &&

-                        !(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey)))

-                    {

-                        //MonoBehaviour.print("Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");

-                        partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, allFuelLines, visited);

-                        if (partSources.Count > 0)

-                        {

-                            allSources.UnionWith(partSources);

-                            partSources.Clear();

-                        }

-                    }

-                }

-            }

-

-            if (allSources.Count > 0)

-            {

-                //MonoBehaviour.print("Returning " + allSources.Count + " attached sources (" + name + ":" + partId + ")");

-                return allSources;

-            }

-

-            // Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled [Experiment]) and it contains fuel, it returns itself.

-            // Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment]

-            //MonoBehaviour.print("testing enabled container");

-            if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0)

-            {

-                if (this.resources[type] > 1f)

-                    allSources.Add(this);

-

-                //MonoBehaviour.print("Returning this as only source (" + name + ":" + partId + ")");

-                return allSources;

-            }

-

-            // Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its parent and returns whatever the parent scan returned. [Experiment] [Experiment]

-            if (this.parent != null)

-            {

-                allSources = this.parent.GetSourceSet(type, allParts, allFuelLines, visited);

-                if (allSources.Count > 0)

-                {

-                    //MonoBehaviour.print("Returning " + allSources.Count + " parent sources (" + name + ":" + partId + ")");

-                    return allSources;

-                }

-            }

-

-            // Rule 8: If all preceding rules failed, part returns empty list.

-            //MonoBehaviour.print("Returning empty set, no sources found (" + name + ":" + partId + ")");

-            return allSources;

-        }

-

-

-        public void RemoveAttachedParts(HashSet<PartSim> partSims)

-        {

-            // Loop through the attached parts

-            foreach (AttachNodeSim attachSim in this.attachNodes)

-            {

-                // If the part is in the set then "remove" it by clearing the PartSim reference

-                if (partSims.Contains(attachSim.attachedPartSim))

-                    attachSim.attachedPartSim = null;

-            }

-        }

-

-

-        public void DrainResources(double time)

-        {

-            //MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")");

-            foreach (int type in this.resourceDrains.Types)

-            {

-                //MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type));

-                this.resources.Add(type, -time * this.resourceDrains[type]);

-                //MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]);

-            }

-        }

-

-        public double TimeToDrainResource()

-        {

-            //MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")");

-            double time = double.MaxValue;

-

-            foreach (int type in this.resourceDrains.Types)

-            {

-                //MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + "  amount = " + resources[type] + "  rate = " + resourceDrains[type]);

-                if (this.resourceDrains[type] > 0)

-                    time = Math.Min(time, this.resources[type] / this.resourceDrains[type]);

-            }

-

-            //if (time < double.MaxValue)

-            //    MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time);

-            return time;

-        }

-

-        public double GetStartMass()

-        {

-            return this.startMass;

-        }

-

-        public double GetMass()

-        {

-            double mass = this.baseMass;

-

-            foreach (int type in this.resources.Types)

-                mass += this.resources.GetResourceMass(type);

-

-            return mass;

-        }

-

-        public ResourceContainer Resources

-        {

-            get

-            {

-                return this.resources;

-            }

-        }

-

-        public ResourceContainer ResourceConsumptions

-        {

-            get

-            {

-                return this.resourceConsumptions;

-            }

-        }

-

-        public ResourceContainer ResourceDrains

-        {

-            get

-            {

-                return this.resourceDrains;

-            }

-        }

-

-#if LOG

-        public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix)

-        {

-            if (parent != null)

-            {

-                prefix = parent.DumpPartAndParentsToBuffer(buffer, prefix) + " ";

-            }

-

-            DumpPartToBuffer(buffer, prefix);

-

-            return prefix;

-        }

-

-        public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null)

-        {

-            buffer.Append(prefix);

-            buffer.Append(name);

-            buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", partId, decoupledInStage, inverseStage);

-

-            buffer.AppendFormat(", isSep = {0}", isSepratron);

-

-            foreach (int type in resources.Types)

-                buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), resources[type]);

-

-            if (attachNodes.Count > 0)

-            {

-                buffer.Append(", attached = <");

-                attachNodes[0].DumpToBuffer(buffer);

-                for (int i = 1; i < attachNodes.Count; i++)

-                {

-                    buffer.Append(", ");

-                    attachNodes[i].DumpToBuffer(buffer);

-                }

-                buffer.Append(">");

-            }

-

-            // Add more info here

-

-            buffer.Append("]\n");

-

-            if (allParts != null)

-            {

-                String newPrefix = prefix + " ";

-                foreach (PartSim partSim in allParts)

-                {

-                    if (partSim.parent == this)

-                        partSim.DumpPartToBuffer(buffer, newPrefix, allParts);

-                }

-            }

-        }

-#endif

-    }

-}

-
+namespace KerbalEngineer.Simulation
+{
+    public class PartSim
+    {
+        public ResourceContainer resources = new ResourceContainer();
+        public ResourceContainer resourceDrains = new ResourceContainer();
+        ResourceContainer resourceFlowStates = new ResourceContainer();
+        //ResourceContainer resourceConsumptions = new ResourceContainer();
+
+        //Dictionary<int, bool> resourceCanSupply = new Dictionary<int, bool>();
+
+        List<AttachNodeSim> attachNodes = new List<AttachNodeSim>();
+
+        public Part part;              // This is only set while the data structures are being initialised
+        public int partId = 0;
+        public String name;
+        public PartSim parent;
+        public PartSim fuelLineTarget;
+        public bool hasVessel;
+        public bool isLanded;
+        public bool isDecoupler;
+        public int decoupledInStage;
+        public int inverseStage;
+        public int cost;
+        double baseMass = 0d;
+        double startMass = 0d;
+        public String noCrossFeedNodeKey;
+        public bool fuelCrossFeed;
+        public bool isEngine;
+        public bool isFuelLine;
+        public bool isFuelTank;
+        public bool isSepratron;
+        public bool hasMultiModeEngine;
+        public bool hasModuleEnginesFX;
+        public bool hasModuleEngines;
+        public bool isNoPhysics;
+        public bool localCorrectThrust;
+
+        public PartSim(Part thePart, int id, double atmosphere)
+        {
+            this.part = thePart;
+            this.partId = id;
+            this.name = this.part.partInfo.name;
+#if LOG
+            MonoBehaviour.print("Create PartSim for " + name);
+#endif
+            this.parent = null;
+            this.fuelCrossFeed = this.part.fuelCrossFeed;
+            this.noCrossFeedNodeKey = this.part.NoCrossFeedNodeKey;
+            this.decoupledInStage = this.DecoupledInStage(this.part);
+            this.isFuelLine = this.part is FuelLine;
+            this.isFuelTank = this.part is FuelTank;
+            this.isSepratron = this.IsSepratron();
+            this.inverseStage = this.part.inverseStage;
+            //MonoBehaviour.print("inverseStage = " + inverseStage);
+
+            this.cost = this.part.partInfo.cost;
+
+            // Work out if the part should have no physical significance
+            this.isNoPhysics = this.part.HasModule<ModuleLandingGear>() ||
+                            this.part.HasModule<LaunchClamp>() ||
+                            this.part.physicalSignificance == Part.PhysicalSignificance.NONE ||
+                            this.part.PhysicsSignificance == 1;
+
+            if (!this.isNoPhysics)
+                this.baseMass = this.part.mass;
+#if LOG
+            MonoBehaviour.print((isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + part.mass);
+#endif
+            foreach (PartResource resource in this.part.Resources)
+            {
+                // Make sure it isn't NaN as this messes up the part mass and hence most of the values
+                // This can happen if a resource capacity is 0 and tweakable
+                if (!Double.IsNaN(resource.amount))
+                {
+#if LOG
+                    MonoBehaviour.print(resource.resourceName + " = " + resource.amount);
+#endif
+                    this.resources.Add(resource.info.id, resource.amount);
+                    this.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0);
+                }
+                else
+                {
+                    MonoBehaviour.print(resource.resourceName + " is NaN. Skipping.");
+                }
+            }
+
+            this.startMass = this.GetMass();
+
+            this.hasVessel = (this.part.vessel != null);
+            this.isLanded = this.hasVessel && this.part.vessel.Landed;
+
+            this.hasMultiModeEngine = this.part.HasModule<MultiModeEngine>();
+            this.hasModuleEnginesFX = this.part.HasModule<ModuleEnginesFX>();
+            this.hasModuleEngines = this.part.HasModule<ModuleEngines>();
+
+            this.isEngine = this.hasMultiModeEngine || this.hasModuleEnginesFX || this.hasModuleEngines;
+#if LOG
+            MonoBehaviour.print("Created " + name + ". Decoupled in stage " + decoupledInStage);
+#endif
+        }
+
+        public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere)
+        {
+            bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(this.part);
+            //MonoBehaviour.print("Engine " + name + " correctThrust = " + correctThrust);
+#if LOG
+            LogMsg log = new LogMsg();
+            log.buf.AppendLine("CreateEngineSims for " + name);
+
+            foreach (PartModule partMod in part.Modules)
+            {
+                log.buf.AppendLine("Module: " + partMod.moduleName);
+            }
+
+            log.buf.AppendLine("correctThrust = " + correctThrust);
+#endif
+
+            if (this.hasMultiModeEngine)
+            {
+                // A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point
+                // The mode of the engine is the engineID of the ModuleEnginesFX that is active
+                string mode = this.part.GetModule<MultiModeEngine>().mode;
+
+                foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())
+                {
+                    if (engine.engineID == mode)
+                    {
+                        EngineSim engineSim = new EngineSim(this, atmosphere,
+                                                            engine.maxThrust,
+                                                            engine.thrustPercentage,
+                                                            engine.requestedThrust,
+                                                            engine.realIsp,
+                                                            engine.atmosphereCurve,
+                                                            engine.throttleLocked,
+                                                            engine.propellants,
+                                                            correctThrust);
+                        allEngines.Add(engineSim);
+                    }
+                }
+            }
+            else
+            {
+                if (this.hasModuleEnginesFX)
+                {
+                    foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())
+                    {
+                        EngineSim engineSim = new EngineSim(this, atmosphere,
+                                                            engine.maxThrust,
+                                                            engine.thrustPercentage,
+                                                            engine.requestedThrust,
+                                                            engine.realIsp,
+                                                            engine.atmosphereCurve,
+                                                            engine.throttleLocked,
+                                                            engine.propellants,
+                                                            correctThrust);
+                        allEngines.Add(engineSim);
+                    }
+                }
+
+                if (this.hasModuleEngines)
+                {
+                    foreach (ModuleEngines engine in this.part.GetModules<ModuleEngines>())
+                    {
+                        EngineSim engineSim = new EngineSim(this, atmosphere,
+                                                            engine.maxThrust,
+                                                            engine.thrustPercentage,
+                                                            engine.requestedThrust,
+                                                            engine.realIsp,
+                                                            engine.atmosphereCurve,
+                                                            engine.throttleLocked,
+                                                            engine.propellants,
+                                                            correctThrust);
+                        allEngines.Add(engineSim);
+                    }
+                }
+            }
+#if LOG
+            log.Flush();
+#endif
+        }
+
+
+        public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup)
+        {
+#if LOG
+            LogMsg log = new LogMsg();
+            log.buf.AppendLine("SetupAttachNodes for " + name + ":" + partId + "");
+#endif
+            this.attachNodes.Clear();
+            foreach (AttachNode attachNode in this.part.attachNodes)
+            {
+#if LOG
+                log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null"));
+#endif
+                if (attachNode.attachedPart != null && attachNode.id != "Strut")
+                {
+                    PartSim attachedSim;
+                    if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim))
+                    {
+#if LOG
+                        log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + "");
+#endif
+                        this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType));
+                    }
+                    else
+                    {
+#if LOG
+                        log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");
+#endif
+                    }
+                }
+            }
+
+            if (this.isFuelLine)
+            {
+                if ((this.part as FuelLine).target != null)
+                {
+                    PartSim targetSim;
+                    if (partSimLookup.TryGetValue((this.part as FuelLine).target, out targetSim))
+                    {
+#if LOG
+                        log.buf.AppendLine("Fuel line target is " + targetSim.name + ":" + targetSim.partId);
+#endif
+                        this.fuelLineTarget = targetSim;
+                    }
+                    else
+                    {
+#if LOG
+                        log.buf.AppendLine("No PartSim for fuel line target (" + part.partInfo.name + ")");
+#endif
+                        this.fuelLineTarget = null;
+                    }
+
+                }
+                else
+                {
+#if LOG
+                    log.buf.AppendLine("Fuel line target is null");
+#endif
+                    this.fuelLineTarget = null;
+                }
+            }
+
+            if (this.part.parent != null)
+            {
+                this.parent = null;
+                if (partSimLookup.TryGetValue(this.part.parent, out this.parent))
+                {
+#if LOG
+                    log.buf.AppendLine("Parent part is " + parent.name + ":" + parent.partId);
+#endif
+                }
+                else
+                {
+#if LOG
+                    log.buf.AppendLine("No PartSim for parent part (" + part.parent.partInfo.name + ")");
+#endif
+                }
+            }
+#if LOG
+            log.Flush();
+#endif
+        }
+
+        private int DecoupledInStage(Part thePart, int stage = -1)
+        {
+            if (this.IsDecoupler(thePart))
+            {
+                if (thePart.inverseStage > stage)
+                {
+                    stage = thePart.inverseStage;
+                }
+            }
+
+            if (thePart.parent != null)
+            {
+                stage = this.DecoupledInStage(thePart.parent, stage);
+            }
+
+            return stage;
+        }
+
+        private bool IsDecoupler(Part thePart)
+        {
+            return thePart.HasModule<ModuleDecouple>() ||
+                    thePart.HasModule<ModuleAnchoredDecoupler>();
+        }
+
+        private bool IsActiveDecoupler(Part thePart)
+        {
+            return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) ||
+                    thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled);
+        }
+
+        private bool IsSepratron()
+        {
+            if (!this.part.ActivatesEvenIfDisconnected)
+                return false;
+
+            if (this.part is SolidRocket)
+                return true;
+
+            var modList = this.part.Modules.OfType<ModuleEngines>();
+            if (modList.Count() == 0)
+                return false;
+
+            if (modList.First().throttleLocked == true)
+                return true;
+
+            return false;
+        }
+
+        public void ReleasePart()
+        {
+            this.part = null;
+        }
+
+
+        // All functions below this point must not rely on the part member (it may be null)
+        //
+
+        public HashSet<PartSim> GetSourceSet(int type, List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> visited, LogMsg log, String indent)
+        {
+#if LOG
+            log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + name + ":" + partId);
+            indent += "  ";
+#endif
+            HashSet<PartSim> allSources = new HashSet<PartSim>();
+            HashSet<PartSim> partSources = null;
+
+            // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns empty list.
+            if (visited.Contains(this))
+            {
+#if LOG
+                log.buf.AppendLine(indent + "Returning empty set, already visited (" + name + ":" + partId + ")");
+#endif
+                return allSources;
+            }
+
+#if LOG
+            log.buf.AppendLine("Adding this to visited");
+#endif
+            visited.Add(this);
+
+            // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result.
+            //MonoBehaviour.print("foreach fuel line");
+            
+            foreach (PartSim partSim in allFuelLines)
+            {
+                if (partSim.fuelLineTarget == this)
+                {
+#if LOG
+                    log.buf.AppendLine(indent + "Adding fuel line as source (" + partSim.name + ":" + partSim.partId + ")");
+#endif
+                    partSources = partSim.GetSourceSet(type, allParts, allFuelLines, visited, log, indent);
+                    if (partSources.Count > 0)
+                    {
+                        allSources.UnionWith(partSources);
+                        partSources.Clear();
+                    }
+                }
+            }
+
+            if (allSources.Count > 0)
+            {
+#if LOG
+                log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel line sources (" + name + ":" + partId + ")");
+#endif
+                return allSources;
+            }
+
+            // Rule 3: If the part is not crossfeed capable, it returns empty list.
+            //MonoBehaviour.print("Test crossfeed");
+            if (!this.fuelCrossFeed)
+            {
+#if LOG
+                log.buf.AppendLine(indent + "Returning empty set, no cross feed (" + name + ":" + partId + ")");
+#endif
+                return allSources;
+            }
+
+            // Rule 4: Part performs scan on each of its axially mounted neighbors. 
+            //  Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side, skip the points on the side where multiple points are. [Experiment]
+            //  Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list. 
+            //  The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment]
+            //MonoBehaviour.print("foreach attach node");
+            foreach (AttachNodeSim attachSim in this.attachNodes)
+            {
+                if (attachSim.attachedPartSim != null)
+                {
+                    if (attachSim.nodeType == AttachNode.NodeType.Stack &&
+                        (attachSim.attachedPartSim.fuelCrossFeed || attachSim.attachedPartSim.isFuelTank) &&
+                        !(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey)))
+                    {
+#if LOG
+                        log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
+#endif
+                        partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, allFuelLines, visited, log, indent);
+                        if (partSources.Count > 0)
+                        {
+                            allSources.UnionWith(partSources);
+                            partSources.Clear();
+                        }
+                    }
+                }
+            }
+
+            if (allSources.Count > 0)
+            {
+#if LOG
+                log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + name + ":" + partId + ")");
+#endif
+                return allSources;
+            }
+
+            // Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled [Experiment]) and it contains fuel, it returns itself.
+            // Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment]
+            if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0)
+            {
+                if (this.resources[type] > SimManager.RESOURCE_MIN)
+                {
+                    allSources.Add(this);
+#if LOG
+                    log.buf.AppendLine(indent + "Returning enabled tank as only source (" + name + ":" + partId + ")");
+#endif
+                }
+                else
+                {
+#if LOG
+                    log.buf.AppendLine(indent + "Returning empty set, enabled tank is empty (" + name + ":" + partId + ")");
+#endif
+                }
+
+                return allSources;
+            }
+
+            // Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its parent and returns whatever the parent scan returned. [Experiment] [Experiment]
+            if (this.parent != null)
+            {
+                allSources = this.parent.GetSourceSet(type, allParts, allFuelLines, visited, log, indent);
+                if (allSources.Count > 0)
+                {
+#if LOG
+                    log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + name + ":" + partId + ")");
+#endif
+                    return allSources;
+                }
+            }
+
+            // Rule 8: If all preceding rules failed, part returns empty list.
+#if LOG
+            log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")");
+#endif
+            return allSources;
+        }
+
+
+        public void RemoveAttachedParts(HashSet<PartSim> partSims)
+        {
+            // Loop through the attached parts
+            foreach (AttachNodeSim attachSim in this.attachNodes)
+            {
+                // If the part is in the set then "remove" it by clearing the PartSim reference
+                if (partSims.Contains(attachSim.attachedPartSim))
+                    attachSim.attachedPartSim = null;
+            }
+        }
+
+
+        public void DrainResources(double time)
+        {
+            //MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")");
+            foreach (int type in this.resourceDrains.Types)
+            {
+                //MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type));
+                this.resources.Add(type, -time * this.resourceDrains[type]);
+                //MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]);
+            }
+        }
+
+        public double TimeToDrainResource()
+        {
+            //MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")");
+            double time = double.MaxValue;
+
+            foreach (int type in this.resourceDrains.Types)
+            {
+                if (this.resourceDrains[type] > 0)
+                {
+                    time = Math.Min(time, this.resources[type] / this.resourceDrains[type]);
+                    //MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + "  amount = " + resources[type] + "  rate = " + resourceDrains[type] + "  time = " + time);
+                }
+            }
+
+            //if (time < double.MaxValue)
+            //    MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time);
+            return time;
+        }
+
+        public int DecouplerCount()
+        {
+            int count = 0;
+            PartSim partSim = this;
+            while (partSim != null)
+            {
+                if (partSim.isDecoupler)
+                    count++;
+
+                partSim = partSim.parent;
+            }
+            return count;
+        }
+
+        public double GetStartMass()
+        {
+            return this.startMass;
+        }
+
+        public double GetMass()
+        {
+            double mass = this.baseMass;
+
+            foreach (int type in this.resources.Types)
+                mass += this.resources.GetResourceMass(type);
+
+            return mass;
+        }
+
+        public ResourceContainer Resources
+        {
+            get
+            {
+                return this.resources;
+            }
+        }
+#if false
+        public ResourceContainer ResourceConsumptions
+        {
+            get
+            {
+                return resourceConsumptions;
+            }
+        }
+#endif
+        public ResourceContainer ResourceDrains
+        {
+            get
+            {
+                return this.resourceDrains;
+            }
+        }
+
+#if LOG || true
+        public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix)
+        {
+            if (this.parent != null)
+            {
+                prefix = this.parent.DumpPartAndParentsToBuffer(buffer, prefix) + " ";
+            }
+
+            this.DumpPartToBuffer(buffer, prefix);
+
+            return prefix;
+        }
+
+        public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null)
+        {
+            buffer.Append(prefix);
+            buffer.Append(this.name);
+            buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", this.partId, this.decoupledInStage, this.inverseStage);
+
+            buffer.AppendFormat(", fuelCF = {0}", this.fuelCrossFeed);
+            buffer.AppendFormat(", noCFNKey = '{0}'", this.noCrossFeedNodeKey);
+
+            if (this.isFuelLine)
+                buffer.AppendFormat(", fuelLineTarget = {0:d}", this.fuelLineTarget == null ? -1 : this.fuelLineTarget.partId);
+            
+            buffer.AppendFormat(", isSep = {0}", this.isSepratron);
+
+            foreach (int type in this.resources.Types)
+                buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), this.resources[type]);
+
+            if (this.attachNodes.Count > 0)
+            {
+                buffer.Append(", attached = <");
+                this.attachNodes[0].DumpToBuffer(buffer);
+                for (int i = 1; i < this.attachNodes.Count; i++)
+                {
+                    buffer.Append(", ");
+                    this.attachNodes[i].DumpToBuffer(buffer);
+                }
+                buffer.Append(">");
+            }
+
+            // Add more info here
+
+            buffer.Append("]\n");
+
+            if (allParts != null)
+            {
+                String newPrefix = prefix + " ";
+                foreach (PartSim partSim in allParts)
+                {
+                    if (partSim.parent == this)
+                        partSim.DumpPartToBuffer(buffer, newPrefix, allParts);
+                }
+            }
+        }
+#endif
+    }
+}
+

--- a/KerbalEngineer/Simulation/ResourceContainer.cs
+++ b/KerbalEngineer/Simulation/ResourceContainer.cs
@@ -16,24 +16,16 @@
             get
             {
                 if (this.resources.ContainsKey(type))
-                {
                     return (double)this.resources[type];
-                }
-                else
-                {
-                    return 0d;
-                }
+
+                return 0d;
             }
             set
             {
                 if (this.resources.ContainsKey(type))
-                {
                     this.resources[type] = value;
-                }
                 else
-                {
                     this.resources.Add(type, value);
-                }
             }
         }
 
@@ -49,9 +41,7 @@
                 List<int> types = new List<int>();
 
                 foreach (int key in this.resources.Keys)
-                {
                     types.Add(key);
-                }
 
                 return types;
             }
@@ -64,9 +54,7 @@
                 double mass = 0d;
 
                 foreach (double resource in this.resources.Values)
-                {
                     mass += resource;
-                }
 
                 return mass;
             }
@@ -78,26 +66,31 @@
             {
                 foreach (int type in this.resources.Keys)
                 {
-                    if ((double)this.resources[type] > 1d)
-                    {
+                    if ((double)this.resources[type] > SimManager.RESOURCE_MIN)
                         return false;
-                    }
                 }
 
                 return true;
             }
         }
 
+        public bool EmptyOf(HashSet<int> types)
+        {
+            foreach (int type in types)
+            {
+                if (this.HasType(type) && (double)this.resources[type] > SimManager.RESOURCE_MIN)
+                    return false;
+            }
+
+            return true;
+        }
+
         public void Add(int type, double amount)
         {
             if (this.resources.ContainsKey(type))
-            {
                 this.resources[type] = (double)this.resources[type] + amount;
-            }
             else
-            {
                 this.resources.Add(type, amount);
-            }
         }
 
         public void Reset()
@@ -115,7 +108,8 @@
 
         public double GetResourceMass(int type)
         {
-            return (double)this.resources[type] * GetResourceDensity(type);
+            double density = GetResourceDensity(type);
+            return density == 0d ? 0d : (double)this.resources[type] * density;
         }
 
         public static ResourceFlowMode GetResourceFlowMode(int type)

--- /dev/null
+++ b/KerbalEngineer/Simulation/SimManager.cs
@@ -1,1 +1,211 @@
-
+using System;

+using System.Collections.Generic;

+using System.Diagnostics;

+using System.Linq;

+using System.Threading;

+

+using UnityEngine;

+

+namespace KerbalEngineer.Simulation
+{
+    public class SimManager
+    {
+        public const double RESOURCE_MIN = 0.0001;
+        
+        private static bool bRequested = false;
+        private static bool bRunning = false;
+        private static Stopwatch timer = new Stopwatch();
+        private static long delayBetweenSims = 0;
+
+        public static Stage[] Stages { get; private set; }
+        public static Stage LastStage { get; private set; }
+        public static String failMessage { get; private set; }
+
+        public static long minSimTime = 150;
+        public static double Gravity { get; set; }
+        public static double Atmosphere { get; set; }
+
+        // Support for RealFuels using reflection to check localCorrectThrust without dependency
+        private static bool hasCheckedForRealFuels = false;
+        private static bool hasInstalledRealFuels = false;
+
+        private static Type RF_ModuleEngineConfigs_Type = null;
+        private static Type RF_ModuleHybridEngine_Type = null;
+
+        private static System.Reflection.FieldInfo RF_ModuleEngineConfigs_locaCorrectThrust = null;
+        private static System.Reflection.FieldInfo RF_ModuleHybridEngine_locaCorrectThrust = null;
+
+        private static void GetRealFuelsTypes()
+        {
+			hasCheckedForRealFuels = true;
+
+			foreach (AssemblyLoader.LoadedAssembly assembly in AssemblyLoader.loadedAssemblies)
+            {
+                MonoBehaviour.print("Assembly:" + assembly.assembly.ToString());
+
+                if (assembly.assembly.ToString().Split(',')[0] == "RealFuels")
+                {
+                    MonoBehaviour.print("Found RealFuels mod");
+
+                    RF_ModuleEngineConfigs_Type = assembly.assembly.GetType("RealFuels.ModuleEngineConfigs");
+                    if (RF_ModuleEngineConfigs_Type == null)
+                    {
+                        MonoBehaviour.print("Failed to find ModuleEngineConfigs type");
+                        break;
+                    }
+
+                    RF_ModuleEngineConfigs_locaCorrectThrust = RF_ModuleEngineConfigs_Type.GetField("localCorrectThrust");
+                    if (RF_ModuleEngineConfigs_locaCorrectThrust == null)
+                    {
+                        MonoBehaviour.print("Failed to find ModuleEngineConfigs.localCorrectThrust field");
+                        break;
+                    }
+
+                    RF_ModuleHybridEngine_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngine");
+                    if (RF_ModuleHybridEngine_Type == null)
+                    {
+                        MonoBehaviour.print("Failed to find ModuleHybridEngine type");
+                        break;
+                    }
+                    
+                    RF_ModuleHybridEngine_locaCorrectThrust = RF_ModuleHybridEngine_Type.GetField("localCorrectThrust");
+                    if (RF_ModuleHybridEngine_locaCorrectThrust == null)
+                    {
+                        MonoBehaviour.print("Failed to find ModuleHybridEngine.localCorrectThrust field");
+                        break;
+                    }
+                    
+					hasInstalledRealFuels = true;
+					break;
+				}
+
+			}
+
+		}
+
+        public static bool DoesEngineUseCorrectedThrust(Part theEngine)
+        {
+            if (!hasInstalledRealFuels /*|| HighLogic.LoadedSceneIsFlight*/)
+                return false;
+
+            // Look for either of the Real Fuels engine modules and call the relevant method to find out
+            PartModule modEngineConfigs = theEngine.Modules["ModuleEngineConfigs"];
+            if (modEngineConfigs != null)
+            {
+                // Check the localCorrectThrust
+                if ((bool)RF_ModuleEngineConfigs_locaCorrectThrust.GetValue(modEngineConfigs))
+                    return true;
+            }
+
+            PartModule modHybridEngine = theEngine.Modules["ModuleHybridEngine"];
+            if (modHybridEngine != null)
+            {
+                // Check the localCorrectThrust
+                if ((bool)RF_ModuleHybridEngine_locaCorrectThrust.GetValue(modHybridEngine))
+                    return true;
+            }
+
+            return false;
+        }
+
+
+        public static void RequestSimulation()
+        {
+            if (!hasCheckedForRealFuels)
+                GetRealFuelsTypes();
+
+            bRequested = true;
+            if (!timer.IsRunning)
+                timer.Start();
+        }
+
+        public static void TryStartSimulation()
+        {
+            if (bRequested && !bRunning && (HighLogic.LoadedSceneIsEditor || FlightGlobals.ActiveVessel != null) && timer.ElapsedMilliseconds > delayBetweenSims)
+            {
+                bRequested = false;
+                timer.Reset();
+                StartSimulation();
+            }
+        }
+
+        public static bool ResultsReady()
+        {
+            return !bRunning;
+        }
+
+        private static void ClearResults()
+        {
+            failMessage = "";
+            Stages = null;
+            LastStage = null;
+        }
+
+        private static void StartSimulation()
+        {
+            try
+            {
+                bRunning = true;
+                ClearResults();
+                timer.Start();
+
+                List<Part> parts = HighLogic.LoadedSceneIsEditor ? EditorLogic.SortedShipList : FlightGlobals.ActiveVessel.Parts;
+
+                // Create the Simulation object in this thread
+                Simulation sim = new Simulation();
+
+                // This call doesn't ever fail at the moment but we'll check and return a sensible error for display
+                if (sim.PrepareSimulation(parts, Gravity, Atmosphere))
+                {
+                    ThreadPool.QueueUserWorkItem(RunSimulation, sim);
+                }
+                else
+                {
+                    failMessage = "PrepareSimulation failed";
+                    bRunning = false;
+                }
+            }
+            catch (Exception e)
+            {
+                MonoBehaviour.print("Exception in StartSimulation: " + e);
+                failMessage = e.ToString();
+                bRunning = false;
+            }
+        }
+
+        private static void RunSimulation(object simObject)
+        {
+            try
+            {
+                Stages = (simObject as Simulation).RunSimulation();
+                if (Stages != null)
+                {
+#if LOG
+                    foreach (Stage stage in Stages)
+                        stage.Dump();
+#endif
+                    LastStage = Stages.Last();
+                }
+            }
+            catch (Exception e)
+            {
+                MonoBehaviour.print("Exception in RunSimulation: " + e);
+                Stages = null;
+                LastStage = null;
+                failMessage = e.ToString();
+            }
+
+            timer.Stop();
+            MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms");
+            delayBetweenSims = minSimTime - timer.ElapsedMilliseconds;
+            if (delayBetweenSims < 0)
+                delayBetweenSims = 0;
+
+            timer.Reset();
+            timer.Start();
+
+            bRunning = false;
+        }
+    }
+}
+

--- a/KerbalEngineer/Simulation/Simulation.cs
+++ b/KerbalEngineer/Simulation/Simulation.cs
@@ -22,13 +22,14 @@
         private HashSet<PartSim> drainingParts;
         private List<EngineSim> allEngines;
         private List<EngineSim> activeEngines;
+        private HashSet<int> drainingResources;
 
         private int lastStage = 0;
         private int currentStage = 0;
 
         private double gravity = 0;
         private double atmosphere = 0;
-#if LOG
+#if LOG || TIMERS
         private Stopwatch _timer = new Stopwatch();
 #endif
         private const double STD_GRAVITY = 9.81d;
@@ -49,6 +50,8 @@
         {
 #if LOG
             MonoBehaviour.print("PrepareSimulation started");
+#endif
+#if LOG || TIMERS
             _timer.Start();
 #endif
             // Store the parameters in members for ease of access in other functions
@@ -64,6 +67,7 @@
             this.drainingParts = new HashSet<PartSim>();
             this.allEngines = new List<EngineSim>();
             this.activeEngines = new List<EngineSim>();
+            this.drainingResources = new HashSet<int>();
 
             // A dictionary for fast lookup of Part->PartSim during the preparation phase
             Dictionary<Part, PartSim> partSimLookup = new Dictionary<Part, PartSim>();
@@ -109,9 +113,12 @@
 
             // And dereference the core's part list
             this.partList = null;
-#if LOG
+
+#if LOG || TIMERS
             _timer.Stop();
-            MonoBehaviour.print("PrepareSimulation took " + _timer.ElapsedMilliseconds + "ms");
+            MonoBehaviour.print("PrepareSimulation: " + _timer.ElapsedMilliseconds + "ms");
+#endif
+#if LOG
             Dump();
 #endif
             return true;
@@ -123,6 +130,9 @@
         {
 #if LOG
             MonoBehaviour.print("RunSimulation started");
+#endif
+#if LOG || TIMERS
+            _timer.Start();
 #endif
             // Start with the last stage to simulate
             // (this is in a member variable so it can be accessed by AllowedToStage and ActiveStage)
@@ -175,18 +185,22 @@
                     currentisp = 0;
 
                 // Store various things in the Stage object
-                stage.Thrust = totalStageThrust;
-                stage.ThrustToWeight = (double)(totalStageThrust / (stageStartMass * this.gravity));
-                stage.ActualThrust = totalStageActualThrust;
-                stage.ActualThrustToWeight = (double)(totalStageActualThrust / (stageStartMass * this.gravity));
+                stage.thrust = totalStageThrust;
+                //MonoBehaviour.print("stage.thrust = " + stage.thrust);
+                stage.thrustToWeight = totalStageThrust / (stageStartMass * this.gravity);
+                stage.maxThrustToWeight = stage.thrustToWeight;
+                //MonoBehaviour.print("StageMass = " + stageStartMass);
+                //MonoBehaviour.print("Initial maxTWR = " + stage.maxThrustToWeight);
+                stage.actualThrust = totalStageActualThrust;
+                stage.actualThrustToWeight = totalStageActualThrust / (stageStartMass * this.gravity);
 
                 // Calculate the cost and mass of this stage
                 foreach (PartSim partSim in this.allParts)
                 {
                     if (partSim.decoupledInStage == this.currentStage - 1)
                     {
-                        stage.Cost += partSim.cost;
-                        stage.Mass += partSim.GetStartMass();
+                        stage.cost += partSim.cost;
+                        stage.mass += partSim.GetStartMass();
                     }
                 }
 #if LOG
@@ -201,14 +215,18 @@
 
                     // Calculate how long each draining tank will take to drain and run for the minimum time
                     double resourceDrainTime = double.MaxValue;
+                    PartSim partMinDrain = null;
                     foreach (PartSim partSim in this.drainingParts)
                     {
                         double time = partSim.TimeToDrainResource();
                         if (time < resourceDrainTime)
+                        {
                             resourceDrainTime = time;
-                    }
-#if LOG
-                    MonoBehaviour.print("Drain time = " + resourceDrainTime);
+                            partMinDrain = partSim;
+                        }
+                    }
+#if LOG
+                    MonoBehaviour.print("Drain time = " + resourceDrainTime + " (" + partMinDrain.name + ":" + partMinDrain.partId + ")");
 #endif
                     foreach (PartSim partSim in this.drainingParts)
                         partSim.DrainResources(resourceDrainTime);
@@ -217,6 +235,15 @@
                     stepEndMass = this.ShipMass;
                     stageTime += resourceDrainTime;
 
+                    double stepEndTWR = totalStageThrust / (stepEndMass * this.gravity);
+                    //MonoBehaviour.print("After drain mass = " + stepEndMass);
+                    //MonoBehaviour.print("currentThrust = " + totalStageThrust);
+                    //MonoBehaviour.print("currentTWR = " + stepEndTWR);
+                    if (stepEndTWR > stage.maxThrustToWeight)
+                        stage.maxThrustToWeight = stepEndTWR;
+
+                    //MonoBehaviour.print("newMaxTWR = " + stage.maxThrustToWeight);
+
                     // If we have drained anything and the masses make sense then add this step's deltaV to the stage total
                     if (resourceDrainTime > 0d && stepStartMass > stepEndMass && stepStartMass > 0d && stepEndMass > 0d)
                         stageDeltaV += (currentisp * STD_GRAVITY) * Math.Log(stepStartMass / stepEndMass);
@@ -224,14 +251,21 @@
                     // Update the active engines and resource drains for the next step
                     this.UpdateResourceDrains();
 
-                    // Recalculate the current isp for the next step
+                    // Recalculate the current thrust and isp for the next step
+                    totalStageThrust = 0d;
+                    totalStageActualThrust = 0d;
                     totalStageFlowRate = 0d;
                     totalStageIspFlowRate = 0d;
                     foreach (EngineSim engine in this.activeEngines)
                     {
+                        totalStageActualThrust += engine.actualThrust;
+                        totalStageThrust += engine.thrust;
+
                         totalStageFlowRate += engine.ResourceConsumptions.Mass;
                         totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp;
                     }
+
+                    //MonoBehaviour.print("next step thrust = " + totalStageThrust);
 
                     if (totalStageFlowRate > 0d && totalStageIspFlowRate > 0d)
                         currentisp = totalStageIspFlowRate / totalStageFlowRate;
@@ -261,16 +295,15 @@
 
                 // Store more values in the Stage object and stick it in the array
                 // Recalculate effective stage isp from the stageDeltaV (flip the standard deltaV calculation around)
-                stage.Isp = stageDeltaV / (STD_GRAVITY * Math.Log(stageStartMass / this.ShipMass));

-                if (double.IsNaN(stage.Isp))

-                {

-                    stage.Isp = 0;

-                }
-                stage.DeltaV = stageDeltaV;
+                // Note: If the mass doesn't change then this is a divide by zero
+                if (stageStartMass != stepStartMass)
+                    stage.isp = stageDeltaV / (STD_GRAVITY * Math.Log(stageStartMass / stepStartMass));
+                else
+                    stage.isp = 0;
+                stage.deltaV = stageDeltaV;
                 // Zero stage time if more than a day (this should be moved into the window code)
-                stage.Time = (stageTime < SECONDS_PER_DAY) ? stageTime : 0d;
-                stage.Number = this.currentStage;

-                stage.PartCount = this.allParts.Count;
+                stage.time = (stageTime < SECONDS_PER_DAY) ? stageTime : 0d;
+                stage.number = this.currentStage;
                 stages[this.currentStage] = stage;
 
                 // Now activate the next stage
@@ -298,23 +331,26 @@
                 // For each stage we total up the cost, mass, deltaV and time for this stage and all the stages above
                 for (int j = i; j >= 0; j--)
                 {
-                    stages[i].TotalCost += stages[j].Cost;
-                    stages[i].TotalMass += stages[j].Mass;
-                    stages[i].TotalDeltaV += stages[j].DeltaV;
-                    stages[i].TotalTime += stages[j].Time;
+                    stages[i].totalCost += stages[j].cost;
+                    stages[i].totalMass += stages[j].mass;
+                    stages[i].totalDeltaV += stages[j].deltaV;
+                    stages[i].totalTime += stages[j].time;
                 }
                 // We also total up the deltaV for stage and all stages below
                 for (int j = i; j < stages.Length; j++)
                 {
-                    stages[i].InverseTotalDeltaV += stages[j].DeltaV;
+                    stages[i].inverseTotalDeltaV += stages[j].deltaV;
                 }
 
                 // Zero the total time if the value will be huge (24 hours?) to avoid the display going weird
                 // (this should be moved into the window code)
-                if (stages[i].TotalTime > SECONDS_PER_DAY)
-                    stages[i].TotalTime = 0d;
-            }
-
+                if (stages[i].totalTime > SECONDS_PER_DAY)
+                    stages[i].totalTime = 0d;
+            }
+#if LOG || TIMERS
+            _timer.Stop();
+            MonoBehaviour.print("RunSimulation: " + _timer.ElapsedMilliseconds + "ms");
+#endif
             return stages;
         }
 
@@ -323,8 +359,9 @@
         // and setting the drain rates
         private void UpdateResourceDrains()
         {
-            // Empty the active engines list
+            // Empty the active engines list and the draining resources set
             this.activeEngines.Clear();
+            this.drainingResources.Clear();
 
             // Reset the resource drains of all draining parts
             foreach (PartSim partSim in this.drainingParts)
@@ -341,7 +378,11 @@
                 {
                     // Set the resource drains for this engine and add it to the active list if it is active
                     if (engine.SetResourceDrains(this.allParts, this.allFuelLines, this.drainingParts))
+                    {
                         this.activeEngines.Add(engine);
+                        foreach (int type in engine.ResourceConsumptions.Types)
+                            this.drainingResources.Add(type);
+                    }
                 }
             }
 #if LOG
@@ -357,35 +398,42 @@
         // This function works out if it is time to stage
         private bool AllowedToStage()
         {
-            //StringBuilder buffer = new StringBuilder(1024);
-            //buffer.Append("AllowedToStage\n");
-            //buffer.AppendFormat("currentStage = {0:d}\n", currentStage);
-
+#if LOG
+            StringBuilder buffer = new StringBuilder(1024);
+            buffer.AppendLine("AllowedToStage");
+            buffer.AppendFormat("currentStage = {0:d}\n", currentStage);
+#endif
             if (this.activeEngines.Count == 0)
             {
-                //buffer.Append("No active engines => true\n");
-                //MonoBehaviour.print(buffer);
+#if LOG
+                buffer.AppendLine("No active engines => true");
+                MonoBehaviour.print(buffer);
+#endif
                 return true;
             }
 
             foreach (PartSim partSim in this.allParts)
             {
-                //partSim.DumpPartToBuffer(buffer, "Testing: ");
+                //partSim.DumpPartToBuffer(buffer, "Testing: ", allParts);
                 //buffer.AppendFormat("isSepratron = {0}\n", partSim.isSepratron ? "true" : "false");
                 if (partSim.decoupledInStage == (this.currentStage - 1) && (!partSim.isSepratron || partSim.decoupledInStage < partSim.inverseStage))
                 {
-                    if (!partSim.Resources.Empty)
-                    {
-                        //buffer.Append("Decoupled part not empty => false\n");
-                        //MonoBehaviour.print(buffer);
+                    if (!partSim.Resources.EmptyOf(this.drainingResources))
+                    {
+#if LOG
+                        partSim.DumpPartToBuffer(buffer, "Decoupled part not empty => false: ");
+                        MonoBehaviour.print(buffer);
+#endif
                         return false;
                     }
                     foreach (EngineSim engine in this.activeEngines)
                     {
                         if (engine.partSim == partSim)
                         {
-                            //buffer.Append("Decoupled part is active engine => false\n");
-                            //MonoBehaviour.print(buffer);
+#if LOG
+                            partSim.DumpPartToBuffer(buffer, "Decoupled part is active engine => false: ");
+                            MonoBehaviour.print(buffer);
+#endif
                             return false;
                         }
                     }
@@ -394,13 +442,17 @@
 
             if (this.currentStage > 0)
             {
-                //buffer.Append("Current stage > 0 => true\n");
-                //MonoBehaviour.print(buffer);
+#if LOG
+                buffer.AppendLine("Current stage > 0 => true");
+                MonoBehaviour.print(buffer);
+#endif
                 return true;
             }
 
-            //buffer.Append("Returning false\n");
-            //MonoBehaviour.print(buffer);
+#if LOG
+            buffer.AppendLine("Returning false");
+            MonoBehaviour.print(buffer);
+#endif
             return false;
         }
 
@@ -442,21 +494,6 @@
             }
         }
 
-
-        private bool StageHasSolids
-        {
-            get
-            {
-                foreach (EngineSim engine in this.activeEngines)
-                {
-                    if (engine.partSim.isSolidMotor)
-                        return true;
-                }
-
-                return false;
-            }
-        }
-
         private double ShipStartMass
         {
             get

--- a/KerbalEngineer/Simulation/SimulationManager.cs
+++ b/KerbalEngineer/Simulation/SimulationManager.cs
@@ -5,14 +5,8 @@
 #region Using Directives

 

 using System;

-using System.Collections.Generic;

-using System.Diagnostics;

-using System.Linq;

-using System.Threading;

 

 using KerbalEngineer.Flight;

-

-using UnityEngine;

 

 #endregion

 

@@ -20,125 +14,67 @@
 {

     public class SimulationManager : IUpdatable

     {

+        #region Instance

+

         private static readonly SimulationManager instance = new SimulationManager();

 

+        /// <summary>

+        ///     Gets the current instance of the simulation manager.

+        /// </summary>

         public static SimulationManager Instance

         {

             get { return instance; }

         }

 

-        private bool bRequested;

-        private bool bRunning;

-        private readonly Stopwatch timer = new Stopwatch();

-        private long delayBetweenSims;

+        #endregion

 

-        private Stopwatch _func = new Stopwatch();

+        #region Properties

 

-        public Stage[] Stages { get; private set; }

-        public Stage LastStage { get; private set; }

-        public String failMessage { get; private set; }

-

-        public double Gravity { get; set; }

-        public double Atmosphere { get; set; }

-

-        public void RequestSimulation()

+        public Stage[] Stages

         {

-            bRequested = true;

-            if (!timer.IsRunning)

-            {

-                timer.Start();

-            }

+            get { return SimManager.Stages; }

         }

 

-        public void TryStartSimulation()

+        public Stage LastStage

         {

-            if (bRequested && !bRunning && (HighLogic.LoadedSceneIsEditor || FlightGlobals.ActiveVessel != null) && timer.ElapsedMilliseconds > delayBetweenSims)

-            {

-                bRequested = false;

-                timer.Reset();

-                StartSimulation();

-            }

+            get { return SimManager.LastStage; }

         }

 

-        public bool ResultsReady()

+        public double Gravity

         {

-            return !bRunning;

+            get { return SimManager.Gravity; }

+            set { SimManager.Atmosphere = value; }

         }

 

-        private void ClearResults()

+        public double Atmosphere

         {

-            failMessage = "";

-            Stages = null;

-            LastStage = null;

+            get { return SimManager.Atmosphere; }

+            set { SimManager.Atmosphere = value; }

         }

 

-        private void StartSimulation()

-        {

-            try

-            {

-                bRunning = true;

-                ClearResults();

-                timer.Start();

+        #endregion

 

-                List<Part> parts = HighLogic.LoadedSceneIsEditor ? EditorLogic.SortedShipList : FlightGlobals.ActiveVessel.Parts;

-

-                // Create the Simulation object in this thread

-                var sim = new Simulation();

-

-                // This call doesn't ever fail at the moment but we'll check and return a sensible error for display

-                if (sim.PrepareSimulation(parts, Gravity, Atmosphere))

-                {

-                    ThreadPool.QueueUserWorkItem(RunSimulation, sim);

-                }

-                else

-                {

-                    failMessage = "PrepareSimulation failed";

-                    bRunning = false;

-                }

-            }

-            catch (Exception e)

-            {

-                MonoBehaviour.print("Exception in StartSimulation: " + e);

-                failMessage = e.ToString();

-                bRunning = false;

-            }

-        }

-

-        private void RunSimulation(object simObject)

-        {

-            try

-            {

-                Stages = (simObject as Simulation).RunSimulation();

-                if (Stages != null)

-                {

-#if LOG

-                    foreach (Stage stage in Stages)

-                        stage.Dump();

-#endif

-                    LastStage = Stages.Last();

-                }

-            }

-            catch (Exception e)

-            {

-                MonoBehaviour.print("Exception in RunSimulation: " + e);

-                Stages = null;

-                LastStage = null;

-                failMessage = e.ToString();

-            }

-

-            timer.Stop();

-            MonoBehaviour.print("RunSimulation took " + timer.ElapsedMilliseconds + "ms");

-            delayBetweenSims = 10 * timer.ElapsedMilliseconds;

-

-            timer.Reset();

-            timer.Start();

-

-            bRunning = false;

-        }

+        #region IUpdatable Members

 

         public void Update()

         {

             this.TryStartSimulation();

         }

+

+        #endregion

+

+        #region Methods

+

+        public void RequestSimulation()

+        {

+            SimManager.RequestSimulation();

+        }

+

+        public void TryStartSimulation()

+        {

+            SimManager.TryStartSimulation();

+        }

+

+        #endregion

     }

 }

--- a/KerbalEngineer/Simulation/Stage.cs
+++ b/KerbalEngineer/Simulation/Stage.cs
@@ -1,27 +1,27 @@
-// Project:	KerbalEngineer

-// Author:	CYBUTEK

-// License:	Attribution-NonCommercial-ShareAlike 3.0 Unported

+// Kerbal Engineer Redux
+// Author:  CYBUTEK
+// License: Attribution-NonCommercial-ShareAlike 3.0 Unported

 

-namespace KerbalEngineer.Simulation

-{

-    public class Stage

-    {

-        public int Number = 0;

-        public int Cost = 0;

-        public int TotalCost = 0;

-        public int PartCount = 0;

-        public double Time = 0;

-        public double TotalTime = 0;

-        public double Mass = 0;

-        public double TotalMass = 0;

-        public double Isp = 0;

-        public double Thrust = 0;

-        public double ActualThrust = 0;

-        public double ThrustToWeight = 0;

-        public double ActualThrustToWeight = 0;

-        public double DeltaV = 0;

-        public double TotalDeltaV = 0;

-        public double InverseTotalDeltaV = 0;

+namespace KerbalEngineer.Simulation
+{
+    public class Stage
+    {
+        public int number = 0;
+        public int cost = 0;
+        public int totalCost = 0;
+        public double time = 0f;
+        public double totalTime = 0f;
+        public double mass = 0f;
+        public double totalMass = 0f;
+        public double isp = 0f;
+        public double thrust = 0f;
+        public double actualThrust = 0f;
+        public double thrustToWeight = 0f;
+        public double maxThrustToWeight = 0f;
+        public double actualThrustToWeight = 0f;
+        public double deltaV = 0f;
+        public double totalDeltaV = 0f;
+        public double inverseTotalDeltaV = 0f;
 #if LOG
         public void Dump()
         {
@@ -37,6 +37,7 @@
             str.AppendFormat("thrust        : {0:g6}\n", thrust);
             str.AppendFormat("actualThrust  : {0:g6}\n", actualThrust);
             str.AppendFormat("thrustToWeight: {0:g6}\n", thrustToWeight);
+            str.AppendFormat("maxTWR        : {0:g6}\n", maxThrustToWeight);
             str.AppendFormat("actualTWR     : {0:g6}\n", actualThrustToWeight);
             str.AppendFormat("deltaV        : {0:g6}\n", deltaV);
             str.AppendFormat("totalDeltaV   : {0:g6}\n", totalDeltaV);
@@ -44,6 +45,7 @@
             
             MonoBehaviour.print(str);
         }
-#endif

-    }

+#endif
+    }
 }
+

 Binary files a/Output/KerbalEngineer/KerbalEngineer.dll and b/Output/KerbalEngineer/KerbalEngineer.dll differ