Updated simulation logic.
Updated simulation logic.

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

 using KerbalEngineer.Extensions;

 using KerbalEngineer.Settings;

-using KerbalEngineer.Simulation;

+using KerbalEngineer.VesselSimulator;

 

 using UnityEngine;

 


--- a/KerbalEngineer/Editor/BuildOverlay.cs
+++ b/KerbalEngineer/Editor/BuildOverlay.cs
@@ -24,7 +24,7 @@
 

 using KerbalEngineer.Extensions;

 using KerbalEngineer.Settings;

-using KerbalEngineer.Simulation;

+using KerbalEngineer.VesselSimulator;

 

 using UnityEngine;

 


--- a/KerbalEngineer/Flight/ActionMenu.cs
+++ b/KerbalEngineer/Flight/ActionMenu.cs
@@ -24,7 +24,7 @@
 

 using KerbalEngineer.Flight.Sections;

 using KerbalEngineer.Settings;

-using KerbalEngineer.Simulation;

+using KerbalEngineer.VesselSimulator;

 

 using UnityEngine;

 


--- a/KerbalEngineer/Flight/Readouts/Vessel/DeltaVStaged.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/DeltaVStaged.cs
@@ -19,7 +19,7 @@
 

 #region Using Directives

 

-using KerbalEngineer.Simulation;

+using KerbalEngineer.VesselSimulator;

 

 #endregion

 


--- a/KerbalEngineer/Flight/Readouts/Vessel/DeltaVTotal.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/DeltaVTotal.cs
@@ -17,9 +17,9 @@
 //     along with this program.  If not, see <http://www.gnu.org/licenses/>.

 // 

 

-#region Using Directives

+#region

 

-using KerbalEngineer.Simulation;

+using KerbalEngineer.VesselSimulator;

 

 #endregion

 


--- a/KerbalEngineer/Flight/Readouts/Vessel/Mass.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/Mass.cs
@@ -20,7 +20,7 @@
 #region Using Directives

 

 using KerbalEngineer.Extensions;

-using KerbalEngineer.Simulation;

+using KerbalEngineer.VesselSimulator;

 

 #endregion

 


--- a/KerbalEngineer/Flight/Readouts/Vessel/SpecificImpulse.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/SpecificImpulse.cs
@@ -17,9 +17,9 @@
 //     along with this program.  If not, see <http://www.gnu.org/licenses/>.

 // 

 

-#region Using Directives

+#region

 

-using KerbalEngineer.Simulation;

+using KerbalEngineer.VesselSimulator;

 

 #endregion

 


--- a/KerbalEngineer/Flight/Readouts/Vessel/Thrust.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/Thrust.cs
@@ -20,7 +20,7 @@
 #region Using Directives

 

 using KerbalEngineer.Extensions;

-using KerbalEngineer.Simulation;

+using KerbalEngineer.VesselSimulator;

 

 #endregion

 


--- a/KerbalEngineer/Flight/Readouts/Vessel/ThrustToWeight.cs
+++ b/KerbalEngineer/Flight/Readouts/Vessel/ThrustToWeight.cs
@@ -19,7 +19,7 @@
 

 #region Using Directives

 

-using KerbalEngineer.Simulation;

+using KerbalEngineer.VesselSimulator;

 

 #endregion

 


--- a/KerbalEngineer/KerbalEngineer.csproj
+++ b/KerbalEngineer/KerbalEngineer.csproj
@@ -131,14 +131,14 @@
     <Compile Include="Properties\AssemblyInfo.cs" />

     <Compile Include="Settings\SettingHandler.cs" />

     <Compile Include="Settings\SettingItem.cs" />

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

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

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

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

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

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

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

     <Compile Include="TapeDriveAnimator.cs" />

+    <Compile Include="VesselSimulator\AttachNodeSim.cs" />

+    <Compile Include="VesselSimulator\EngineSim.cs" />

+    <Compile Include="VesselSimulator\PartSim.cs" />

+    <Compile Include="VesselSimulator\ResourceContainer.cs" />

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

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

+    <Compile Include="VesselSimulator\Stage.cs" />

   </ItemGroup>

   <ItemGroup />

   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />


--- a/KerbalEngineer/Simulation/AttachNodeSim.cs
+++ /dev/null
@@ -1,40 +1,1 @@
-using System;

-using System.Text;

-

-namespace KerbalEngineer.Simulation
-{
-    class AttachNodeSim
-    {
-        public PartSim attachedPartSim;
-        public AttachNode.NodeType nodeType;
-        public String id;
 
-        public AttachNodeSim(PartSim partSim, String newId, AttachNode.NodeType newNodeType)
-        {
-            this.attachedPartSim = partSim;
-            this.nodeType = newNodeType;
-            this.id = newId;
-        }
-
-#if LOG || true
-        public void DumpToBuffer(StringBuilder buffer)
-        {
-            if (this.attachedPartSim == null)
-            {
-                buffer.Append("<staged>:<n>");
-            }
-            else
-            {
-                buffer.Append(this.attachedPartSim.name);
-                buffer.Append(":");
-                buffer.Append(this.attachedPartSim.partId);
-            }
-            buffer.Append("#");
-            buffer.Append(this.nodeType);
-            buffer.Append(":");
-            buffer.Append(this.id);
-        }
-#endif
-    }
-}
-

--- a/KerbalEngineer/Simulation/EngineSim.cs
+++ /dev/null
@@ -1,281 +1,1 @@
-// Kerbal Engineer Redux
-// Author:  CYBUTEK
-// License: Attribution-NonCommercial-ShareAlike 3.0 Unported

-

-using System;

-using System.Collections.Generic;

-using System.Linq;

-

-using UnityEngine;

-

