From c8d23afa0ac3fd3ed9c1529d8af71cae10fcfea5 Mon Sep 17 00:00:00 2001 From: Anton Kuzin Date: Sun, 4 Jan 2026 23:10:03 +0300 Subject: [PATCH 1/3] First manual version --- MechJeb2/MechJebModuleStageStats.cs | 9 +++++++- MechJeb2/MechJebModuleStagingController.cs | 3 +++ MechJeb2/MechJebStageStatsHelper.cs | 6 ++++++ .../FuelFlowSimulation/FuelFlowSimulation.cs | 21 ++++++++++++++++++- MechJebLib/FuelFlowSimulation/SimVessel.cs | 12 ++++++++++- .../FuelFlowSimulation/SimVesselManager.cs | 6 +++--- 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/MechJeb2/MechJebModuleStageStats.cs b/MechJeb2/MechJebModuleStageStats.cs index 52d006754..affee731d 100644 --- a/MechJeb2/MechJebModuleStageStats.cs +++ b/MechJeb2/MechJebModuleStageStats.cs @@ -1,4 +1,4 @@ -extern alias JetBrainsAnnotations; +extern alias JetBrainsAnnotations; using System.Collections.Generic; using System.Diagnostics; using MechJebLib.FuelFlowSimulation; @@ -19,6 +19,11 @@ public class MechJebModuleStageStats : ComputerModule public double AltSLT = 0; public double Mach = 0; + [Persistent(pass = (int)(Pass.TYPE | Pass.GLOBAL))] + public readonly EditableInt HalfStageIndex = new EditableInt(-1); + [Persistent(pass = (int)(Pass.TYPE | Pass.GLOBAL))] + public readonly EditableDoubleMult HalfStageEndMass = new EditableDoubleMult(0, 0.001); + private int _vabRebuildTimer = 1; public readonly List AtmoStats = new List(); @@ -124,6 +129,7 @@ private void RunSimulation() _vesselManagerVac.SetConditions(0, 0, 0); _vesselManagerVac.SetInitial(VesselState.time, VesselState.orbitalPosition.WorldToV3Rotated(), VesselState.orbitalVelocity.WorldToV3Rotated(), VesselState.forward.WorldToV3Rotated()); + _vesselManagerVac.SetupStageAndAHalf(HalfStageIndex, HalfStageEndMass); _vesselManagerVac.StartFuelFlowSimulationJob(); } @@ -133,6 +139,7 @@ private void RunSimulation() _vesselManagerAtmo.SetConditions(atmDensity, staticPressureKpa * PhysicsGlobals.KpaToAtmospheres, mach); _vesselManagerAtmo.SetInitial(VesselState.time, VesselState.orbitalPosition.WorldToV3Rotated(), VesselState.orbitalVelocity.WorldToV3Rotated(), VesselState.forward.WorldToV3Rotated()); + _vesselManagerAtmo.SetupStageAndAHalf(HalfStageIndex, HalfStageEndMass); _vesselManagerAtmo.StartFuelFlowSimulationJob(); } } diff --git a/MechJeb2/MechJebModuleStagingController.cs b/MechJeb2/MechJebModuleStagingController.cs index eade96021..6800a4ffc 100644 --- a/MechJeb2/MechJebModuleStagingController.cs +++ b/MechJeb2/MechJebModuleStagingController.cs @@ -291,6 +291,9 @@ public override void OnUpdate() UpdateActiveModuleEngines(_allModuleEngines); UpdateBurnedResources(); + if (Vessel.currentStage == _stats.HalfStageIndex && Vessel.totalMass <= _stats.HalfStageEndMass) + Stage(); + // don't decouple active or idle engines or tanks if (InverseStageDecouplesActiveOrIdleEngineOrTank(Vessel.currentStage - 1, _burnedResources, _activeModuleEngines) && !InverseStageReleasesClamps(Vessel.currentStage - 1)) diff --git a/MechJeb2/MechJebStageStatsHelper.cs b/MechJeb2/MechJebStageStatsHelper.cs index d07e824a6..e7e8d7c88 100644 --- a/MechJeb2/MechJebStageStatsHelper.cs +++ b/MechJeb2/MechJebStageStatsHelper.cs @@ -296,6 +296,12 @@ public void AllStageStats() Profiler.BeginSample("AllStageStats.DrawColumns"); + GUILayout.BeginHorizontal(); + GuiUtils.SimpleTextBox("Half-stage index: ", stats.HalfStageIndex, width: 30); + GUILayout.Space(30); + GuiUtils.SimpleTextBox("End mass: ", stats.HalfStageEndMass, "kg"); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); //DrawStageStatsColumn(CachedLocalizer.Instance.MechJeb_InfoItems_StatsColumn0, stages.Select(s => s.ToString()).ToList()); diff --git a/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs b/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs index 743941d40..df944e8a1 100644 --- a/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs +++ b/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ @@ -114,6 +114,11 @@ private void SimulateStage(SimVessel vessel) return; double dt = MinimumTimeStep(); + if (_currentSegment.KSPStage == vessel.HalfStageIndex) + { + double massFlow = ResourceMaxMassFlow(vessel); + dt = Min(dt, (_currentSegment.StartMass - vessel.HalfStageEndMass) / massFlow); + } // FIXME: if we have constructed a segment which is > 0 dV, but less than 0.02s, and there's a // prior > 0dV segment in the same kspStage we should add those together to reduce clutter. @@ -379,6 +384,17 @@ private double ResourceMaxTime() return maxTime; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private double ResourceMaxMassFlow(SimVessel vessel) + { + double massFlow = 0; + + foreach (SimModuleEngines engine in vessel.ActiveEngines) + massFlow += engine.MassFlowRate; + + return massFlow; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void FinishRcsSegment(bool max, double deltaTime, double startMass, double endMass, double rcsThrust) { @@ -453,6 +469,9 @@ private static bool AllowedToStage(SimVessel vessel) if (vessel.ActiveEngines.Count == 0) return true; + if (vessel.CurrentStage == vessel.HalfStageIndex && vessel.Mass <= vessel.HalfStageEndMass + 0.001) + return true; + for (int i = 0; i < vessel.ActiveEngines.Count; i++) { SimModuleEngines e = vessel.ActiveEngines[i]; diff --git a/MechJebLib/FuelFlowSimulation/SimVessel.cs b/MechJebLib/FuelFlowSimulation/SimVessel.cs index deff7eaf3..65f67d096 100644 --- a/MechJebLib/FuelFlowSimulation/SimVessel.cs +++ b/MechJebLib/FuelFlowSimulation/SimVessel.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ @@ -42,6 +42,9 @@ public class SimVessel : IDisposable public double T; public V3 R, V, U; + public int HalfStageIndex; + public double HalfStageEndMass; + // CurrentStage gets scribbled over by the FuelFlowSimulation, SetCurrentStage() is intended to be used in // the VesselBuilder and DecouplingAnalyzer to figure out the right value, ResetCurrentStage() is called by // the VesselUpdater to reset it back. @@ -76,6 +79,13 @@ public void SetInitial(double t, V3 r, V3 v, V3 u) U = u; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetupStageAndAHalf(int halfStageIndex, double endMass) + { + HalfStageIndex = halfStageIndex; + HalfStageEndMass = endMass; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void UpdateMass() { diff --git a/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs b/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs index bea60aec1..676ff3157 100644 --- a/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs +++ b/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ @@ -13,8 +13,6 @@ namespace MechJebLibBindings.FuelFlowSimulation // need to link against KSP GameObjects (MechJebLibBindings.dll or something like that) public partial class SimVesselManager { - public List Segments => FuelFlowSimulation.Segments; - private readonly SimVesselBuilder _builder; private readonly SimVesselUpdater _updater; private SimVessel _vessel; @@ -59,6 +57,8 @@ public void SetConditions(double atmDensity, double atmPressure, double machNumb public void SetInitial(double t, V3 r, V3 v, V3 u) => _vessel.SetInitial(t, r, v, u); + public void SetupStageAndAHalf(int halfStageIndex, double endMass) => _vessel.SetupStageAndAHalf(halfStageIndex, endMass); + public void StartFuelFlowSimulationJob() { FuelFlowSimulation.DVLinearThrust = DVLinearThrust; From 84e7bd83d0a6cb592446a082c16de65ea1fbad7d Mon Sep 17 00:00:00 2001 From: Anton Kuzin Date: Fri, 9 Jan 2026 00:36:42 +0300 Subject: [PATCH 2/3] Small refactoring - more sane control flow. --- .../FuelFlowSimulation/SimVesselBuilder.cs | 24 ++++++------------- .../FuelFlowSimulation/SimVesselManager.cs | 7 ++++-- .../FuelFlowSimulation/SimVesselUpdater.cs | 4 ++-- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/MechJebLibBindings/FuelFlowSimulation/SimVesselBuilder.cs b/MechJebLibBindings/FuelFlowSimulation/SimVesselBuilder.cs index 5af63f1fa..7ce3fa6fa 100644 --- a/MechJebLibBindings/FuelFlowSimulation/SimVesselBuilder.cs +++ b/MechJebLibBindings/FuelFlowSimulation/SimVesselBuilder.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ @@ -29,17 +29,9 @@ public class SimVesselBuilder private static readonly CrewMass _crewMassDelegate; - private SimVessel _vessel - { - get => _manager._vessel; - set => _manager._vessel = value; - } + private SimVessel _vessel => _manager._vessel; - private IShipconstruct _kspVessel - { - get => _manager._kspVessel; - set => _manager._kspVessel = value; - } + private IShipconstruct _kspVessel => _manager._kspVessel; private readonly SimVesselManager _manager; private static readonly Type? _rfType; @@ -68,7 +60,7 @@ static SimVesselBuilder() private static double CrewMassNew(ProtoCrewMember crew) => PhysicsGlobals.KerbalCrewMass + crew.ResourceMass() + crew.InventoryMass(); - public SimVesselBuilder(SimVesselManager manager) + internal SimVesselBuilder(SimVesselManager manager) { _manager = manager; } @@ -126,14 +118,12 @@ internal void UpdateSymmetryParts() } } - internal void BuildVessel(IShipconstruct kspVessel) + internal void BuildVessel() { - _vessel.Dispose(); - _vessel = SimVessel.Borrow(); - _kspVessel = kspVessel; + BuildParts(); } - internal void BuildParts() + private void BuildParts() { _vessel.SetCurrentStage(StageManager.CurrentStage); diff --git a/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs b/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs index 676ff3157..713736b60 100644 --- a/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs +++ b/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs @@ -39,9 +39,12 @@ public SimVesselManager() public void Build(IShipconstruct vessel) { + _vessel.Dispose(); + _vessel = SimVessel.Borrow(); + _kspVessel = vessel; + Clear(); - _builder.BuildVessel(vessel); - _builder.BuildParts(); + _builder.BuildVessel(); Update(); _builder.UpdateLinks(); _builder.UpdateCrossFeedSet(); diff --git a/MechJebLibBindings/FuelFlowSimulation/SimVesselUpdater.cs b/MechJebLibBindings/FuelFlowSimulation/SimVesselUpdater.cs index 6265c7958..a7d1adef3 100644 --- a/MechJebLibBindings/FuelFlowSimulation/SimVesselUpdater.cs +++ b/MechJebLibBindings/FuelFlowSimulation/SimVesselUpdater.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ @@ -49,7 +49,7 @@ static SimVesselUpdater() } } - public SimVesselUpdater(SimVesselManager manager) + internal SimVesselUpdater(SimVesselManager manager) { _manager = manager; } From 627038963636ef7f778bd0c75608816cea5ff56f Mon Sep 17 00:00:00 2001 From: Anton Kuzin Date: Fri, 9 Jan 2026 17:13:05 +0300 Subject: [PATCH 3/3] Detect stage and a half automatically --- MechJeb2/MechJebModuleStageStats.cs | 7 +++---- MechJeb2/MechJebStageStatsHelper.cs | 15 +++++++++------ .../FuelFlowSimulation/FuelFlowSimulation.cs | 8 ++++++++ MechJebLib/FuelFlowSimulation/SimVessel.cs | 12 +++++++----- .../FuelFlowSimulation/SimVesselManager.cs | 4 +++- 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/MechJeb2/MechJebModuleStageStats.cs b/MechJeb2/MechJebModuleStageStats.cs index affee731d..d677b525b 100644 --- a/MechJeb2/MechJebModuleStageStats.cs +++ b/MechJeb2/MechJebModuleStageStats.cs @@ -19,8 +19,7 @@ public class MechJebModuleStageStats : ComputerModule public double AltSLT = 0; public double Mach = 0; - [Persistent(pass = (int)(Pass.TYPE | Pass.GLOBAL))] - public readonly EditableInt HalfStageIndex = new EditableInt(-1); + public int HalfStageIndex => _vesselManagerVac.HalfStageIndex; [Persistent(pass = (int)(Pass.TYPE | Pass.GLOBAL))] public readonly EditableDoubleMult HalfStageEndMass = new EditableDoubleMult(0, 0.001); @@ -129,7 +128,7 @@ private void RunSimulation() _vesselManagerVac.SetConditions(0, 0, 0); _vesselManagerVac.SetInitial(VesselState.time, VesselState.orbitalPosition.WorldToV3Rotated(), VesselState.orbitalVelocity.WorldToV3Rotated(), VesselState.forward.WorldToV3Rotated()); - _vesselManagerVac.SetupStageAndAHalf(HalfStageIndex, HalfStageEndMass); + _vesselManagerVac.SetupStageAndAHalf(HalfStageEndMass); _vesselManagerVac.StartFuelFlowSimulationJob(); } @@ -139,7 +138,7 @@ private void RunSimulation() _vesselManagerAtmo.SetConditions(atmDensity, staticPressureKpa * PhysicsGlobals.KpaToAtmospheres, mach); _vesselManagerAtmo.SetInitial(VesselState.time, VesselState.orbitalPosition.WorldToV3Rotated(), VesselState.orbitalVelocity.WorldToV3Rotated(), VesselState.forward.WorldToV3Rotated()); - _vesselManagerAtmo.SetupStageAndAHalf(HalfStageIndex, HalfStageEndMass); + _vesselManagerAtmo.SetupStageAndAHalf(HalfStageEndMass); _vesselManagerAtmo.StartFuelFlowSimulationJob(); } } diff --git a/MechJeb2/MechJebStageStatsHelper.cs b/MechJeb2/MechJebStageStatsHelper.cs index e7e8d7c88..726e5af13 100644 --- a/MechJeb2/MechJebStageStatsHelper.cs +++ b/MechJeb2/MechJebStageStatsHelper.cs @@ -294,13 +294,16 @@ public void AllStageStats() Profiler.EndSample(); - Profiler.BeginSample("AllStageStats.DrawColumns"); + if (stats.HalfStageIndex != -1) + { + GUILayout.BeginHorizontal(); + GUILayout.Label($"Half-stage index: {stats.HalfStageIndex}", GUILayout.Width(130)); + GUILayout.Space(30); + GuiUtils.SimpleTextBox("End mass: ", stats.HalfStageEndMass, "kg"); + GUILayout.EndHorizontal(); + } - GUILayout.BeginHorizontal(); - GuiUtils.SimpleTextBox("Half-stage index: ", stats.HalfStageIndex, width: 30); - GUILayout.Space(30); - GuiUtils.SimpleTextBox("End mass: ", stats.HalfStageEndMass, "kg"); - GUILayout.EndHorizontal(); + Profiler.BeginSample("AllStageStats.DrawColumns"); GUILayout.BeginHorizontal(); //DrawStageStatsColumn(CachedLocalizer.Instance.MechJeb_InfoItems_StatsColumn0, stages.Select(s => s.ToString()).ToList()); diff --git a/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs b/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs index df944e8a1..d86bb35d3 100644 --- a/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs +++ b/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using MechJebLib.FuelFlowSimulation.PartModules; using MechJebLib.Utils; @@ -113,6 +114,13 @@ private void SimulateStage(SimVessel vessel) if (AllowedToStage(vessel)) return; + int earliestDroppedEgineInStage = vessel.ActiveEngines.Max(x => x.Part.DecoupledInStage); + int earliestDroppedTankInStage = _sources.Max(x => x.DecoupledInStage); + if (earliestDroppedEgineInStage > earliestDroppedTankInStage) + { + vessel.HalfStageIndex = earliestDroppedEgineInStage + 1; + } + double dt = MinimumTimeStep(); if (_currentSegment.KSPStage == vessel.HalfStageIndex) { diff --git a/MechJebLib/FuelFlowSimulation/SimVessel.cs b/MechJebLib/FuelFlowSimulation/SimVessel.cs index 65f67d096..0b3b5607b 100644 --- a/MechJebLib/FuelFlowSimulation/SimVessel.cs +++ b/MechJebLib/FuelFlowSimulation/SimVessel.cs @@ -42,8 +42,8 @@ public class SimVessel : IDisposable public double T; public V3 R, V, U; - public int HalfStageIndex; - public double HalfStageEndMass; + public int HalfStageIndex = -1; + public double HalfStageEndMass = 0; // CurrentStage gets scribbled over by the FuelFlowSimulation, SetCurrentStage() is intended to be used in // the VesselBuilder and DecouplingAnalyzer to figure out the right value, ResetCurrentStage() is called by @@ -80,10 +80,12 @@ public void SetInitial(double t, V3 r, V3 v, V3 u) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetupStageAndAHalf(int halfStageIndex, double endMass) + public void SetupStageAndAHalf(double endMass) { - HalfStageIndex = halfStageIndex; - HalfStageEndMass = endMass; + if (HalfStageIndex != -1) + { + HalfStageEndMass = endMass; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs b/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs index 713736b60..08af089f0 100644 --- a/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs +++ b/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs @@ -29,6 +29,8 @@ public partial class SimVesselManager public V3 V => _vessel.V; public V3 U => _vessel.U; + public int HalfStageIndex => _vessel.HalfStageIndex; + public SimVesselManager() { _builder = new SimVesselBuilder(this); @@ -60,7 +62,7 @@ public void SetConditions(double atmDensity, double atmPressure, double machNumb public void SetInitial(double t, V3 r, V3 v, V3 u) => _vessel.SetInitial(t, r, v, u); - public void SetupStageAndAHalf(int halfStageIndex, double endMass) => _vessel.SetupStageAndAHalf(halfStageIndex, endMass); + public void SetupStageAndAHalf(double endMass) => _vessel.SetupStageAndAHalf(endMass); public void StartFuelFlowSimulationJob() {