Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c8d23af
First manual version
Jan 4, 2026
c4719ed
Merge branch 'dev' into atlas-friendly
Jan 4, 2026
1b7e68b
Merge branch 'dev' into atlas-friendly
Jan 6, 2026
5d731a8
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Jan 8, 2026
84e7bd8
Small refactoring - more sane control flow.
Jan 8, 2026
6270389
Detect stage and a half automatically
Jan 9, 2026
19e0f93
Merge branch 'dev' into atlas-friendly
Jan 9, 2026
c680899
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Jan 12, 2026
895f062
Merge branch 'dev' into atlas-friendly
Feb 4, 2026
6c9186d
Merge branch 'dev' into atlas-friendly
Feb 5, 2026
13cff53
Small refactoring
Feb 7, 2026
c3e9f54
Merge branch 'staging-controller-fix' into atlas-friendly
Feb 7, 2026
2c9280a
Don't check if there's a fairing twice. Revert the function argument …
Feb 7, 2026
8364162
Merge branch 'staging-controller-fix' into atlas-friendly
Feb 8, 2026
02f5f2f
Revert fairing check removal
Feb 8, 2026
5fed147
Merge branch 'staging-controller-fix' into atlas-friendly
Feb 8, 2026
46447bb
Merge branch 'dev' into atlas-friendly
Feb 11, 2026
2d4f825
Merge remote-tracking branch 'origin/dev' into atlas-friendly
Feb 15, 2026
aabfd35
Merge remote-tracking branch 'origin/dev' into atlas-friendly
Mar 1, 2026
e38049a
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Mar 15, 2026
27768ab
Merge branch 'MuMech:dev' into staging-controller-fix
AntonKuzin Mar 15, 2026
b42cec3
Fix fuel flow for stages with multiple tanks
Mar 15, 2026
c95518c
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Apr 11, 2026
24630c2
Merge branch 'MuMech:dev' into staging-controller-fix
AntonKuzin Apr 11, 2026
a174911
Merge branch 'MuMech:dev' into staging-controller-fix
AntonKuzin Apr 25, 2026
f935f1f
Merge from master
Apr 25, 2026
ca61779
Return immediately after staging
Apr 25, 2026
34c478c
Merge from master
May 6, 2026
c7265c9
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin May 14, 2026
7f1c9c1
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Jun 1, 2026
293adf5
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Jun 5, 2026
8c93efd
Fix space
Jun 18, 2026
305c973
Merge from master
Jun 18, 2026
8210600
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Jun 21, 2026
d15d1b9
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Jun 25, 2026
0561d31
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Jun 25, 2026
af8161e
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Jun 28, 2026
158c13a
Merge branch 'MuMech:dev' into atlas-friendly
AntonKuzin Jun 29, 2026
c4ae1b4
Merge from master
Jul 29, 2026
6923068
Fuel flow simulation and staging controller improvements
Aug 13, 2026
d9cc29d
Fixed the fixme
Aug 13, 2026
dfd99fb
Don't drain more than the residuals allow
Aug 14, 2026
350d30d
Merge branch 'dev' into atlas-friendly
Aug 14, 2026
60373c9
Handle solids correctly. Replace linq with loops
Aug 14, 2026
654014d
Bring back into the correct place the engines count update
Aug 15, 2026
7382c87
Merge branch 'dev' into atlas-friendly
Aug 15, 2026
af33214
Use the correct tanks collection for half-stage detection
Aug 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions MechJeb2/MechJebModuleStageStats.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
extern alias JetBrainsAnnotations;
extern alias JetBrainsAnnotations;
using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand All @@ -21,6 +21,10 @@ public class MechJebModuleStageStats : ComputerModule
public double AltSLT = 0;
public double Mach = 0;

public int HalfStageIndex => _vesselManagerVac.HalfStageIndex;
[Persistent(pass = (int)(Pass.TYPE | Pass.GLOBAL))]
public readonly EditableDoubleMult HalfStageEndMass = new EditableDoubleMult(0, 0.001);

private int _vabRebuildTimer = 1;