-namespace KerbalEngineer.Simulation
-{
-    public class EngineSim
-    {
-        ResourceContainer resourceConsumptions = new ResourceContainer();
 
-        public PartSim partSim;
-
-        public double thrust = 0;
-        public double actualThrust = 0;
-        public double isp = 0;
-
-        // Add thrust vector to account for directional losses
-        //public Vector3d thrustVec;
-
-        public EngineSim(PartSim theEngine, double atmosphere,
-                            float maxThrust,
-                            float thrustPercentage,
-                            float requestedThrust,
-                            float realIsp,
-                            FloatCurve atmosphereCurve,
-                            bool throttleLocked,
-                            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)
-                    {
-                        //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
-        }
-
-
-        public bool SetResourceDrains(List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> drainingParts)
-        {
-            // A dictionary to hold a set of parts for each resource
-            Dictionary<int, HashSet<PartSim>> sourcePartSets = new Dictionary<int, HashSet<PartSim>>();
-
-            foreach (int type in this.resourceConsumptions.Types)
-            {
-                HashSet<PartSim> sourcePartSet = null;
-                switch (ResourceContainer.GetResourceFlowMode(type))
-                {
-                    case ResourceFlowMode.NO_FLOW:
-                        if (this.partSim.resources[type] > SimManager.RESOURCE_MIN)
-                        {
-                            sourcePartSet = new HashSet<PartSim>();
-                            //MonoBehaviour.print("SetResourceDrains(" + name + ":" + partId + ") setting sources to just this");
-                            sourcePartSet.Add(this.partSim);
-                        }
-                        break;
-
-                    case ResourceFlowMode.ALL_VESSEL:
-                        foreach (PartSim aPartSim in allParts)
-                        {
-                            if (aPartSim.resources[type] > SimManager.RESOURCE_MIN)
-                            {
-                                if (sourcePartSet == null)
-                                    sourcePartSet = new HashSet<PartSim>();
-
-                                sourcePartSet.Add(aPartSim);
-                            }
-                        }
-                        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>();
-#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 + ") 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
-            foreach (int type in this.resourceConsumptions.Types)
-            {
-                HashSet<PartSim> sourcePartSet = sourcePartSets[type];
-                // Loop through the members of the set 
-                double amount = this.resourceConsumptions[type] / sourcePartSet.Count;
-                foreach (PartSim partSim in sourcePartSet)
-                {
-#if LOG
-                    MonoBehaviour.print("Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" + partSim.partId);
-#endif
-                    partSim.resourceDrains.Add(type, amount);
-                    drainingParts.Add(partSim);
-                }
-            }
-
-            return true;
-        }
-
-
-        public ResourceContainer ResourceConsumptions
-        {
-            get
-            {
-                return this.resourceConsumptions;
-            }
-        }
-
-#if LOG
-        public void DumpEngineToBuffer(StringBuilder buffer, String prefix)
-        {
-            buffer.Append(prefix);
-            buffer.AppendFormat("[thrust = {0:g6}, actual = {1:g6}, isp = {2:g6}\n", thrust, actualThrust, isp);
-        }
-#endif
-    }
-}
-

--- a/KerbalEngineer/Simulation/PartSim.cs
+++ /dev/null
@@ -1,630 +1,1 @@
-// 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 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
+++ /dev/null
@@ -1,136 +1,1 @@
-// Kerbal Engineer Redux
-// Author:  CYBUTEK
-// License: Attribution-NonCommercial-ShareAlike 3.0 Unported

-

-using System.Collections;

-using System.Collections.Generic;

-

-namespace KerbalEngineer.Simulation
-{
-    public class ResourceContainer
-    {
-        Hashtable resources = new Hashtable();
 
-        public double this[int type]
-        {
-            get
-            {
-                if (this.resources.ContainsKey(type))
-                    return (double)this.resources[type];
-
-                return 0d;
-            }
-            set
-            {
-                if (this.resources.ContainsKey(type))
-                    this.resources[type] = value;
-                else
-                    this.resources.Add(type, value);
-            }
-        }
-
-        public bool HasType(int type)
-        {
-            return this.resources.ContainsKey(type);
-        }
-
-        public List<int> Types
-        {
-            get
-            {
-                List<int> types = new List<int>();
-
-                foreach (int key in this.resources.Keys)
-                    types.Add(key);
-
-                return types;
-            }
-        }
-
-        public double Mass
-        {
-            get
-            {
-                double mass = 0d;
-
-                foreach (double resource in this.resources.Values)
-                    mass += resource;
-
-                return mass;
-            }
-        }
-
-        public bool Empty
-        {
-            get
-            {
-                foreach (int type in this.resources.Keys)
-                {
-                    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()
-        {
-            this.resources = new Hashtable();
-        }
-
-        public void Debug()
-        {
-            foreach (int key in this.resources.Keys)
-            {
-                UnityEngine.MonoBehaviour.print(" -> " + GetResourceName(key) + " = " + this.resources[key]);
-            }
-        }
-
-        public double GetResourceMass(int type)
-        {
-            double density = GetResourceDensity(type);
-            return density == 0d ? 0d : (double)this.resources[type] * density;
-        }
-
-        public static ResourceFlowMode GetResourceFlowMode(int type)
-        {
-            return PartResourceLibrary.Instance.GetDefinition(type).resourceFlowMode;
-        }
-
-        public static ResourceTransferMode GetResourceTransferMode(int type)
-        {
-            return PartResourceLibrary.Instance.GetDefinition(type).resourceTransferMode;
-        }
-
-        public static float GetResourceDensity(int type)
-        {
-            return PartResourceLibrary.Instance.GetDefinition(type).density;
-        }
-
-        public static string GetResourceName(int type)
-        {
-            return PartResourceLibrary.Instance.GetDefinition(type).name;
-        }
-    }
-}
-

--- a/KerbalEngineer/Simulation/SimManager.cs
+++ /dev/null
@@ -1,258 +1,1 @@
-#region

-

-using System;

-using System.Collections.Generic;

-using System.ComponentModel.Design.Serialization;

-using System.Diagnostics;

-using System.Linq;

-using System.Reflection;

-using System.Threading;

-

-using KerbalEngineer.Flight;

-

-using UnityEngine;

-

-#endregion

-

-namespace KerbalEngineer.Simulation

-{

-    public class SimManager : IUpdatable, IUpdateRequest

-    {

-        #region Instance

-

-        private static readonly SimManager instance = new SimManager();

-

-        public static SimManager Instance

-        {

-            get { return instance; }

-        }

-

-        #endregion

-

-        public const double RESOURCE_MIN = 0.0001;

-

-        private static bool bRequested;

-        private static bool bRunning;

-        private static readonly Stopwatch timer = new Stopwatch();

-        private static long delayBetweenSims;

-

-        public static long minSimTime = 150;

-

-        // Support for RealFuels using reflection to check localCorrectThrust without dependency

-        private static bool hasCheckedForRealFuels;

-        private static bool hasInstalledRealFuels;

-

-        private static Type RF_ModuleEngineConfigs_Type;

-        private static Type RF_ModuleHybridEngine_Type;

-

-        private static FieldInfo RF_ModuleEngineConfigs_locaCorrectThrust;

-        private static FieldInfo RF_ModuleHybridEngine_locaCorrectThrust;

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

-        public static Stage LastStage { get; private set; }

-        public static String failMessage { get; private set; }

-        public static double Gravity { get; set; }

-        public static double Atmosphere { get; set; }

-

-        #region IUpdatable Members

-

-        public void Update()

-        {

-            TryStartSimulation();

-        }

-

-        #endregion

-

-        #region IUpdateRequest Members

-

-        public bool UpdateRequested { get; set; }

-

-        #endregion

-

-        public static void RequestUpdate()

-        {

-            instance.UpdateRequested = true;

-            RequestSimulation();

-        }

-

-        private static void GetRealFuelsTypes()

-        {

-            hasCheckedForRealFuels = true;

-

-            foreach (AssemblyLoader.LoadedAssembly assembly in AssemblyLoader.loadedAssemblies)

-            {

-                MonoBehaviour.print("Assembly:" + assembly.assembly);

-

-                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
+++ /dev/null
@@ -1,547 +1,1 @@
-// 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 UnityEngine;

-

-namespace KerbalEngineer.Simulation
-{
-    public class Simulation
-    {
-        private List<Part> partList;
 
-        private List<PartSim> allParts;
-        private List<PartSim> allFuelLines;
-        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 || TIMERS
-        private Stopwatch _timer = new Stopwatch();
-#endif
-        private const double STD_GRAVITY = 9.81d;
-        private const double SECONDS_PER_DAY = 86400;
-        
-        public Simulation()
-        {
-#if LOG
-            MonoBehaviour.print("Simulation created");
-#endif
-        }
-
-        // This function prepares the simulation by creating all the necessary data structures it will 
-        // need during the simulation.  All required data is copied from the core game data structures 
-        // so that the simulation itself can be run in a background thread without having issues with 
-        // the core game changing the data while the simulation is running.
-        public bool PrepareSimulation(List<Part> parts, double theGravity, double theAtmosphere = 0)
-        {
-#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
-            this.partList = parts;
-            this.gravity = theGravity;
-            this.atmosphere = theAtmosphere;
-            this.lastStage = Staging.lastStage;
-            //MonoBehaviour.print("lastStage = " + lastStage);
-
-            // Create the lists for our simulation parts
-            this.allParts = new List<PartSim>();
-            this.allFuelLines = new List<PartSim>();
-            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>();
-
-            // First we create a PartSim for each Part (giving each a unique id)
-            int partId = 1;
-            foreach (Part part in this.partList)
-            {
-                // If the part is already in the lookup dictionary then log it and skip to the next part
-                if (partSimLookup.ContainsKey(part))
-                {
-                    MonoBehaviour.print("Part " + part.name + " appears in vessel list more than once");
-                    continue;
-                }
-
-                // Create the PartSim
-                PartSim partSim = new PartSim(part, partId, this.atmosphere);
-
-                // Add it to the Part lookup dictionary and the necessary lists
-                partSimLookup.Add(part, partSim);
-                this.allParts.Add(partSim);
-                if (partSim.isFuelLine)
-                    this.allFuelLines.Add(partSim);
-                if (partSim.isEngine)
-                    partSim.CreateEngineSims(this.allEngines, this.atmosphere);
-
-                partId++;
-            }
-
-            // Now that all the PartSims have been created we can do any set up that needs access to other parts
-            //MonoBehaviour.print("SetupAttachNodes and count stages");
-            foreach (PartSim partSim in this.allParts)
-            {
-                partSim.SetupAttachNodes(partSimLookup);
-                if (partSim.decoupledInStage >= this.lastStage)
-                    this.lastStage = partSim.decoupledInStage + 1;
-            }
-
-            // And finally release the Part references from all the PartSims
-            //MonoBehaviour.print("ReleaseParts");
-            foreach (PartSim partSim in this.allParts)
-                partSim.ReleasePart();
-
-            // And dereference the core's part list
-            this.partList = null;
-
-#if LOG || TIMERS
-            _timer.Stop();
-            MonoBehaviour.print("PrepareSimulation: " + _timer.ElapsedMilliseconds + "ms");
-#endif
-#if LOG
-            Dump();
-#endif
-            return true;
-        }
-
-        
-        // This function runs the simulation and returns a newly created array of Stage objects
-        public Stage[] RunSimulation()
-        {
-#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)
-            this.currentStage = this.lastStage;
-
-            // Create the array of stages that will be returned
-            Stage[] stages = new Stage[this.currentStage + 1];
-
-            // Loop through the stages
-            while (this.currentStage >= 0)
-            {
-#if LOG
-                MonoBehaviour.print("Simulating stage " + currentStage);
-                MonoBehaviour.print("ShipMass = " + ShipMass);
-                _timer.Reset();
-                _timer.Start();
-#endif
-                // Update active engines and resource drains
-                this.UpdateResourceDrains();
-
-                // Create the Stage object for this stage
-                Stage stage = new Stage();
-
-                double stageTime = 0d;
-                double stageDeltaV = 0d;            
-                double totalStageThrust = 0d;
-                double totalStageActualThrust = 0d;
-
-                double totalStageFlowRate = 0d;
-                double totalStageIspFlowRate = 0d;
-                double currentisp = 0;
-                double stageStartMass = this.ShipMass;
-                double stepStartMass = stageStartMass;
-                double stepEndMass = 0;
-
-                // Loop through all the active engines totalling the thrust, actual thrust and mass flow rates
-                foreach (EngineSim engine in this.activeEngines)
-                {
-                    totalStageActualThrust += engine.actualThrust;
-                    totalStageThrust += engine.thrust;
-
-                    totalStageFlowRate += engine.ResourceConsumptions.Mass;
-                    totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp;
-                }
-
-                // Calculate the effective isp at this point
-                if (totalStageFlowRate > 0d && totalStageIspFlowRate > 0d)
-                    currentisp = totalStageIspFlowRate / totalStageFlowRate;
-                else
-                    currentisp = 0;
-
-                // Store various things in the Stage object
-                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();
-                    }
-                }
-#if LOG
-                MonoBehaviour.print("Stage setup took " + _timer.ElapsedMilliseconds + "ms");
-#endif
-                // Now we will loop until we are allowed to stage
-                int loopCounter = 0;
-                while (!this.AllowedToStage())
-                {
-                    loopCounter++;
-                    //MonoBehaviour.print("loop = " + loopCounter);
-
-                    // 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;
-                            partMinDrain = partSim;
-                        }
-                    }
-#if LOG
-                    MonoBehaviour.print("Drain time = " + resourceDrainTime + " (" + partMinDrain.name + ":" + partMinDrain.partId + ")");
-#endif
-                    foreach (PartSim partSim in this.drainingParts)
-                        partSim.DrainResources(resourceDrainTime);
-
-                    // Get the mass after draining
-                    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);
-
-                    // Update the active engines and resource drains for the next step
-                    this.UpdateResourceDrains();
-
-                    // 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;
-                    else
-                        currentisp = 0;
-
-                    // Check if we actually changed anything
-                    if (stepStartMass == stepEndMass)
-                    {
-                        MonoBehaviour.print("No change in mass");
-                        break;
-                    }
-
-                    // Check to stop rampant looping
-                    if (loopCounter == 1000)
-                    {
-                        MonoBehaviour.print("exceeded loop count");
-                        MonoBehaviour.print("stageStartMass = " + stageStartMass);
-                        MonoBehaviour.print("stepStartMass = " + stepStartMass);
-                        MonoBehaviour.print("StepEndMass   = " + stepEndMass);
-                        break;
-                    }
-
-                    // The next step starts at the mass this one ended at
-                    stepStartMass = stepEndMass;
-                }
-
-                // 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)
-                // 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;
-                stages[this.currentStage] = stage;
-
-                // Now activate the next stage
-                this.currentStage--;
-#if LOG
-                // Log how long the stage took
-                _timer.Stop();
-                MonoBehaviour.print("Simulating stage took " + _timer.ElapsedMilliseconds + "ms");
-                stage.Dump();
-                _timer.Reset();
-                _timer.Start();
-#endif
-                // Activate the next stage
-                this.ActivateStage();
-#if LOG
-                // Log home long it took to activate
-                _timer.Stop();
-                MonoBehaviour.print("ActivateStage took " + _timer.ElapsedMilliseconds + "ms");
-#endif
-            }
-
-            // Now we add up the various total fields in the stages
-            for (int i = 0; i < stages.Length; i++)
-            {
-                // 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;
-                }
-                // 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;
-                }
-
-                // 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 LOG || TIMERS
-            _timer.Stop();
-            MonoBehaviour.print("RunSimulation: " + _timer.ElapsedMilliseconds + "ms");
-#endif
-            return stages;
-        }
-
-
-        // This function does all the hard work of working out which engines are burning, which tanks are being drained 
-        // and setting the drain rates
-        private void UpdateResourceDrains()
-        {
-            // 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)
-                partSim.ResourceDrains.Reset();
-
-            // Empty the draining parts set
-            this.drainingParts.Clear();
-
-            // Loop through all the engine modules in the ship
-            foreach (EngineSim engine in this.allEngines)
-            {
-                // If the engine is active in the current stage
-                if (engine.partSim.inverseStage >= this.currentStage)
-                {
-                    // 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
-            StringBuilder buffer = new StringBuilder(1024);
-            buffer.AppendFormat("Active engines = {0:d}\n", activeEngines.Count);
-            int i = 0;
-            foreach (EngineSim engine in activeEngines)
-                engine.DumpEngineToBuffer(buffer, "Engine " + (i++) + ":");
-            MonoBehaviour.print(buffer);
-#endif
-        }
-
-        // This function works out if it is time to stage
-        private bool AllowedToStage()
-        {
-#if LOG
-            StringBuilder buffer = new StringBuilder(1024);
-            buffer.AppendLine("AllowedToStage");
-            buffer.AppendFormat("currentStage = {0:d}\n", currentStage);
-#endif
-            if (this.activeEngines.Count == 0)
-            {
-#if LOG
-                buffer.AppendLine("No active engines => true");
-                MonoBehaviour.print(buffer);
-#endif
-                return true;
-            }
-
-            foreach (PartSim partSim in this.allParts)
-            {
-                //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.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)
-                        {
-#if LOG
-                            partSim.DumpPartToBuffer(buffer, "Decoupled part is active engine => false: ");
-                            MonoBehaviour.print(buffer);
-#endif
-                            return false;
-                        }
-                    }
-                }
-            }
-
-            if (this.currentStage > 0)
-            {
-#if LOG
-                buffer.AppendLine("Current stage > 0 => true");
-                MonoBehaviour.print(buffer);
-#endif
-                return true;
-            }
-
-#if LOG
-            buffer.AppendLine("Returning false");
-            MonoBehaviour.print(buffer);
-#endif
-            return false;
-        }
-
-        // This function activates the next stage
-        // currentStage must be updated before calling this function
-        private void ActivateStage()
-        {
-            // Build a set of all the parts that will be decoupled
-            HashSet<PartSim> decoupledParts = new HashSet<PartSim>();
-            foreach (PartSim partSim in this.allParts)
-            {
-                if (partSim.decoupledInStage >= this.currentStage)
-                    decoupledParts.Add(partSim);
-            }
-
-            foreach (PartSim partSim in decoupledParts)
-            {
-                // Remove it from the all parts list
-                this.allParts.Remove(partSim);
-                if (partSim.isEngine)
-                {
-                    // If it is an engine then loop through all the engine modules and remove all the ones from this engine part
-                    for (int i = this.allEngines.Count - 1; i >= 0; i--)
-                    {
-                        if (this.allEngines[i].partSim == partSim)
-                            this.allEngines.RemoveAt(i);
-                    }
-                }
-                // If it is a fuel line then remove it from the list of all fuel lines
-                if (partSim.isFuelLine)
-                    this.allFuelLines.Remove(partSim);
-            }
-
-            // Loop through all the (remaining) parts
-            foreach (PartSim partSim in this.allParts)
-            {
-                // Ask the part to remove all the parts that are decoupled
-                partSim.RemoveAttachedParts(decoupledParts);
-            }
-        }
-
-        private double ShipStartMass
-        {
-            get
-            {
-                double mass = 0d;
-
-                foreach (PartSim partSim in this.allParts)
-                {
-                    mass += partSim.GetStartMass();
-                }
-
-                return mass;
-            }
-        }
-
-        private double ShipMass
-        {
-            get
-            {
-                double mass = 0d;
-
-                foreach (PartSim partSim in this.allParts)
-                {
-                    mass += partSim.GetMass();
-                }
-
-                return mass;
-            }
-        }
-#if LOG
-        public void Dump()
-        {
-            StringBuilder buffer = new StringBuilder(1024);
-            buffer.AppendFormat("Part count = {0:d}\n", allParts.Count);
-
-            // Output a nice tree view of the rocket
-            if (allParts.Count > 0)
-            {
-                PartSim root = allParts[0];
-                while (root.parent != null)
-                    root = root.parent;
-
-                root.DumpPartToBuffer(buffer, "", allParts);
-            }
-
-            MonoBehaviour.print(buffer);
-        }
-#endif
-    }
-}
-

--- a/KerbalEngineer/Simulation/Stage.cs
+++ /dev/null
@@ -1,51 +1,1 @@
-// 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 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()
-        {
-            StringBuilder str = new StringBuilder("", 512);
-            str.AppendFormat("number        : {0:d}\n", number);
-            str.AppendFormat("cost          : {0:d}\n", cost);
-            str.AppendFormat("totalCost     : {0:d}\n", totalCost);
-            str.AppendFormat("time          : {0:g6}\n", time);
-            str.AppendFormat("totalTime     : {0:g6}\n", totalTime);
-            str.AppendFormat("mass          : {0:g6}\n", mass);
-            str.AppendFormat("totalMass     : {0:g6}\n", totalMass);
-            str.AppendFormat("isp           : {0:g6}\n", isp);
-            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);
-            str.AppendFormat("invTotDeltaV  : {0:g6}\n", inverseTotalDeltaV);
-            
-            MonoBehaviour.print(str);
-        }
-#endif
-    }
-}
 

