Delta-V
Overview
The system computes Tsiolkovsky rocket-equation delta-V for every stage of a vessel or OAB assembly. Three classes collaborate: VesselDeltaVComponent orchestrates the whole pipeline; DeltaVStageInfo owns the per-stage simulation; DeltaVPartInfo tracks per-part mass and staging metadata.
Data Model
VesselDeltaVComponent
├─ PartInfo: DictionaryValueList<IGGuid, DeltaVPartInfo> (one entry per part)
├─ EngineStageSet: DeltaVEngineStageSet
│ ├─ EngineInfo: List<DeltaVEngineInfo> (one entry per engine)
│ └─ StageInfo: List<DeltaVStageInfo> (one per stage number)
└─ SeparationStageIndexes: List<int> (decoupler stage numbers)DeltaVPartInfo
├─ ActivationStage, DecoupleStage, DecoupleBeforeBurn
├─ DryMass, JettisonMass
└─ StageFuelMass: DictionaryValueList<int, PartStageFuelMass>
└─ PartStageFuelMass { StartMass, EndMass, Resources }DeltaVStageInfo
├─ Parts: List<DeltaVPartInfo> (parts still attached this stage)
├─ EnginesActiveInStage / EnginesInStage
└─ DeltaVCalcs (_subStages): List<DeltaVCalc>Delta-V Calculations
Phase 1 — Separation Index Walk (SetSeparationIndices)
Called once on construction and on every full recalc. Walks the part tree recursively from the root, propagating DecoupleStage downward.
Normal decoupler (explosive node toward parent):
DecoupleStage = decoupler.ActivationStage. Children inherit this value. The parent side is jettisoned."Facing-away" decoupler (node toward children, #595 fix): the children are the payload — they keep the pre-decoupler
sepIndex. The ancestor subtree (lower stage) is later tagged bySetLowerStagesDecoupleStage.Disabled staging (
IsActiveInStagingProp = false): the part joins the parent's stage instead.Non-engine parts with
IsActiveInStaging = falsehave theirActivationStagereassigned to the highest active ancestor stage seen so far (carriedInverseStage). Engines are explicitly excluded from this mutation.
Phase 2 — Part Info Reset (ResetPartInfo)
Builds/refreshes _partInfo keyed by part GUID. For each part:
If already in the dict: refresh
DryMass,ActivationStage,DecoupleStage.Otherwise: create a new
DeltaVPartInfo(which callsCalculateStagingValues).
Post-loop corrections propagate DecoupleStage through the tree for separators and docking ports using ProcessDecouplePartInfoChildren. For bottom-attached fuel tanks below an engine, DecoupleStage is promoted to match the engine's StartBurnStage so the tank is included in the engine's stage mass (not counted as already-decoupled dead weight).
CalculateStagingValues resolves ActivationStage (falls back up the parent chain only when the value is −1), then determines DecoupleStage from the nearest separation event at or below that stage. The edge case for stage-0 engines with a facing-away decoupler parent forces DecoupleStage = -1 so the engine isn't misclassified as decoupled-before-burn.
Phase 3 — Stage Assembly (ProcessParts)
For each DeltaVStageInfo, rebuilds Parts from VesselDeltaVComponent.PartInfo:
Include a part if
part.DecoupleStage ≤ stage(it is still physically present when this stage fires).Exclude parts belonging to a different vessel (checked against
_ship.Partsin OAB orVessel.Containsin flight).
Then calls ProcessActiveEngines, which filters EngineInfo to produce EnginesActiveInStage:
include engine if:
Stage ≤ engine.StartBurnStage // engine fires this stage or earlier
AND part is in _partInfoDictionary // part is still attached
AND engine.DecoupleStage < Stage // engine hasn't been jettisoned yet
AND engine.ModuleEngine.IsActiveInStagingProp // player hasn't disabled it
AND (not flight current-stage, OR engine ignited, OR not yet launched)Phase 4 — Stage Simulation (SimulateDeltaV)
The core calculation chain for one stage:
CalculateStartMass()
└─ CarryOverConsumedFuelMass() // propagate prior-stage fuel depletion
└─ GetStageMass() // sum DryMass + FuelMass for all Parts
Parts with DecoupleStage == Stage → DecoupledMass (excluded from StartMass)
StartMass = StageMass − DecoupledMassSortEnginesByResource() // group by propellant type
CalcEngineStats()
└─ CalculateISP() // effective ISP = ThrustTotal / Σ(T_i/ISP_i)
└─ FuelFlow = Thrust / (ISP × g₀) // per engine, three conditions
└─ CalculateTWR() // Thrust / (StartMass × g_body)
ProcessEngineSharedResources() // distribute shared tank consumption proportionally by flow rate
CalcEngineBurnTimes() // BurnTime = fuelAvailable / FuelFlow; drop engines with ~0 fuel
ProcessEnginesToSubStages() // split into sub-stages at each engine burnout boundary
CalcSubStageStartEndMasses() // chain StartMass/EndMass across sub-stages
CalcSubStageTotals()
└─ ΔV = |vectoredThrust| × ln(m₀/m₁) × ISP × g₀ / Thrust (per sub-stage)
CalcStageDeltaVTotal() // sum sub-stages → DeltaVinVac, DeltaVatASL, DeltaVActualISP averaging (mixed engines): ISP_eff = ThrustTotal / Σ(Thrust_i / ISP_i) — the standard thrust-weighted harmonic mean.
Sub-staging: engines are sorted by MinBurnTime. Each distinct burnout time boundary creates a new DeltaVCalc sub-stage. Engines that run longer carry into subsequent sub-stages.
Update Scheduling (CheckDirtyAndRunCalcs)
Condition | Action |
|---|---|
| Full |
Flight, engine firing | Full recalc at 1 Hz |
Flight, coasting | Full recalc at 10 Hz |
Flight, always | Lightweight |
OAB, situation change |
|
A 1-second debounce (_vesselEventDelayTime) absorbs bursts of structural events (part placement, docking, fuel changes) before triggering a full recalc.
Three-Condition Outputs
Every quantity is computed for three atmospheric conditions, selected at query time:
Constant |
|
|
|
|---|---|---|---|
ISP | ISP at 0 Pa | ISP at 1 atm | ISP at current pressure |
Thrust | Vac thrust | ASL thrust | Current thrust |
TWR | Vac thrust / weight | ASL thrust / weight | Actual thrust / weight |
ΔV | Tsiolkovsky w/ ISP_vac | Tsiolkovsky w/ ISP_ASL | Tsiolkovsky w/ ISP_actual |
DeltaVActual in flight is optionally interpolated as Lerp(DvVac, DvASL, pressure_ratio) rather than re-simulated, which is the cheap path in RunFlightSceneCalculations.
Other relevant classes
Central classes
DeltaVEngineInfo — Per-engine data node. Holds ThrustVac/ASL/Actual, IspVac/ASL/Actual, the three FuelFlow scalars, MinBurnTime, and a back-reference to DeltaVPropellantInfo. Its two key methods:
CalculateISP(situation)— readsatmosphereCurveat the relevant pressure; in flight, substitutes the liveEngine.RealISPValuewhen the engine is actually ignited.CalculateThrustVector(situation)— callsCalcThrustActual, then scales the engine's world-spaceThrustDirRelativePartWorldSpacedirection by each thrust scalar to produce the threeThrustVector*vectors used in the vectored ΔV formula.
DeltaVPropellantInfo — Fuel availability tracker for one propellant type on one engine. Key responsibilities:
UpdatePropellantInfo(stage, partCount)— walks the resource flow graph (FlowRequestBroker) to populateContainers(theDeltaVPartInfo → PartStageFuelMassdict of tanks this engine can pull from). Skips parts already decoupled at this stage.AmountAvailable(stage)— sums stored units minus already-consumed units across allContainers, subtractsSharedAmount(this engine's share pre-reserved byProcessEngineSharedResources), then returns the limiting ingredient's units. IntakeAir is excluded from the limit.ConsumeUnits(units)— simulates fuel draw by writing toPartStageFuelMass.Resources.UnitsConsumed, first-come-first-served across containers. This is the mechanism that carries forward depletion across sub-stages.ConsumeSharedUnits(resourceID, units)— accumulates units intoSharedAmountso shared-tank consumption is apportioned byProcessEngineSharedResources.
DeltaVEngineStageSet — Container and lifecycle manager. Owns List<DeltaVStageInfo> and List<DeltaVEngineInfo>, sorted descending by stage number. UpdateStageInfo() calls Reset() on existing entries or creates new ones for each stage from 0 to LastStage. RemoveInvalidEngines() purges engine entries whose part no longer exists in the vessel/ship.
DeltaVCalc — Plain data bag for one sub-stage. Holds ActiveEngines, StartMass, EndMass, BurnTime, the three thrust/ISP/TWR/ΔV/fuel-flow triples, and the vectored thrust directions. No logic of its own — written by CalcSubStageStartEndMasses and CalcSubStageTotals.
Interface
IDeltaVPart — The abstraction boundary between the dV system and the actual part simulation. Exposes ActivationStage/DecoupleStage (read-write), DryMass/GreenMass, atmospheric conditions (StaticPressureAtm, AtmDensity, etc.), DeltaVContainers, DeltaVChildren, Parent, and FindAttachNodeAttachedPart. Implemented by both PartComponent (flight) and IObjectAssemblyPart (OAB).
Pure utilities
DeltaVExtensions (static) — Mode queries (ModeIsFlight, ModeIsOAB) and stage queries (CurrentStage, LastStage, CurrentStageActive, LastStageWithEngine). Also provides GetSwitchedValue<T> which is the universal ASL/Actual/Vac selector used everywhere.
IDeltaVPartExtensions (static) — IsPartDecoupler, IsPartEngine, IsPartDockingPort, etc. — all thin wrappers around TryGetModuleData<TModule, TData>. Also the Get(IDeltaVPart) list lookup.
DeltaVSituationOptions — A 3-value enum: SeaLevel, Altitude, Vaccum [sic]. The argument passed into every GetSituation* accessor.
DeltaVEngineResourcePartInfo — A tiny struct pairing a DeltaVPartInfo with a FuelMassUsed float. Appears to be legacy scaffolding; not visibly used in the current simulation path.
Class relationships at a glance
VesselDeltaVComponent
├─ DeltaVEngineStageSet
│ ├─ List<DeltaVStageInfo>
│ │ └─ List<DeltaVCalc> (_subStages)
│ └─ List<DeltaVEngineInfo>
│ └─ DeltaVPropellantInfo
│ └─ Containers: { DeltaVPartInfo → PartStageFuelMass }
└─ PartInfo: { IGGuid → DeltaVPartInfo }
└─ StageFuelMass: { stageN → PartStageFuelMass }IDeltaVPart <──────────── implemented by PartComponent (flight)
and IObjectAssemblyPart (OAB)