public readonly List<FuelStats> AtmoStats = new List<FuelStats>();
Expand Down Expand Up @@ -148,6 +152,7 @@ private void RunSimulation()
_vesselManagerVac.SetConditions(0, 0, 0);
_vesselManagerVac.SetInitial(VesselState.Time, VesselState.OrbitalPosition.WorldToV3Rotated(),
VesselState.OrbitalVelocity.WorldToV3Rotated(), VesselState.Forward.WorldToV3Rotated());
_vesselManagerVac.SetupStageAndAHalf(HalfStageEndMass);
if (!_vesselManagerVac.TryStartFuelFlowSimulationJob())
throw new Exception("[MechJebModuleStageStats] could not start vac stats job");
}
Expand All @@ -158,7 +163,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.PrintVessel();
_vesselManagerAtmo.SetupStageAndAHalf(HalfStageEndMass);
if (!_vesselManagerAtmo.TryStartFuelFlowSimulationJob())
throw new Exception("[MechJebModuleStageStats] could not start atmo stats job");
}
Expand Down
8 changes: 7 additions & 1 deletion MechJeb2/MechJebModuleStagingController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,12 @@ public override void OnFixedUpdate()
UpdateBurnedResources();
_shouldDropSolids = null; // invalidate the cache; _droppingSolids recomputes lazily if needed this update

if (Vessel.currentStage == _stats.HalfStageIndex && Vessel.totalMass <= _stats.HalfStageEndMass)
{
Stage();
return;
}