--- /dev/null
+++ b/KerbalEngineer/VesselSimulator/AttachNodeSim.cs
@@ -1,1 +1,38 @@
+using System;

+using System.Text;

+

+namespace KerbalEngineer.VesselSimulator
+{
+    class AttachNodeSim
+    {
+        public PartSim attachedPartSim;
+        public AttachNode.NodeType nodeType;
+        public String id;
 
+        public AttachNodeSim(PartSim partSim, String newId, AttachNode.NodeType newNodeType)
+        {
+            this.attachedPartSim = partSim;
+            this.nodeType = newNodeType;
+            this.id = newId;
+        }
+
+        public void DumpToBuffer(StringBuilder buffer)
+        {
+            if (this.attachedPartSim == null)
+            {
+                buffer.Append("<staged>:<n>");
+            }
+            else
+            {
+                buffer.Append(this.attachedPartSim.name);
+                buffer.Append(":");
+                buffer.Append(this.attachedPartSim.partId);
+            }
+            buffer.Append("#");
+            buffer.Append(this.nodeType);
+            buffer.Append(":");
+            buffer.Append(this.id);
+        }
+    }
+}
+

--- /dev/null
+++ b/KerbalEngineer/VesselSimulator/EngineSim.cs
@@ -1,1 +1,363 @@
-
+// Kerbal Engineer Redux

+// Author:  CYBUTEK

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

+

+#region

+

+using System;

+using System.Collections.Generic;

+using System.Linq;

+using System.Text;

+

+using UnityEngine;

+

+#endregion

+

+namespace KerbalEngineer.VesselSimulator

