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 by SetLowerStagesDecoupleStage.

  • Disabled staging (IsActiveInStagingProp = false): the part joins the parent's stage instead.

  • Non-engine parts with IsActiveInStaging = false have their ActivationStage reassigned 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:

  1. If already in the dict: refresh DryMass, ActivationStage, DecoupleStage.

  2. Otherwise: create a new DeltaVPartInfo (which calls CalculateStagingValues).

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.Parts in OAB or Vessel.Contains in 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 − DecoupledMass
SortEnginesByResource()              // 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, DeltaVActual

ISP 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

_calcsDirty

Full RunCalculations() (all stages)

Flight, engine firing

Full recalc at 1 Hz

Flight, coasting

Full recalc at 10 Hz

Flight, always

Lightweight RunFlightSceneCalculations() at 5 Hz — reruns only the active stage with fresh mass; other stages do a cheap pressure-based lerp (CalcLerpDeltaV)

OAB, situation change

RunSimulateStage() for the requested stage only

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

Vac

ASL

Actual

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) — reads atmosphereCurve at the relevant pressure; in flight, substitutes the live Engine.RealISPValue when the engine is actually ignited.

  • CalculateThrustVector(situation) — calls CalcThrustActual, then scales the engine's world-space ThrustDirRelativePartWorldSpace direction by each thrust scalar to produce the three ThrustVector* 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 populate Containers (the DeltaVPartInfo → PartStageFuelMass dict of tanks this engine can pull from). Skips parts already decoupled at this stage.

  • AmountAvailable(stage) — sums stored units minus already-consumed units across all Containers, subtracts SharedAmount (this engine's share pre-reserved by ProcessEngineSharedResources), then returns the limiting ingredient's units. IntakeAir is excluded from the limit.

  • ConsumeUnits(units) — simulates fuel draw by writing to PartStageFuelMass.Resources.UnitsConsumed, first-come-first-served across containers. This is the mechanism that carries forward depletion across sub-stages.

  • ConsumeSharedUnits(resourceID, units) — accumulates units into SharedAmount so shared-tank consumption is apportioned by ProcessEngineSharedResources.

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)