Vessel flies through Luna's sphere of influence with no encounter and no SOI transition

Maxi

Member
Oct 17, 2025
163
108

Game Version

2026.7.9.5018

Operating System

Windows 10

What Happened​

1784996870597.png

If i use the Transfer planner to plan an Hohmann Transfer from the default LEO starting position -> Luna the trajectory does not interact with the Luna SOI (both visuals and physics are (not) affected).

A vessel on a near-coplanar, highly eccentric Earth orbit passes straight through Luna's sphere of influence. The flight plan never predicts an encounter, no closest approach marker appears even with Luna targeted, and no SOI transition ever happens. the vessel stays parented to Earth and keeps following its unperturbed Earth ellipse while it is deep inside Luna's SOI.

In the attached save the vessel "Rocket" (the default rocket) is 12,826 km from Luna's centre. Luna's sphere of influence is about 61,800 km, so the vessel is at roughly 21 percent of the SOI radius. Luna is set as the target, so the game draws Luna's SOI sphere in map view, and the vessel marker is clearly inside it (see screenshot).

Vessel state in the save, at game time 4d 14:17:37:
  • Parent body: Earth
  • Semi-major axis 188,940 km, eccentricity 0.965, periapsis 6,620 km, apoapsis 371,257 km
  • Distance to Luna: 12,826 km
  • Relative inclination to Luna: 0.01 degrees
The flight plan consists of a single Earth-centric patch.

There is no encounter marker, no closest approach marker, and warping forward carries the vessel through the SOI without any visible trajectory effect on the vessel from Lunas gravity.

What Was Expected​

The trajectory intersects Luna's sphere of influence, so the flight plan should show a Luna encounter patch, and the trajectory, encounters etc. should be affected.

Reproduction Steps​

  1. Load the attached save "No Encounter in Luna SOI bug" (system Sol, vessel "Rocket", Luna already set as target).
  2. Switch to Map View and follow "Rocket".
  3. Observe that the vessel marker sits inside Luna's drawn sphere of influence..
  4. Observe the flight plan: one Earth-centric patch, no Luna encounter marker, no closest approach marker.
  5. Unpause and let time run: the vessel continues along the Earth ellipse through Luna's SOI as if Luna just was not there.

Reproduction Rate​

Encountered independently in several earlier sessions on comparable near-coplanar transfers.

Additional Info​

  • Save File: "No Encounter in Luna SOI bug" (attached).
  • Mods: Reproduced with only Core enabled. No mods.
 

Attachments

Upvote 1
I just played a bit more and it seems to only (or at least more often) happen on near-coplanar transfers so far
 
Last edited:
I just found something that might be the same bug:

1. Just start the default game (no mods loaded)
2. plan Hohmann transfer Rocket (in LEO) -> Luna
3. The autoburn does burn just a few m/s too short (maybe because of finite burn loss etc.). I believe that this is because some parts of the plan/burn are impulsive while the burn is not.
4. I therefore delete the burn that was auto-executed a bit short and try to setup an correction burn. THIS TIME the Trajectory <-> Luna SOI interaction does not work at all even tho i am clearly inside the Luna SOI.

 
I dug into this and I think I have the root cause, plus a fix that I verified against the shipped build.

I believe that the root cause is the encounter-candidate pre-filter added in revision 4993

Revision 4993
  • Replaced the flat SOI-size cutoff for automatic encounter detection with an orbital-geometry check; siblings are now excluded only when their radius band can't overlap ours or when an approximate MOID (evaluated at the two mutual orbital nodes) puts them well beyond their own sphere of influence,
so small moons like Phobos and Deimos are no longer silently skipped just for having a small SOI.
  • Fixed a radius-band gap where bodies orbiting entirely inside our own periapsis weren't excluded from candidacy either.
  • Removed the now-unused FlightPlan.FAST_SOI_PATCH_SIZE constant.
JPLRepoRocketwerkz • 23.07.2026 06:43