+{

+    public class EngineSim

+    {

+        private readonly ResourceContainer resourceConsumptions = new ResourceContainer();

+

+        public double actualThrust = 0;

+        public bool isActive = false;

+        public double isp = 0;

+        public PartSim partSim;

+

+        public double thrust = 0;

+

+        // Add thrust vector to account for directional losses

+        public Vector3 thrustVec;

+

+        public EngineSim(PartSim theEngine,

+                         double atmosphere,

+                         double velocity,

+                         float maxThrust,

+                         float thrustPercentage,

+                         float requestedThrust,

+                         Vector3 vecThrust,

+                         float realIsp,

+                         FloatCurve atmosphereCurve,

+                         FloatCurve velocityCurve,

+                         bool throttleLocked,

+                         List<Propellant> propellants,

+                         bool active,

+                         bool correctThrust)

+        {

+            StringBuilder buffer = null;

+            //MonoBehaviour.print("Create EngineSim for " + theEngine.name);

+            //MonoBehaviour.print("maxThrust = " + maxThrust);

+            //MonoBehaviour.print("thrustPercentage = " + thrustPercentage);

+            //MonoBehaviour.print("requestedThrust = " + requestedThrust);

+            //MonoBehaviour.print("velocity = " + velocity);

+

+            this.partSim = theEngine;

+

+            this.isActive = active;

+            this.thrust = maxThrust * (thrustPercentage / 100f);

+            //MonoBehaviour.print("thrust = " + thrust);

+

+            this.thrustVec = vecThrust;

+

+            double flowRate = 0d;

+            if (this.partSim.hasVessel)

+            {

+                //MonoBehaviour.print("hasVessel is true");

+                this.actualThrust = requestedThrust;

+                if (velocityCurve != null)

+                {

+                    this.actualThrust *= velocityCurve.Evaluate((float)velocity);

+                    //MonoBehaviour.print("actualThrust at velocity = " + actualThrust);

+                }

+

+                this.isp = atmosphereCurve.Evaluate((float)this.partSim.part.staticPressureAtm);

+                if (this.isp == 0d)

+                {

+                    MonoBehaviour.print("Isp at " + this.partSim.part.staticPressureAtm + " is zero. Flow rate will be NaN");

+                }

+

+                if (correctThrust && realIsp == 0)

+                {

+                    float ispsl = atmosphereCurve.Evaluate(0);

+                    if (ispsl != 0)

+                    {

+                        this.thrust = this.thrust * this.isp / ispsl;

+                    }

+                    else

+                    {

+                        MonoBehaviour.print("Isp at sea level is zero. Unable to correct thrust.");

+                    }

+                    //MonoBehaviour.print("corrected thrust = " + thrust);

+                }

+

+                if (velocityCurve != null)

+                {

+                    this.thrust *= velocityCurve.Evaluate((float)velocity);

+                    //MonoBehaviour.print("thrust at velocity = " + thrust);

+                }

+

+                if (throttleLocked)

+                {

+                    //MonoBehaviour.print("throttleLocked is true");

+                    flowRate = this.thrust / (this.isp * 9.81d);

+                }

+                else

+                {

+                    if (this.partSim.isLanded)

+                    {

+                        //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)

+                        {

+                            if (velocityCurve != null)

+                            {

+                                requestedThrust *= velocityCurve.Evaluate((float)velocity);

+                                //MonoBehaviour.print("requestedThrust at velocity = " + requestedThrust);

+                            }

+

+                            //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 (this.isp == 0d)

+                {

+                    MonoBehaviour.print("Isp at " + atmosphere + " is zero. Flow rate will be NaN");

+                }

+                if (correctThrust)

+                {

+                    float ispsl = atmosphereCurve.Evaluate(0);

+                    if (ispsl != 0)

+                    {

+                        this.thrust = this.thrust * this.isp / ispsl;

+                    }

+                    else

+                    {

+                        MonoBehaviour.print("Isp at sea level is zero. Unable to correct thrust.");

+                    }

+                    //MonoBehaviour.print("corrected thrust = " + thrust);

+                }

+

+                if (velocityCurve != null)

+                {

+                    this.thrust *= velocityCurve.Evaluate((float)velocity);

+                    //MonoBehaviour.print("thrust at velocity = " + thrust);

+                }

+

+                flowRate = this.thrust / (this.isp * 9.81d);

+            }

+

+            if (SimManager.logOutput)

+            {

+                buffer = new StringBuilder(1024);

+                buffer.AppendFormat("flowRate = {0:g6}\n", flowRate);

+            }

+

+            float flowMass = 0f;

+            foreach (Propellant propellant in propellants)

+            {

+                flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);

+            }

+

+            if (SimManager.logOutput)

+            {

+                buffer.AppendFormat("flowMass = {0:g6}\n", flowMass);

+            }

+

+            foreach (Propellant propellant in propellants)

+            {

+                if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir")

+                {

+                    continue;

+                }

+

+                double consumptionRate = propellant.ratio * flowRate / flowMass;

+                if (SimManager.logOutput)

+                {

+                    buffer.AppendFormat("Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate);

+                }

+                this.resourceConsumptions.Add(propellant.id, consumptionRate);

+            }

+

+            if (SimManager.logOutput)

+            {

+                MonoBehaviour.print(buffer);

+            }

+        }

+

+        public ResourceContainer ResourceConsumptions

+        {

+            get { return this.resourceConsumptions; }

+        }

+

+        public bool SetResourceDrains(List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> drainingParts)

+        {

+            LogMsg log = null;

+

+            // A dictionary to hold a set of parts for each resource

+            Dictionary<int, HashSet<PartSim>> sourcePartSets = new Dictionary<int, HashSet<PartSim>>();

+

+            foreach (int type in this.resourceConsumptions.Types)

+            {

+                HashSet<PartSim> sourcePartSet = null;

+                switch (ResourceContainer.GetResourceFlowMode(type))

+                {

+                    case ResourceFlowMode.NO_FLOW:

+                        if (this.partSim.resources[type] > SimManager.RESOURCE_MIN)

+                        {

+                            sourcePartSet = new HashSet<PartSim>();

+                            //MonoBehaviour.print("SetResourceDrains(" + name + ":" + partId + ") setting sources to just this");

+                            sourcePartSet.Add(this.partSim);

+                        }

+                        break;

+

+                    case ResourceFlowMode.ALL_VESSEL:

+                        foreach (PartSim aPartSim in allParts)

+                        {

+                            if (aPartSim.resources[type] > SimManager.RESOURCE_MIN)

+                            {

+                                if (sourcePartSet == null)

+                                {

+                                    sourcePartSet = new HashSet<PartSim>();

+                                }

+

+                                sourcePartSet.Add(aPartSim);

+                            }

+                        }

+                        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>();

+

+                        if (SimManager.logOutput)

+                        {

+                            log = new LogMsg();

+                            log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + this.partSim.name + ":" + this.partSim.partId);

+                        }

+                        sourcePartSet = this.partSim.GetSourceSet(type, allParts, visited, log, "");

+                        if (SimManager.logOutput)

+                        {

+                            MonoBehaviour.print(log.buf);

+                        }

+                        break;

+

+                    default:

+                        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 (SimManager.logOutput)

+                    {

+                        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);

+                    }

+                }

+            }

+

+            // 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 (SimManager.logOutput)

+                    {

+                        MonoBehaviour.print("No source of " + ResourceContainer.GetResourceName(type));

+                    }

+

+                    this.isActive = false;

+                    return false;

+                }

+            }

+

+            // Now we set the drains on the members of the sets and update the draining parts set

+            foreach (int type in this.resourceConsumptions.Types)

+            {

+                HashSet<PartSim> sourcePartSet = sourcePartSets[type];

+                // Loop through the members of the set 

+                double amount = this.resourceConsumptions[type] / sourcePartSet.Count;

+                foreach (PartSim partSim in sourcePartSet)

+                {

+                    if (SimManager.logOutput)

+                    {

+                        MonoBehaviour.print("Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" + partSim.partId);

+                    }

+

+                    partSim.resourceDrains.Add(type, amount);

+                    drainingParts.Add(partSim);

+                }

+            }

+

+            return true;

+        }

+

+        public void DumpEngineToBuffer(StringBuilder buffer, String prefix)

+        {

+            buffer.Append(prefix);

+            buffer.AppendFormat("[thrust = {0:g6}, actual = {1:g6}, isp = {2:g6}\n", this.thrust, this.actualThrust, this.isp);

+        }

+    }

+}

