Launching into Orbit#

This tutorial launches the game’s Rocket from Cape Canaveral into a circular orbit 200 km up. The script flies the ascent, stages when the first stage runs out of propellant, and has the flight computer execute the circularization burn.

The complete script is launch_into_orbit.py.

The Ascent Profile#

Earth’s atmosphere is deep, so the rocket climbs most of the way through it before it flies level. The gravity turn starts at 1 km and ends at 150 km, and the target apoapsis is above the top of the atmosphere:

TURN_START_ALTITUDE = 1_000
TURN_END_ALTITUDE = 150_000
TARGET_ALTITUDE = 200_000

Launching#

We launch the vehicle on the pad, point it straight up and light the first stage:

vehicle = ksa.launch_vehicle("Rocket", "CCSFS LC-39A")
auto_pilot.target_pitch_and_heading(90, 90)
auto_pilot.engage()
control.throttle = 1
control.activate_next_sequence()
control.engine_on = True

The Gravity Turn#

The loop pitches the rocket over toward the east as it climbs, until the apoapsis reaches the target. The pitch follows a square root of the altitude, so the rocket turns fastest low down:

while apoapsis() < TARGET_ALTITUDE:
    # Pitch over gradually from vertical to horizontal, heading east
    if altitude() > TURN_START_ALTITUDE:
        fraction = (altitude() - TURN_START_ALTITUDE) / (
            TURN_END_ALTITUDE - TURN_START_ALTITUDE
        )
        auto_pilot.target_pitch = 90 * (1 - math.sqrt(min(fraction, 1)))

    # Separate the first stage and light the second once the first runs dry
    if available_thrust() == 0:
        stage()

    time.sleep(0.1)

print("Target apoapsis reached")
control.engine_on = False

A stage that has run dry has no thrust available. Staging activates the next two sequences, the decoupler that drops the spent stage and the engine of the next one:

def stage():
    """Separate the spent stage, and light the next one."""
    print("Staging")
    control.activate_next_sequence()
    control.activate_next_sequence()


Circularizing#

An event waits for the rocket to leave the atmosphere. The client computes the atmosphere’s altitude once, and the server compares the rocket’s altitude with it every frame:

atmosphere_altitude = body.atmosphere_radius - body.radius
above_atmosphere = conn.add_event(lambda: flight.mean_altitude > atmosphere_altitude)
with above_atmosphere.condition:
    above_atmosphere.wait()

Server Side Functions covers events, and the other server side functions. Once the rocket is out of the atmosphere, we plan a burn at the apoapsis that makes the orbit circular, as in Executing a Burn, and warp to it:

mu = body.gravitational_parameter
r = orbit.apoapsis
v1 = math.sqrt(mu * (2 / r - 1 / orbit.semi_major_axis))
v2 = math.sqrt(mu / r)
burn = vehicle.add_burn(ksa.ut + orbit.time_to_apoapsis, prograde=v2 - v1)
ksa.warp_to(burn.ignition_time - 30)

The flight computer turns the auto-burn off when the burn is complete, or when the engines run out of propellant. An event waits for either. The first stage can run dry during the burn, so we then stage and continue the burn:

def execute_burn(burn):
    """Execute a burn with the flight computer, staging when a stage runs dry."""
    while True:
        auto_pilot.auto_burn = True
        finished = conn.add_event(lambda: not auto_pilot.auto_burn)
        with finished.condition:
            finished.wait()
        finished.remove()
        # The flight computer stops the burn when the engines run out of propellant
        if burn.remaining_delta_v < 1:
            break
        stage()