That change replaced the flat SOI-size cutoff with an approximate MOID "evaluated at the two mutual orbital nodes" (PatchedConic.OrbitsCanApproach). The problem is that it takes both radii at the same direction from the focus, so the value it computes is the distance between two concrete points, i.e. an upper bound on the true minimum distance between the two orbits.
Rejecting a candidate because an upper bound exceeds a threshold should not work a big gap at the nodes says nothing about how close the orbits get anywhere else.

Sampling the nodes is only near-tight while the two planes are separated by more than the margin away from the nodes, which needs sin(relative inclination) * bandInnerRadius > 4 * SOI.
For a vessel and Luna that works out to roughly 40 to 44 degrees of relative inclination. Every ordinary Earth-to-Luna transfer is far below that, so the node samples carry no information and Luna gets dropped from candidacy entirely.

The intended escape hatch never fires, because the only degeneracy guard is an IsExactlyZero() test on the normalized cross product of the two orbit normals, i.e. bit-exact coplanarity.

In the attached save:


Vessel: a = 187,538.6 km, e = 0.964698, Pe 6,620.5 km, Ap 368,456.7 km. Luna: Pe 355,215.4 km, Ap 406,511.2 km, SOI 61,853.6 km. Relative inclination 0.0056 deg.

Code:
+node: vessel nu = 120.85 deg r =  25,738.0 km | Luna r = 369,362.8 km | |dr| = 343,624.8 km
-node: vessel nu = 300.85 deg r =   8,702.7 km | Luna r = 389,440.9 km | |dr| = 380,738.1 km
min 343,624.8 km  >  4 * SOI = 247,414.3 km   ->  Luna rejected

The two orbits actually pass within 60 km of each other, and the vessel physically flies to 12,826 km of Luna. The test is wrong by about 343,000 km.

Sweeping the node-line orientation over the full circle for this orbit pair, only 26.6% of orientations pass, so roughly three quarters of near-coplanar Earth-to-Luna transfers on an ellipse like this get silently dropped. Hence why i earlier wrote "more often on near-coplanar transfers".


Since OrbitsCanApproach is a pure function of two fixed conics, the verdict is constant in time, so the vessel was excluded for the whole coast. And because PatchedConic.ScanForEncounters uses the same predicate with no filter, the re-scan cannot catch it either. PhysicsStates.CheckSoiTransitions does implement a correct geometric proximity test, but it is only reached from the constrained step and from the Maneuvering/Rolling/Sailing/Dragging branch, never "off the rails", so a coasting vessel has no backstop.

Everything downstream of the filter is fine. If I bypass the candidate filter and hand the same orbit to the encounter solver from before SOI entry, it finds the encounter and builds the right patch chain: Earth -> Encounter at t=328,008 s -> Luna -> Escape at t=457,635 s -> Earth.

Proposed fix

C#:
private static bool OrbitsCanApproach(Orbit orbit, Orbit siblingOrbit, double siblingSoi)

{

    double margin = siblingSoi * ENCOUNTER_MOID_SOI_MARGIN;


    // GetRadiusAt is p / (1 + e cos nu) with no reachability check, so past a hyperbola's

    // asymptote it returns a negative radius, which can only inflate the sampled gap.

    if (orbit.IsUnbound() || siblingOrbit.IsUnbound())

        return true;


    double3 nodeAxis = double3.Cross(siblingOrbit.GetOrbitNormalCci(), orbit.GetOrbitNormalCci());

    double sinRelativeInclination = nodeAxis.Length();


    // An approach can only happen at radii both orbits reach, and the out-of-plane separation

    // r * sin(relativeInclination) is smallest at the inner edge of that shared band.

    // If even there the planes are within the margin, a close approach can occur at any true anomaly and

    // sampling the two nodes proves nothing.

    double bandInnerRadius = Math.Max(orbit.Periapsis, siblingOrbit.Periapsis);

    if (!(sinRelativeInclination * bandInnerRadius > margin))

        return true;


    double3 node = nodeAxis / sinRelativeInclination;

    double atNode = Math.Abs(orbit.GetRadiusAt(orbit.GetTrueAnomaly(node))

                             - siblingOrbit.GetRadiusAt(siblingOrbit.GetTrueAnomaly(node)));

    double3 antiNode = -node;

    double atAntiNode = Math.Abs(orbit.GetRadiusAt(orbit.GetTrueAnomaly(antiNode))

                                 - siblingOrbit.GetRadiusAt(siblingOrbit.GetTrueAnomaly(antiNode)));

    return Math.Min(atNode, atAntiNode) <= margin;

}