--- /dev/null
+++ b/KerbalEngineer/VesselSimulator/PartSim.cs
@@ -1,1 +1,703 @@
-
+// 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.VesselSimulator
+{
+    public class PartSim
+    {
+        public ResourceContainer resources = new ResourceContainer();
+        public ResourceContainer resourceDrains = new ResourceContainer();
+        public ResourceContainer resourceFlowStates = new ResourceContainer();
+
+        List<AttachNodeSim> attachNodes = new List<AttachNodeSim>();
+        public List<PartSim> fuelTargets = new List<PartSim>();
+
+        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 bool hasVessel;
+        public String vesselName;
+        public VesselType vesselType;
+        public String initialVesselName;
+        public bool isLanded;
+        public bool isDecoupler;
+        public int decoupledInStage;
+        public int inverseStage;
+        public float cost;
+        public double baseMass = 0d;
+        public 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, LogMsg log)
+        {
+            this.part = thePart;
+            this.partId = id;
+            this.name = this.part.partInfo.name;
+
+            if (log != null)
+                log.buf.AppendLine("Create PartSim for " + this.name);
+
+            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;
+            foreach (PartResource resource in this.part.Resources)
+            {
+                this.cost -= (float)((resource.maxAmount - resource.amount) * resource.info.unitCost);
+            }
+
+            // 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 (SimManager.logOutput)
+                MonoBehaviour.print((this.isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + 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))
+                {
+                    if (SimManager.logOutput)
+                        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;
+            if (this.hasVessel)
+            {
+                this.vesselName = this.part.vessel.vesselName;
+                this.vesselType = this.part.vesselType;
+            }
+            this.initialVesselName = this.part.initialVesselName;
+
+            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 (SimManager.logOutput)
+                MonoBehaviour.print("Created " + this.name + ". Decoupled in stage " + this.decoupledInStage);
+        }
+
+        public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double velocity, bool vectoredThrust, LogMsg log)
+        {
+            bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(this.part);
+            if (log != null)
+            {
+                log.buf.AppendLine("CreateEngineSims for " + this.name);
+
+                foreach (PartModule partMod in this.part.Modules)
+                {
+                    log.buf.AppendLine("Module: " + partMod.moduleName);
+                }
+
+                log.buf.AppendLine("correctThrust = " + correctThrust);
+            }
+
+            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)
+                    {
+                        if (log != null)
+                            log.buf.AppendLine("Module: " + engine.moduleName);
+
+                        Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
+
+                        EngineSim engineSim = new EngineSim(this,
+                                                            atmosphere,
+                                                            velocity,
+                                                            engine.maxThrust,
+                                                            engine.thrustPercentage,
+                                                            engine.requestedThrust,
+                                                            thrustvec,
+                                                            engine.realIsp,
+                                                            engine.atmosphereCurve,
+                                                            engine.useVelocityCurve ? engine.velocityCurve : null,
+                                                            engine.throttleLocked,
+                                                            engine.propellants,
+                                                            engine.isOperational,
+                                                            correctThrust);
+                        allEngines.Add(engineSim);
+                    }
+                }
+            }
+            else
+            {
+                if (this.hasModuleEnginesFX)
+                {
+                    foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())
+                    {
+                        if (log != null)
+                            log.buf.AppendLine("Module: " + engine.moduleName);
+
+                        Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
+                        
+                        EngineSim engineSim = new EngineSim(this,
+                                                            atmosphere,
+                                                            velocity,
+                                                            engine.maxThrust,
+                                                            engine.thrustPercentage,
+                                                            engine.requestedThrust,
+                                                            thrustvec,
+                                                            engine.realIsp,
+                                                            engine.atmosphereCurve,
+                                                            engine.useVelocityCurve ? engine.velocityCurve : null,
+                                                            engine.throttleLocked,
+                                                            engine.propellants,
+                                                            engine.isOperational,
+                                                            correctThrust);
+                        allEngines.Add(engineSim);
+                    }
+                }
+
+                if (this.hasModuleEngines)
+                {
+                    foreach (ModuleEngines engine in this.part.GetModules<ModuleEngines>())
+                    {
+                        if (log != null)
+                            log.buf.AppendLine("Module: " + engine.moduleName);
+
+                        Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
+
+                        EngineSim engineSim = new EngineSim(this,
+                                                            atmosphere,
+                                                            velocity,
+                                                            engine.maxThrust,
+                                                            engine.thrustPercentage,
+                                                            engine.requestedThrust,
+                                                            thrustvec,
+                                                            engine.realIsp,
+                                                            engine.atmosphereCurve,
+                                                            engine.useVelocityCurve ? engine.velocityCurve : null,
+                                                            engine.throttleLocked,
+                                                            engine.propellants,
+                                                            engine.isOperational,
+                                                            correctThrust);
+                        allEngines.Add(engineSim);
+                    }
+                }
+            }
+
+            if (log != null)
+                log.Flush();
+        }
+
+        private Vector3 CalculateThrustVector(List<Transform> thrustTransforms, LogMsg log)
+        {
+            if (thrustTransforms == null)
+                return Vector3.forward;
+            
+            Vector3 thrustvec = Vector3.zero;
+            foreach (Transform trans in thrustTransforms)
+            {
+                if (log != null)
+                    log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6})   length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude);
+
+                thrustvec -= trans.forward;
+            }
+
+            if (log != null)
+                log.buf.AppendFormat("ThrustVec  = ({0:g6}, {1:g6}, {2:g6})   length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
+
+            thrustvec.Normalize();
+
+            if (log != null)
+                log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6})   length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
+
+            return thrustvec;
+        }
+
+        public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
+        {
+            if (this.part.parent != null)
+            {
+                this.parent = null;
+                if (partSimLookup.TryGetValue(this.part.parent, out this.parent))
+                {
+                    if (log != null)
+                        log.buf.AppendLine("Parent part is " + this.parent.name + ":" + this.parent.partId);
+                }
+                else
+                {
+                    if (log != null)
+                        log.buf.AppendLine("No PartSim for parent part (" + this.part.parent.partInfo.name + ")");
+                }
+            }
+        }
+
+        public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
+        {
+            if (log != null)
+                log.buf.AppendLine("SetupAttachNodes for " + this.name + ":" + this.partId + "");
+
+            this.attachNodes.Clear();
+            foreach (AttachNode attachNode in this.part.attachNodes)
+            {
+                if (log != null)
+                    log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null"));
+
+                if (attachNode.attachedPart != null && attachNode.id != "Strut")
+                {
+                    PartSim attachedSim;
+                    if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim))
+                    {
+                        if (log != null)
+                            log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + "");
+
+                        this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType));
+                    }
+                    else
+                    {
+                        if (log != null)
+                            log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");
+                    }
+                }
+            }
+
+            foreach (Part p in this.part.fuelLookupTargets)
+            {
+                PartSim targetSim;
+                if (partSimLookup.TryGetValue(p, out targetSim))
+                {
+                    if (log != null)
+                        log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId);
+
+                    this.fuelTargets.Add(targetSim);
+                }
+                else
+                {
+                    if (log != null)
+                        log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")");
+                }
+            }
+        }
+
+        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, HashSet<PartSim> visited, LogMsg log, String indent)
+        {
+            if (log != null)
+            {
+                log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + this.name + ":" + this.partId);
+                indent += "  ";
+            }
+
+            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 != null)
+                    log.buf.AppendLine(indent + "Returning empty set, already visited (" + this.name + ":" + this.partId + ")");
+
+                return allSources;
+            }
+
+            //if (log != null)
+            //    log.buf.AppendLine(indent + "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 this.fuelTargets)
+            {
+                if (visited.Contains(partSim))
+                {
+                    //if (log != null)
+                    //    log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
+                }
+                else
+                {
+                    //if (log != null)
+                    //    log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");
+
+                    partSources = partSim.GetSourceSet(type, allParts, visited, log, indent);
+                    if (partSources.Count > 0)
+                    {
+                        allSources.UnionWith(partSources);
+                        partSources.Clear();
+                    }
+                }
+            }
+
+            if (allSources.Count > 0)
+            {
+                if (log != null)
+                    log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel target sources (" + this.name + ":" + this.partId + ")");
+
+                return allSources;
+            }
+
+            // Rule 3: This rule has been removed and merged with rules 4 and 7 to fix issue with fuel tanks with disabled crossfeed
+
+            // 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]
+            if (this.fuelCrossFeed)
+            {
+                //MonoBehaviour.print("foreach attach node");
+                foreach (AttachNodeSim attachSim in this.attachNodes)
+                {
+                    if (attachSim.attachedPartSim != null)
+                    {
+                        if (/*attachSim.nodeType != AttachNode.NodeType.Surface &&*/
+                            !(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey)))
+                        {
+                            if (visited.Contains(attachSim.attachedPartSim))
+                            {
+                                //if (log != null)
+                                //    log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
+                            }
+                            else
+                            {
+                                //if (log != null)
+                                //    log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
+
+                                partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, log, indent);
+                                if (partSources.Count > 0)
+                                {
+                                    allSources.UnionWith(partSources);
+                                    partSources.Clear();
+                                }
+                            }
+                        }
+                        else
+                        {
+                            //if (log != null)
+                            //    log.buf.AppendLine(indent + "AttachNode is noCrossFeedKey, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
+                        }
+                    }
+                }
+
+                if (allSources.Count > 0)
+                {
+                    if (log != null)
+                        log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + this.name + ":" + this.partId + ")");
+
+                    return allSources;
+                }
+            }
+            else
+            {
+                //if (log != null)
+                //    log.buf.AppendLine(indent + "Crossfeed disabled, skipping axial connected parts (" + name + ":" + partId + ")");
+            }
+
+            // 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 != null)
+                        log.buf.AppendLine(indent + "Returning enabled tank as only source (" + this.name + ":" + this.partId + ")");
+                }
+                else
+                {
+                    //if (log != null)
+                    //    log.buf.AppendLine(indent + "Returning empty set, enabled tank is empty (" + 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)
+            {
+                if (this.fuelCrossFeed)
+                {
+                    if (visited.Contains(this.parent))
+                    {
+                        //if (log != null)
+                        //    log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")");
+                    }
+                    else
+                    {
+                        allSources = this.parent.GetSourceSet(type, allParts, visited, log, indent);
+                        if (allSources.Count > 0)
+                        {
+                            if (log != null)
+                                log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + this.name + ":" + this.partId + ")");
+
+                            return allSources;
+                        }
+                    }
+                }
+                else
+                {
+                    //if (log != null)
+                    //    log.buf.AppendLine(indent + "Crossfeed disabled, skipping radial parent (" + name + ":" + partId + ")");
+                }
+            }
+
+            // Rule 8: If all preceding rules failed, part returns empty list.
+            //if (log != null)
+            //    log.buf.AppendLine(indent + "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)
+            {
+                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;
+            }
+        }
+
+        public ResourceContainer ResourceDrains
+        {
+            get
+            {
+                return this.resourceDrains;
+            }
+        }
+
+        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(", vesselName = '{0}'", this.vesselName);
+            buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(this.vesselType));
+            buffer.AppendFormat(", initialVesselName = '{0}'", this.initialVesselName);
+
+            buffer.AppendFormat(", fuelCF = {0}", this.fuelCrossFeed);
+            buffer.AppendFormat(", noCFNKey = '{0}'", this.noCrossFeedNodeKey);
+
+            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);
+                }
+            }
+        }
+    }
+}
+

--- /dev/null
+++ b/KerbalEngineer/VesselSimulator/ResourceContainer.cs
@@ -1,1 +1,136 @@
+// Kerbal Engineer Redux
+// Author:  CYBUTEK
+// License: Attribution-NonCommercial-ShareAlike 3.0 Unported

+

+using System.Collections;

+using System.Collections.Generic;

+