// don't decouple active or idle engines or tanks
if (InverseStageDecouplesActiveOrIdleEngineOrTank(Vessel.currentStage - 1, _burnedResources, _activeModuleEngines) &&
!InverseStageReleasesClamps(Vessel.currentStage - 1))
Expand Down Expand Up @@ -613,7 +619,7 @@ private bool HasActiveOrIdleEngineOrTankDescendant(Part p, List<int> tankResourc
PartResource r = p.Resources.Get(propellant.id);

if (r.amount <= p.resourceRequestRemainingThreshold)
continue;
return false;
if (r.info.id == PartResourceLibrary.ElectricityHashcode)
continue;
if (!tankResources.Contains(r.info.id))
Expand Down
9 changes: 9 additions & 0 deletions MechJeb2/MechJebStageStatsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,15 @@ public void AllStageStats()

Profiler.EndSample();

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

Profiler.BeginSample("AllStageStats.DrawColumns");

GUILayout.BeginHorizontal();
Expand Down
59 changes: 52 additions & 7 deletions MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/*
/*
* 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+
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using MechJebLib.FuelFlowSimulation.PartModules;
using MechJebLib.Utils;
Expand All @@ -25,6 +26,7 @@ public class FuelFlowSimulation : AsyncJob
private readonly HashSet<SimPart> _partsWithRCSDrains = new HashSet<SimPart>();
private readonly HashSet<SimPart> _partsWithRCSDrains2 = new HashSet<SimPart>();
private bool _allocatedFirstSegment;
private bool _halfStageIsDetected;

public override void Run(object? o = null)
{
Expand All @@ -34,6 +36,7 @@ public override void Run(object? o = null)
if (!(o is SimVessel vessel))
throw new ArgumentException("o is not a SimVessel", nameof(o));

_halfStageIsDetected = false;
_allocatedFirstSegment = false;
_time = 0;
Segments.Clear();
Expand All @@ -53,6 +56,9 @@ public override void Run(object? o = null)
Segments.Reverse();

_partsWithResourceDrains.Clear();

if (!_halfStageIsDetected)
vessel.HalfStageIndex = -1;
}

private void SimulateRCS(SimVessel vessel, bool max)
Expand Down Expand Up @@ -129,24 +135,49 @@ private void SimulateStage(SimVessel vessel)
ComputeRcsUllageTime(vessel);

UpdateResourceDrainsAndResiduals(vessel);
double currentThrust = vessel.ThrustMagnitude;
int activeAngines = vessel.ActiveEngines.Count;

if (!_halfStageIsDetected //is anyone insane enough to build a rocket with multiple half-stages? You never know
&& vessel.ActiveEngines.Count > 0
&& _partsWithResourceDrains.Count > 0)
{
int earliestDroppedEgineInStage = 0, earliestDroppedTankInStage = 0;
for (int i = 0; i < vessel.ActiveEngines.Count; i++)
{
earliestDroppedEgineInStage = Max(earliestDroppedEgineInStage, vessel.ActiveEngines[i].Part.DecoupledInStage);
}

foreach (var tank in _partsWithResourceDrains)
{
earliestDroppedTankInStage = Max(earliestDroppedTankInStage, tank.DecoupledInStage);
}

if (earliestDroppedEgineInStage > earliestDroppedTankInStage)
{
_halfStageIsDetected = true;
vessel.HalfStageIndex = earliestDroppedEgineInStage + 1;
}
}

for (int steps = MAXSTEPS; steps > 0; steps--)
{
if (AllowedToStage(vessel))
return;

double dt = MinimumTimeStep();
if (_currentSegment.KSPStage == vessel.HalfStageIndex)
{
double massFlow = ResourceMaxMassFlow(vessel);
dt = Min(dt, (vessel.Mass - 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.
if (Abs(vessel.ThrustMagnitude - currentThrust) > 1e-12)
if (dt >= 0.02 && activeAngines != vessel.ActiveEngines.Count)
{
ClearResiduals();
ComputeRcsMaxValues(vessel);
FinishSegment(vessel);
GetNextSegment(vessel);
currentThrust = vessel.ThrustMagnitude;
activeAngines = vessel.ActiveEngines.Count;
}

_time += dt;
Expand Down Expand Up @@ -388,7 +419,7 @@ private double MinimumTimeStep()
{
double maxTime = ResourceMaxTime();

return maxTime < double.MaxValue && maxTime >= 0 ? maxTime : 0;
return maxTime < double.MaxValue && maxTime > 0.001 ? maxTime : 0.001;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand All @@ -402,6 +433,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)
{
Expand Down Expand Up @@ -488,6 +530,9 @@ private static bool AllowedToStage(SimVessel vessel)
if (vessel.ActiveEngines.Count == 0)
return true;

if (vessel.CurrentStage == vessel.HalfStageIndex && vessel.Mass - vessel.HalfStageEndMass < 1e-6)
return true;

for (int i = 0; i < vessel.ActiveEngines.Count; i++)
{
SimModuleEngines e = vessel.ActiveEngines[i];
Expand Down
6 changes: 3 additions & 3 deletions MechJebLib/FuelFlowSimulation/SimResource.cs
Original file line number Diff line number Diff line change
@@ -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+
*/
Expand Down Expand Up @@ -27,8 +27,8 @@ public double Amount
public SimResource Drain(double resourceDrain)
{
_amount -= resourceDrain;
if (_amount < 0)
_amount = 0;
if (_amount < ResidualThreshold)
_amount = ResidualThreshold;

return this;
}
Expand Down
14 changes: 13 additions & 1 deletion MechJebLib/FuelFlowSimulation/SimVessel.cs
Original file line number Diff line number Diff line change
@@ -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+
*/
Expand Down Expand Up @@ -49,6 +49,9 @@ public class SimVessel : IDisposable
public double T;
public V3 R, V, U;

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
// the VesselUpdater to reset it back.
Expand Down Expand Up @@ -83,6 +86,15 @@ public void SetInitial(double t, V3 r, V3 v, V3 u)
U = u;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetupStageAndAHalf(double endMass)
{
if (HalfStageIndex != -1)
{
HalfStageEndMass = endMass;
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateMass()
{
Expand Down
24 changes: 7 additions & 17 deletions MechJebLibBindings/FuelFlowSimulation/SimVesselBuilder.cs
Original file line number Diff line number Diff line change
@@ -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+
*/
Expand Down Expand Up @@ -36,17 +36,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;

Expand All @@ -63,7 +55,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;
}
Expand Down Expand Up @@ -121,14 +113,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);

Expand Down
13 changes: 10 additions & 3 deletions MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs
Original file line number Diff line number Diff line change
@@ -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+
*/
Expand Down Expand Up @@ -32,6 +32,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);
Expand All @@ -42,9 +44,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();
Expand All @@ -62,6 +67,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(double endMass) => _vessel.SetupStageAndAHalf(endMass);

public bool TryStartFuelFlowSimulationJob()
{
FuelFlowSimulation.DVLinearThrust = DVLinearThrust;
Expand Down
4 changes: 2 additions & 2 deletions MechJebLibBindings/FuelFlowSimulation/SimVesselUpdater.cs
Original file line number Diff line number Diff line change
@@ -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+
*/
Expand Down Expand Up @@ -38,7 +38,7 @@ static SimVesselUpdater()
_isRealFuelsLoadedCorrectly = IsLoadedRealFuels && _rfPredictedMaximumResiduals.IsValid;
}

public SimVesselUpdater(SimVesselManager manager)
internal SimVesselUpdater(SimVesselManager manager)
{
_manager = manager;
}
Expand Down