For this save the guard evaluates 9.848e-5 * 355,215 km = 35.0 km against a 247,414 km margin, so Luna is kept. Both degenerate directions are covered, prograde coplanar and retrograde coplanar near 180 deg, and both are correct to keep since those orbits genuinely intersect.

This keeps the intent of 4993 where it matters, because the guard scales with SOI: Phobos (SOI override 30 km) re-enables the node test above about 0.74 deg of relative inclination and Deimos above about 0.08 deg, so small moons keep being filtered.
For Luna-class bodies, where the SOI is a large fraction of the orbit radius, the prune ends up effectively disabled below ~44 deg and candidacy falls back to the radial-band test, which is the right fallback since coplanar orbits with overlapping bands genuinely do intersect.

Being honest: This is A fix, not sure if it is "the best" fix even with the guard, the node radii are compared at a single point rather than across the node window, so residual false negatives are still possible for eccentric orbits at moderate inclination.
A structurally correct version could maybe compare radius ranges over asin(margin / (periapsis * sin i_rel)) around each node.

I patched exactly that one method over the shipped 2026.7.9.5018 build and re-ran the ordinary planner path on the attached save's orbit. IsEncounterCandidate(Luna) flips to true and the plan comes back as the correct three patches, Earth -> Luna -> Earth, while a control ellipse with apoapsis 100,000 km is still rejected.

Three related things I noticed (but did not test via an patch such as the main bug)


  1. PatchedConic.IsEncounterCandidate has two bare radius comparisons with no SOI margin: sibling.Orbit.Periapsis > maxReachableRadius and sibling.Orbit.Apoapsis < orbit.Periapsis. The first drops any vessel with apoapsis in 287,176 to 355,215 km for Luna, a 68,039 km band where Luna's SOI genuinely reaches down, and it sits outside the sibling != encounterFilter block so targeting Luna does not bypass it. That one is not new, it goes back to at least 2026.7.3.4826, but it is my prime suspect for the follow-up case where an underperforming transfer burn left a lower apoapsis. Both probably want the same margin ApPeIntersects already uses.
  2. Once a vessel is inside the SOI the encounter can never be re-acquired, even with the candidate filter fully out of the way. I measured at 1 h after SOI entry (58,751 km from Luna, still approaching) and at the save epoch (12,826 km, receding) and the solver returns nothing in both cases. FindSoiBoundary only searches for a |r| = SOI crossing between the patch start and the closest approach and clamps its iterate into that interval, so from inside the sphere there is no root; past closest approach the candidate is discarded even earlier for being in the past. Since the SOI transition is driven purely by the flight plan, the ship just coasts through. That is why setting up a correction burn from inside the SOI still ignores Luna. Practical consequence: fixing the filter will not repair an already-affected save, it needs a fresh transfer.
  3. When zero candidates survive, FlightPlan.CalculatePatch leaves expiryGameTime at positive infinity, FoldEventExpiry skips it because it is not finite, EventsVerifiedUntil stays infinite, and VehicleUpdateTask.TickPlanVerification therefore never calls ScanForEncounters. So the revision 4848 rolling re-scan is switched off by exactly the failure it exists to catch. Clamping a bound final patch's expiry to startTime + Orbit.Period would restore it.
 
  • Like
Reactions: Kiwi Shark