+namespace KerbalEngineer.VesselSimulator
+{
+    public class ResourceContainer
+    {
+        Hashtable resources = new Hashtable();
 
+        public double this[int type]
+        {
+            get
+            {
+                if (this.resources.ContainsKey(type))
+                    return (double)this.resources[type];
+
+                return 0d;
+            }
+            set
+            {
+                if (this.resources.ContainsKey(type))
+                    this.resources[type] = value;
+                else
+                    this.resources.Add(type, value);
+            }
+        }
+
+        public bool HasType(int type)
+        {
+            return this.resources.ContainsKey(type);
+        }
+
+        public List<int> Types
+        {
+            get
+            {
+                List<int> types = new List<int>();
+
+                foreach (int key in this.resources.Keys)
+                    types.Add(key);
+
+                return types;
+            }
+        }
+
+        public double Mass
+        {
+            get
+            {
+                double mass = 0d;
+
+                foreach (double resource in this.resources.Values)
+                    mass += resource;
+
+                return mass;
+            }
+        }
+
+        public bool Empty
+        {
+            get
+            {
+                foreach (int type in this.resources.Keys)
+                {
+                    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()
+        {
+            this.resources = new Hashtable();
+        }
+
+        public void Debug()
+        {
+            foreach (int key in this.resources.Keys)
+            {
+                UnityEngine.MonoBehaviour.print(" -> " + GetResourceName(key) + " = " + this.resources[key]);
+            }
+        }
+
+        public double GetResourceMass(int type)
+        {
+            double density = GetResourceDensity(type);
+            return density == 0d ? 0d : (double)this.resources[type] * density;
+        }
+
+        public static ResourceFlowMode GetResourceFlowMode(int type)
+        {
+            return PartResourceLibrary.Instance.GetDefinition(type).resourceFlowMode;
+        }
+
+        public static ResourceTransferMode GetResourceTransferMode(int type)
+        {
+            return PartResourceLibrary.Instance.GetDefinition(type).resourceTransferMode;
+        }
+
+        public static float GetResourceDensity(int type)
+        {
+            return PartResourceLibrary.Instance.GetDefinition(type).density;
+        }
+
+        public static string GetResourceName(int type)
+        {
+            return PartResourceLibrary.Instance.GetDefinition(type).name;
+        }
+    }
+}
+

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

+using System.Collections.Generic;

+using System.Diagnostics;

+using System.Linq;

+using System.Threading;

+

+using KerbalEngineer.Flight;

+

+using UnityEngine;

+

+namespace KerbalEngineer.VesselSimulator
+{
+    public class SimManager: IUpdatable, IUpdateRequest
+    {
+        public static SimManager Instance = new 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 bool dumpTree = false;
+        public static bool logOutput = false;
+        public static bool vectoredThrust = false;
+        public static long minSimTime = 150;
+        public static double Gravity { get; set; }
+        public static double Atmosphere { get; set; }
+        public static double Velocity { get; set; }
+
+        // Support for RealFuels using reflection to check localCorrectThrust without dependency
+        private static bool hasCheckedForRealFuels = false;
+        private static bool hasInstalledRealFuels = false;
+
+        private static System.Reflection.FieldInfo RF_ModuleEngineConfigs_locaCorrectThrust = null;
+        private static System.Reflection.FieldInfo RF_ModuleHybridEngine_locaCorrectThrust = null;
+        private static System.Reflection.FieldInfo RF_ModuleHybridEngines_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");
+
+                    Type RF_ModuleEngineConfigs_Type = assembly.assembly.GetType("RealFuels.ModuleEngineConfigs");
+                    if (RF_ModuleEngineConfigs_Type != null)
+                        RF_ModuleEngineConfigs_locaCorrectThrust = RF_ModuleEngineConfigs_Type.GetField("localCorrectThrust");
+
+                    Type RF_ModuleHybridEngine_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngine");
+                    if (RF_ModuleHybridEngine_Type != null)
+                        RF_ModuleHybridEngine_locaCorrectThrust = RF_ModuleHybridEngine_Type.GetField("localCorrectThrust");
+
+                    Type RF_ModuleHybridEngines_Type = assembly.assembly.GetType("RealFuels.ModuleHybridEngines");
+                    if (RF_ModuleHybridEngines_Type != null)
+                        RF_ModuleHybridEngines_locaCorrectThrust = RF_ModuleHybridEngines_Type.GetField("localCorrectThrust");
+
+					hasInstalledRealFuels = true;
+					break;
+				}
+
+			}
+
+		}
+
+        public static bool DoesEngineUseCorrectedThrust(Part theEngine)
+        {
+            if (!hasInstalledRealFuels /*|| HighLogic.LoadedSceneIsFlight*/)
+                return false;
+
+            // Look for any of the Real Fuels engine modules and call the relevant method to find out
+            if (RF_ModuleEngineConfigs_locaCorrectThrust != null && theEngine.Modules.Contains("ModuleEngineConfigs"))
+            {
+                PartModule modEngineConfigs = theEngine.Modules["ModuleEngineConfigs"];
+                if (modEngineConfigs != null)
+                {
+                    // Check the localCorrectThrust
+                    if ((bool)RF_ModuleEngineConfigs_locaCorrectThrust.GetValue(modEngineConfigs))
+                        return true;
+                }
+            }
+
+            if (RF_ModuleHybridEngine_locaCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngine"))
+            {
+                PartModule modHybridEngine = theEngine.Modules["ModuleHybridEngine"];
+                if (modHybridEngine != null)
+                {
+                    // Check the localCorrectThrust
+                    if ((bool)RF_ModuleHybridEngine_locaCorrectThrust.GetValue(modHybridEngine))
+                        return true;
+                }
+            }
+
+            if (RF_ModuleHybridEngines_locaCorrectThrust != null && theEngine.Modules.Contains("ModuleHybridEngines"))
+            {
+                PartModule modHybridEngines = theEngine.Modules["ModuleHybridEngines"];
+                if (modHybridEngines != null)
+                {
+                    // Check the localCorrectThrust
+                    if ((bool)RF_ModuleHybridEngines_locaCorrectThrust.GetValue(modHybridEngines))
+                        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, Velocity, dumpTree, vectoredThrust))
+                {
+                    ThreadPool.QueueUserWorkItem(new WaitCallback(RunSimulation), sim);
+                }
+                else
+                {
+                    failMessage = "PrepareSimulation failed";
+                    bRunning = false;
+                    logOutput = false;
+                }
+            }
+            catch (Exception e)
+            {
+                MonoBehaviour.print("Exception in StartSimulation: " + e);
+                failMessage = e.ToString();
+                bRunning = false;
+                logOutput = false;
+            }
+            dumpTree = false;
+        }
+
+        private static void RunSimulation(object simObject)
+        {
+            try
+            {
+                Stages = (simObject as Simulation).RunSimulation();
+                if (Stages != null)
+                {
+                    if (logOutput)
+                    {
+                        foreach (Stage stage in Stages)
+                            stage.Dump();
+                    }
+                    LastStage = Stages.Last();
+                }
+            }
+            catch (Exception e)
+            {
+                MonoBehaviour.print("Exception in RunSimulation: " + e);
+                Stages = null;
+                LastStage = null;
+                failMessage = e.ToString();
+            }
+
+            timer.Stop();
+#if TIMERS
+            MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms");
+#else
+            if (logOutput)
+                MonoBehaviour.print("Total simulation time: " + timer.ElapsedMilliseconds + "ms");
+#endif
+            delayBetweenSims = minSimTime - timer.ElapsedMilliseconds;
+            if (delayBetweenSims < 0)
+                delayBetweenSims = 0;
+
+            timer.Reset();
+            timer.Start();
+
+            bRunning = false;
+            logOutput = false;
+        }
+
+        public static String GetVesselTypeString(VesselType vesselType)
+        {
+            switch (vesselType)
+            {
+                case VesselType.Debris:
+                    return "Debris";
+                case VesselType.SpaceObject:
+                    return "SpaceObject";
+                case VesselType.Unknown:
+                    return "Unknown";
+                case VesselType.Probe:
+                    return "Probe";
+                case VesselType.Rover:
+                    return "Rover";
+                case VesselType.Lander:
+                    return "Lander";
+                case VesselType.Ship:
+                    return "Ship";
+                case VesselType.Station:
+                    return "Station";
+                case VesselType.Base:
+                    return "Base";
+                case VesselType.EVA:
+                    return "EVA";
+                case VesselType.Flag:
+                    return "Flag";
+            }
+            return "Undefined";
+        }

+

+        public void Update()

+        {

+            TryStartSimulation();

+        }

+

+        public bool UpdateRequested { get; set; }

+

+        public static void RequestUpdate()

+        {

+            Instance.UpdateRequested = true;

+        }

+    }
+}
+

--- /dev/null
+++ b/KerbalEngineer/VesselSimulator/Simulation.cs
@@ -1,1 +1,724 @@
-
+// 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.Diagnostics;

+using System.Text;

+

+using UnityEngine;

+

+namespace KerbalEngineer.VesselSimulator
+{
+    public class Simulation
+    {
+        private List<Part> partList;
+
+        private List<PartSim> allParts;
+        private List<PartSim> allFuelLines;
+        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 bool doingCurrent = false;
+
+        public String vesselName;
+        public VesselType vesselType;
+
+        private double stageTime = 0d;
+        private Vector3 vecStageDeltaV;
+        private double simpleTotalThrust = 0d;
+        private double totalStageThrust = 0d;
+        private double totalStageActualThrust = 0d;
+        private Vector3 vecThrust;
+        private Vector3 vecActualThrust;
+        private double totalStageFlowRate = 0d;
+        private double totalStageIspFlowRate = 0d;
+        private double currentisp = 0d;
+        private double stageStartMass = 0d;
+        private double stepStartMass = 0d;
+        private double stepEndMass = 0d;
+
+        private double gravity = 0d;
+        private double atmosphere = 0d;
+        private double velocity = 0d;
+        private Stopwatch _timer = new Stopwatch();
+        private const double STD_GRAVITY = 9.81d;
+        private const double SECONDS_PER_DAY = 86400d;
+        
+        public Simulation()
+        {
+            if (SimManager.logOutput)
+                MonoBehaviour.print("Simulation created");
+        }
+
+        // This function prepares the simulation by creating all the necessary data structures it will 
+        // need during the simulation.  All required data is copied from the core game data structures 
+        // so that the simulation itself can be run in a background thread without having issues with 
+        // the core game changing the data while the simulation is running.
+        public bool PrepareSimulation(List<Part> parts, double theGravity, double theAtmosphere = 0, double theVelocity = 0, bool dumpTree = false, bool vectoredThrust = false)
+        {
+            LogMsg log = null;
+            if (SimManager.logOutput)
+            {
+                log = new LogMsg();
+                log.buf.AppendLine("PrepareSimulation started");
+                dumpTree = true;
+            }
+            this._timer.Start();
+
+            // Store the parameters in members for ease of access in other functions
+            this.partList = parts;
+            this.gravity = theGravity;
+            this.atmosphere = theAtmosphere;
+            this.velocity = theVelocity;
+            this.lastStage = Staging.lastStage;
+            //MonoBehaviour.print("lastStage = " + lastStage);
+
+            // Create the lists for our simulation parts
+            this.allParts = new List<PartSim>();
+            this.allFuelLines = new List<PartSim>();
+            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>();
+
+            if (this.partList.Count > 0 && this.partList[0].vessel != null)
+            {
+                this.vesselName = this.partList[0].vessel.vesselName;
+                this.vesselType = this.partList[0].vessel.vesselType;
+            }
+
+            // First we create a PartSim for each Part (giving each a unique id)
+            int partId = 1;
+            foreach (Part part in this.partList)
+            {
+                // If the part is already in the lookup dictionary then log it and skip to the next part
+                if (partSimLookup.ContainsKey(part))
+                {
+                    if (log != null)
+                        log.buf.AppendLine("Part " + part.name + " appears in vessel list more than once");
+                    continue;
+                }
+
+                // Create the PartSim
+                PartSim partSim = new PartSim(part, partId, this.atmosphere, log);
+
+                // Add it to the Part lookup dictionary and the necessary lists
+                partSimLookup.Add(part, partSim);
+                this.allParts.Add(partSim);
+                if (partSim.isFuelLine)
+                    this.allFuelLines.Add(partSim);
+                if (partSim.isEngine)
+                    partSim.CreateEngineSims(this.allEngines, this.atmosphere, this.velocity, vectoredThrust, log);
+
+                partId++;
+            }
+
+            this.UpdateActiveEngines();
+
+            // Now that all the PartSims have been created we can do any set up that needs access to other parts
+            // First we set up all the parent links
+            foreach (PartSim partSim in this.allParts)
+            {
+                partSim.SetupParent(partSimLookup, log);
+            }
+            
+            // Then, in the VAB/SPH, we add the parent of each fuel line to the fuelTargets list of their targets
+            if (HighLogic.LoadedSceneIsEditor)
+            {
+                foreach (PartSim partSim in this.allFuelLines)
+                {
+                    if ((partSim.part as FuelLine).target != null)
+                    {
+                        PartSim targetSim;
+                        if (partSimLookup.TryGetValue((partSim.part as FuelLine).target, out targetSim))
+                        {
+                            if (log != null)
+                                log.buf.AppendLine("Fuel line target is " + targetSim.name + ":" + targetSim.partId);
+
+                            targetSim.fuelTargets.Add(partSim.parent);
+                        }
+                        else
+                        {
+                            if (log != null)
+                                log.buf.AppendLine("No PartSim for fuel line target (" + partSim.part.partInfo.name + ")");
+                        }
+                    }
+                    else
+                    {
+                        if (log != null)
+                            log.buf.AppendLine("Fuel line target is null");
+                    }
+                }
+            }
+
+            //MonoBehaviour.print("SetupAttachNodes and count stages");
+            foreach (PartSim partSim in this.allParts)
+            {
+                partSim.SetupAttachNodes(partSimLookup, log);
+                if (partSim.decoupledInStage >= this.lastStage)
+                    this.lastStage = partSim.decoupledInStage + 1;
+            }
+
+            // And finally release the Part references from all the PartSims
+            //MonoBehaviour.print("ReleaseParts");
+            foreach (PartSim partSim in this.allParts)
+                partSim.ReleasePart();
+
+            // And dereference the core's part list
+            this.partList = null;
+
+            this._timer.Stop();
+            if (log != null)
+            {
+                log.buf.AppendLine("PrepareSimulation: " + this._timer.ElapsedMilliseconds + "ms");
+                log.Flush();
+            }
+
+            if (dumpTree)
+                this.Dump();
+
+            return true;
+        }
+
+        
+        // This function runs the simulation and returns a newly created array of Stage objects
+        public Stage[] RunSimulation()
+        {
+            if (SimManager.logOutput)
+                MonoBehaviour.print("RunSimulation started");
+            this._timer.Start();
+            // Start with the last stage to simulate
+            // (this is in a member variable so it can be accessed by AllowedToStage and ActivateStage)
+            this.currentStage = this.lastStage;
+
+            LogMsg log = null;
+            if (SimManager.logOutput)
+                log = new LogMsg();
+
+            // Work out which engines would be active if just doing the staging and if this is different to the 
+            // currently active engines then generate an extra stage
+            // Loop through all the engines
+            foreach (EngineSim engine in this.allEngines)
+            {
+                if (log != null)
+                    log.buf.AppendLine("Testing engine mod of " + engine.partSim.name + ":" + engine.partSim.partId);
+                bool bActive = engine.isActive;
+                bool bStage = (engine.partSim.inverseStage >= this.currentStage);
+                if (log != null)
+                    log.buf.AppendLine("bActive = " + bActive + "   bStage = " + bStage);
+                if (HighLogic.LoadedSceneIsFlight)
+                {
+                    if (bActive != bStage)
+                    {
+                        // If the active state is different to the state due to staging
+                        if (log != null)
+                            log.buf.AppendLine("Need to do current active engines first");
+
+                        this.doingCurrent = true;
+                        this.currentStage++;
+                        break;
+                    }
+                }
+                else
+                {
+                    if (bStage)
+                    {
+                        if (log != null)
+                            log.buf.AppendLine("Marking as active");
+
+                        engine.isActive = true;
+                    }
+                }
+            }
+
+            if (log != null)
+                log.Flush();
+
+            // Create the array of stages that will be returned
+            Stage[] stages = new Stage[this.currentStage + 1];
+
+            // Loop through the stages
+            while (this.currentStage >= 0)
+            {
+                if (log != null)
+                {
+                    log.buf.AppendLine("Simulating stage " + this.currentStage);
+                    log.buf.AppendLine("ShipMass = " + this.ShipMass);
+                    log.Flush();
+                    this._timer.Reset();
+                    this._timer.Start();
+                }
+
+                // Update active engines and resource drains
+                this.UpdateResourceDrains();
+
+                // Create the Stage object for this stage
+                Stage stage = new Stage();
+
+                this.stageTime = 0d;
+                this.vecStageDeltaV = Vector3.zero;
+                this.stageStartMass = this.ShipMass;
+                this.stepStartMass = this.stageStartMass;
+                this.stepEndMass = 0;
+
+                this.CalculateThrustAndISP();
+
+                // Store various things in the Stage object
+                stage.thrust = this.totalStageThrust;
+                //MonoBehaviour.print("stage.thrust = " + stage.thrust);
+                stage.thrustToWeight = this.totalStageThrust / (this.stageStartMass * this.gravity);
+                stage.maxThrustToWeight = stage.thrustToWeight;
+                //MonoBehaviour.print("StageMass = " + stageStartMass);
+                //MonoBehaviour.print("Initial maxTWR = " + stage.maxThrustToWeight);
+                stage.actualThrust = this.totalStageActualThrust;
+                stage.actualThrustToWeight = this.totalStageActualThrust / (this.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();
+                    }
+                }
+
+                if (log != null)
+                    MonoBehaviour.print("Stage setup took " + this._timer.ElapsedMilliseconds + "ms");
+
+                // Now we will loop until we are allowed to stage
+                int loopCounter = 0;
+                while (!this.AllowedToStage())
+                {
+                    loopCounter++;
+                    //MonoBehaviour.print("loop = " + loopCounter);
+
+                    // 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;
+                            partMinDrain = partSim;
+                        }
+                    }
+
+                    if (log != null)
+                        MonoBehaviour.print("Drain time = " + resourceDrainTime + " (" + partMinDrain.name + ":" + partMinDrain.partId + ")");
+
+                    foreach (PartSim partSim in this.drainingParts)
+                        partSim.DrainResources(resourceDrainTime);
+
+                    // Get the mass after draining
+                    this.stepEndMass = this.ShipMass;
+                    this.stageTime += resourceDrainTime;
+
+                    double stepEndTWR = this.totalStageThrust / (this.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 && this.stepStartMass > this.stepEndMass && this.stepStartMass > 0d && this.stepEndMass > 0d)
+                        this.vecStageDeltaV += this.vecThrust * (float)((this.currentisp * STD_GRAVITY * Math.Log(this.stepStartMass / this.stepEndMass)) / this.simpleTotalThrust);
+
+                    // Update the active engines and resource drains for the next step
+                    this.UpdateResourceDrains();
+
+                    // Recalculate the current thrust and isp for the next step
+                    this.CalculateThrustAndISP();
+
+                    // Check if we actually changed anything
+                    if (this.stepStartMass == this.stepEndMass)
+                    {
+                        //MonoBehaviour.print("No change in mass");
+                        break;
+                    }
+
+                    // Check to stop rampant looping
+                    if (loopCounter == 1000)
+                    {
+                        MonoBehaviour.print("exceeded loop count");
+                        MonoBehaviour.print("stageStartMass = " + this.stageStartMass);
+                        MonoBehaviour.print("stepStartMass = " + this.stepStartMass);
+                        MonoBehaviour.print("StepEndMass   = " + this.stepEndMass);
+                        break;
+                    }
+
+                    // The next step starts at the mass this one ended at
+                    this.stepStartMass = this.stepEndMass;
+                }
+
+                // Store more values in the Stage object and stick it in the array
+
+                // Store the magnitude of the deltaV vector
+                stage.deltaV = this.vecStageDeltaV.magnitude;
+                
+                // Recalculate effective stage isp from the stage deltaV (flip the standard deltaV calculation around)
+                // Note: If the mass doesn't change then this is a divide by zero
+                if (this.stageStartMass != this.stepStartMass)
+                    stage.isp = stage.deltaV / (STD_GRAVITY * Math.Log(this.stageStartMass / this.stepStartMass));
+                else
+                    stage.isp = 0;
+
+                // Zero stage time if more than a day (this should be moved into the window code)
+                stage.time = (this.stageTime < SECONDS_PER_DAY) ? this.stageTime : 0d;
+                stage.number = this.doingCurrent ? -1 : this.currentStage;                // Set the stage number to -1 if doing current engines
+                stages[this.currentStage] = stage;
+
+                // Now activate the next stage
+                this.currentStage--;
+                this.doingCurrent = false;
+
+                if (log != null)
+                {
+                    // Log how long the stage took
+                    this._timer.Stop();
+                    MonoBehaviour.print("Simulating stage took " + this._timer.ElapsedMilliseconds + "ms");
+                    stage.Dump();
+                    this._timer.Reset();
+                    this._timer.Start();
+                }
+
+                // Activate the next stage
+                this.ActivateStage();
+
+                if (log != null)
+                {
+                    // Log how long it took to activate
+                    this._timer.Stop();
+                    MonoBehaviour.print("ActivateStage took " + this._timer.ElapsedMilliseconds + "ms");
+                }
+            }
+
+            // Now we add up the various total fields in the stages
+            for (int i = 0; i < stages.Length; i++)
+            {
+                // 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;
+                }
+                // 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;
+                }
+
+                // 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 (log != null)
+            {
+                this._timer.Stop();
+                MonoBehaviour.print("RunSimulation: " + this._timer.ElapsedMilliseconds + "ms");
+            }
+
+            return stages;
+        }
+
+        // This function simply rebuilds the active engines by testing the isActive flag of all the engines
+        private void UpdateActiveEngines()
+        {
+            this.activeEngines.Clear();
+            foreach (EngineSim engine in this.allEngines)
+            {
+                if (engine.isActive)
+                    this.activeEngines.Add(engine);
+            }
+        }
+
+        private void CalculateThrustAndISP()
+        {
+            // Reset all the values
+            this.vecThrust = Vector3.zero;
+            this.vecActualThrust = Vector3.zero;
+            this.simpleTotalThrust = 0d;
+            this.totalStageThrust = 0d;
+            this.totalStageActualThrust = 0d;
+            this.totalStageFlowRate = 0d;
+            this.totalStageIspFlowRate = 0d;
+
+            // Loop through all the active engines totalling the thrust, actual thrust and mass flow rates
+            // The thrust is totalled as vectors
+            foreach (EngineSim engine in this.activeEngines)
+            {
+                this.simpleTotalThrust += engine.thrust;
+                this.vecThrust += ((float)engine.thrust * engine.thrustVec);
+                this.vecActualThrust += ((float)engine.actualThrust * engine.thrustVec);
+
+                this.totalStageFlowRate += engine.ResourceConsumptions.Mass;
+                this.totalStageIspFlowRate += engine.ResourceConsumptions.Mass * engine.isp;
+            }
+
+            //MonoBehaviour.print("vecThrust = " + vecThrust.ToString() + "   magnitude = " + vecThrust.magnitude);
+
+            this.totalStageThrust = this.vecThrust.magnitude;
+            this.totalStageActualThrust = this.vecActualThrust.magnitude;
+
+            // Calculate the effective isp at this point
+            if (this.totalStageFlowRate > 0d && this.totalStageIspFlowRate > 0d)
+                this.currentisp = this.totalStageIspFlowRate / this.totalStageFlowRate;
+            else
+                this.currentisp = 0;
+        }
+
+        // This function does all the hard work of working out which engines are burning, which tanks are being drained 
+        // and setting the drain rates
+        private void UpdateResourceDrains()
+        {
+            // Update the active engines
+            this.UpdateActiveEngines();
+            
+            // Empty the draining resources set
+            this.drainingResources.Clear();
+
+            // Reset the resource drains of all draining parts
+            foreach (PartSim partSim in this.drainingParts)
+                partSim.ResourceDrains.Reset();
+
+            // Empty the draining parts set
+            this.drainingParts.Clear();
+
+            // Loop through all the active engine modules
+            foreach (EngineSim engine in this.activeEngines)
+            {
+                // Set the resource drains for this engine
+                if (engine.SetResourceDrains(this.allParts, this.allFuelLines, this.drainingParts))
+                {
+                    // If it is active then add the consumed resource types to the set
+                    foreach (int type in engine.ResourceConsumptions.Types)
+                        this.drainingResources.Add(type);
+                }
+            }
+
+            // Update the active engines again to remove any engines that have no fuel supply
+            this.UpdateActiveEngines();
+
+            if (SimManager.logOutput)
+            {
+                StringBuilder buffer = new StringBuilder(1024);
+                buffer.AppendFormat("Active engines = {0:d}\n", this.activeEngines.Count);
+                int i = 0;
+                foreach (EngineSim engine in this.activeEngines)
+                    engine.DumpEngineToBuffer(buffer, "Engine " + (i++) + ":");
+                MonoBehaviour.print(buffer);
+            }
+        }
+
+        // This function works out if it is time to stage
+        private bool AllowedToStage()
+        {
+            StringBuilder buffer = null;
+            if (SimManager.logOutput)
+            {
+                buffer = new StringBuilder(1024);
+                buffer.AppendLine("AllowedToStage");
+                buffer.AppendFormat("currentStage = {0:d}\n", this.currentStage);
+            }
+
+            if (this.activeEngines.Count == 0)
+            {
+                if (SimManager.logOutput)
+                {
+                    buffer.AppendLine("No active engines => true");
+                    MonoBehaviour.print(buffer);
+                }
+
+                return true;
+            }
+
+            bool partDecoupled = false;
+            bool engineDecoupled = false;
+
+            foreach (PartSim partSim in this.allParts)
+            {
+                //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))
+                {
+                    partDecoupled = true;
+                    
+                    if (!partSim.Resources.EmptyOf(this.drainingResources))
+                    {
+                        if (SimManager.logOutput)
+                        {
+                            partSim.DumpPartToBuffer(buffer, "Decoupled part not empty => false: ");
+                            MonoBehaviour.print(buffer);
+                        }
+
+                        return false;
+                    }
+
+                    if (partSim.isEngine)
+                    {
+                        foreach (EngineSim engine in this.activeEngines)
+                        {
+                            if (engine.partSim == partSim)
+                            {
+                                if (SimManager.logOutput)
+                                {
+                                    partSim.DumpPartToBuffer(buffer, "Decoupled part is active engine => false: ");
+                                    MonoBehaviour.print(buffer);
+                                }
+                                return false;
+                            }
+                        }
+
+                        engineDecoupled = true;
+                    }
+                }
+            }
+
+            if (!partDecoupled)
+            {
+                if (SimManager.logOutput)
+                {
+                    buffer.AppendLine("No engine decoupled but something is => false");
+                    MonoBehaviour.print(buffer);
+                }
+                return false;
+            }
+
+            if (this.currentStage > 0 && !this.doingCurrent)
+            {
+                if (SimManager.logOutput)
+                {
+                    buffer.AppendLine("Current stage > 0 && !doingCurrent => true");
+                    MonoBehaviour.print(buffer);
+                }
+                return true;
+            }
+
+            if (SimManager.logOutput)
+            {
+                buffer.AppendLine("Returning false");
+                MonoBehaviour.print(buffer);
+            }
+            return false;
+        }
+
+        // This function activates the next stage
+        // currentStage must be updated before calling this function
+        private void ActivateStage()
+        {
+            // Build a set of all the parts that will be decoupled
+            HashSet<PartSim> decoupledParts = new HashSet<PartSim>();
+            foreach (PartSim partSim in this.allParts)
+            {
+                if (partSim.decoupledInStage >= this.currentStage)
+                    decoupledParts.Add(partSim);
+            }
+
+            foreach (PartSim partSim in decoupledParts)
+            {
+                // Remove it from the all parts list
+                this.allParts.Remove(partSim);
+                if (partSim.isEngine)
+                {
+                    // If it is an engine then loop through all the engine modules and remove all the ones from this engine part
+                    for (int i = this.allEngines.Count - 1; i >= 0; i--)
+                    {
+                        if (this.allEngines[i].partSim == partSim)
+                            this.allEngines.RemoveAt(i);
+                    }
+                }
+                // If it is a fuel line then remove it from the list of all fuel lines
+                if (partSim.isFuelLine)
+                    this.allFuelLines.Remove(partSim);
+            }
+
+            // Loop through all the (remaining) parts
+            foreach (PartSim partSim in this.allParts)
+            {
+                // Ask the part to remove all the parts that are decoupled
+                partSim.RemoveAttachedParts(decoupledParts);
+            }
+
+            // Now we loop through all the engines and activate those that are ignited in this stage
+            foreach(EngineSim engine in this.allEngines)
+            {
+                if (engine.partSim.inverseStage == this.currentStage)
+                    engine.isActive = true;
+            }
+        }
+
+        private double ShipStartMass
+        {
+            get
+            {
+                double mass = 0d;
+
+                foreach (PartSim partSim in this.allParts)
+                {
+                    mass += partSim.GetStartMass();
+                }
+
+                return mass;
+            }
+        }
+
+        private double ShipMass
+        {
+            get
+            {
+                double mass = 0d;
+
+                foreach (PartSim partSim in this.allParts)
+                {
+                    mass += partSim.GetMass();
+                }
+
+                return mass;
+            }
+        }
+
+        public void Dump()
+        {
+            StringBuilder buffer = new StringBuilder(1024);
+            buffer.AppendFormat("Part count = {0:d}\n", this.allParts.Count);
+
+            // Output a nice tree view of the rocket
+            if (this.allParts.Count > 0)
+            {
+                PartSim root = this.allParts[0];
+                while (root.parent != null)
+                    root = root.parent;
+
+                if (root.hasVessel)
+                    buffer.AppendFormat("vesselName = '{0}'  vesselType = {1}\n", this.vesselName, SimManager.GetVesselTypeString(this.vesselType));
+
+                root.DumpPartToBuffer(buffer, "", this.allParts);
+            }
+
+            MonoBehaviour.print(buffer);
+        }
+    }
+}
+

--- /dev/null
+++ b/KerbalEngineer/VesselSimulator/Stage.cs
@@ -1,1 +1,54 @@
+// Kerbal Engineer Redux
+// Author:  CYBUTEK
+// License: Attribution-NonCommercial-ShareAlike 3.0 Unported

+

+using System.Text;

+

+using UnityEngine;

+

+namespace KerbalEngineer.VesselSimulator
+{
+    public class Stage
+    {
+        public int number = 0;
+        public float cost = 0;
+        public float 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;
 
+        public void Dump()
+        {
+            StringBuilder str = new StringBuilder("", 512);
+            str.AppendFormat("number        : {0:d}\n", this.number);
+            str.AppendFormat("cost          : {0:d}\n", this.cost);
+            str.AppendFormat("totalCost     : {0:d}\n", this.totalCost);
+            str.AppendFormat("time          : {0:g6}\n", this.time);
+            str.AppendFormat("totalTime     : {0:g6}\n", this.totalTime);
+            str.AppendFormat("mass          : {0:g6}\n", this.mass);
+            str.AppendFormat("totalMass     : {0:g6}\n", this.totalMass);
+            str.AppendFormat("isp           : {0:g6}\n", this.isp);
+            str.AppendFormat("thrust        : {0:g6}\n", this.thrust);
+            str.AppendFormat("actualThrust  : {0:g6}\n", this.actualThrust);
+            str.AppendFormat("thrustToWeight: {0:g6}\n", this.thrustToWeight);
+            str.AppendFormat("maxTWR        : {0:g6}\n", this.maxThrustToWeight);
+            str.AppendFormat("actualTWR     : {0:g6}\n", this.actualThrustToWeight);
+            str.AppendFormat("deltaV        : {0:g6}\n", this.deltaV);
+            str.AppendFormat("totalDeltaV   : {0:g6}\n", this.totalDeltaV);
+            str.AppendFormat("invTotDeltaV  : {0:g6}\n", this.inverseTotalDeltaV);
+            
+            MonoBehaviour.print(str);
+        }
+    }
+}
+

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