diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
index 823fafd7..c22f8dfe 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
@@ -73,6 +73,7 @@ import ru.dbotthepony.kstarbound.util.HashTableInterner
 import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
 import ru.dbotthepony.kstarbound.util.random.nextRange
 import ru.dbotthepony.kstarbound.util.random.random
+import ru.dbotthepony.kstarbound.world.SystemWorldLocation
 import ru.dbotthepony.kstarbound.world.physics.Poly
 import java.io.*
 import java.lang.ref.Cleaner
@@ -368,6 +369,8 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
 		registerTypeAdapterFactory(BiomePlacementItemType.DEFINITION_ADAPTER)
 		registerTypeAdapterFactory(BiomePlaceables.Item.Companion)
 
+		registerTypeAdapterFactory(SystemWorldLocation.ADAPTER)
+
 		// register companion first, so it has lesser priority than dispatching adapter
 		registerTypeAdapterFactory(VisitableWorldParametersType.Companion)
 		registerTypeAdapterFactory(VisitableWorldParametersType.ADAPTER)
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PlayerWarping.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PlayerWarping.kt
index d542503a..56b48f28 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PlayerWarping.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PlayerWarping.kt
@@ -5,6 +5,7 @@ import com.google.gson.TypeAdapter
 import com.google.gson.annotations.JsonAdapter
 import com.google.gson.stream.JsonReader
 import com.google.gson.stream.JsonWriter
+import kotlinx.coroutines.future.await
 import ru.dbotthepony.kommons.io.StreamCodec
 import ru.dbotthepony.kommons.io.readUUID
 import ru.dbotthepony.kstarbound.io.readVector2d
@@ -13,16 +14,20 @@ import ru.dbotthepony.kommons.io.writeBinaryString
 import ru.dbotthepony.kommons.io.writeStruct2d
 import ru.dbotthepony.kommons.io.writeStruct2f
 import ru.dbotthepony.kommons.io.writeUUID
+import ru.dbotthepony.kstarbound.Globals
 import ru.dbotthepony.kstarbound.math.vector.Vector2d
 import ru.dbotthepony.kstarbound.io.readInternedString
 import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
+import ru.dbotthepony.kstarbound.math.AABB
 import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
 import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
 import ru.dbotthepony.kstarbound.server.ServerConnection
+import ru.dbotthepony.kstarbound.server.world.ServerChunk
 import ru.dbotthepony.kstarbound.server.world.ServerWorld
 import java.io.DataInputStream
 import java.io.DataOutputStream
 import java.util.UUID
+import kotlin.math.PI
 import kotlin.math.roundToInt
 
 // original game has MVariant here
@@ -35,14 +40,14 @@ import kotlin.math.roundToInt
 @JsonAdapter(SpawnTarget.Adapter::class)
 sealed class SpawnTarget {
 	abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
-	abstract fun resolve(world: ServerWorld): Vector2d?
+	abstract suspend fun resolve(world: ServerWorld): Vector2d?
 
 	object Whatever : SpawnTarget() {
 		override fun write(stream: DataOutputStream, isLegacy: Boolean) {
 			stream.writeByte(0)
 		}
 
-		override fun resolve(world: ServerWorld): Vector2d {
+		override suspend fun resolve(world: ServerWorld): Vector2d {
 			return world.playerSpawnPosition
 		}
 
@@ -57,7 +62,7 @@ sealed class SpawnTarget {
 			stream.writeBinaryString(id)
 		}
 
-		override fun resolve(world: ServerWorld): Vector2d? {
+		override suspend fun resolve(world: ServerWorld): Vector2d? {
 			return world.entities.values.firstOrNull { it.uniqueID.get().orNull() == id }?.position
 		}
 
@@ -81,7 +86,7 @@ sealed class SpawnTarget {
 			return "${position.x.roundToInt()}.${position.y.roundToInt()}"
 		}
 
-		override fun resolve(world: ServerWorld): Vector2d {
+		override suspend fun resolve(world: ServerWorld): Vector2d {
 			return position
 		}
 	}
@@ -101,8 +106,26 @@ sealed class SpawnTarget {
 			return position.roundToInt().toString()
 		}
 
-		override fun resolve(world: ServerWorld): Vector2d {
-			TODO("Not yet implemented")
+		override suspend fun resolve(world: ServerWorld): Vector2d {
+			val basePos = Vector2d(position, world.geometry.size.y * 0.5)
+			val tickets = ArrayList<ServerChunk.ITicket>()
+
+			try {
+				for (i in 0 until Globals.worldServer.playerSpaceStartMaximumTries) {
+					val testPos = world.geometry.wrap(basePos + Vector2d.angle(world.random.nextDouble(PI * 2.0), i * Globals.worldServer.playerSpaceStartDistanceIncrement))
+					val testRect = AABB.withSide(testPos, Globals.worldServer.playerSpaceStartRegionSize.x, Globals.worldServer.playerSpaceStartRegionSize.y)
+
+					tickets.addAll(world.permanentChunkTicket(testRect).await())
+					tickets.forEach { it.chunk.await() }
+
+					if (!world.anyCellSatisfies(testRect) { x, y, cell -> cell.foreground.material.value.collisionKind.isSolidCollision })
+						return testPos
+				}
+
+				return basePos
+			} finally {
+				tickets.forEach { it.cancel() }
+			}
 		}
 	}
 
@@ -134,7 +157,8 @@ sealed class SpawnTarget {
 			val matchPos = position.matchEntire(value)
 
 			if (matchPos != null) {
-				return Position(Vector2d(matchPos.groups[0]!!.value.toDouble(), matchPos.groups[0]!!.value.toDouble()))
+				val split = matchPos.groups[0]!!.value.split('.')
+				return Position(Vector2d(split[0].toDouble(), split[1].toDouble()))
 			}
 
 			val matchX = positionX.matchEntire(value)
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/UniverseServerConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/UniverseServerConfig.kt
index 68373311..ba995a28 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/UniverseServerConfig.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/UniverseServerConfig.kt
@@ -11,6 +11,7 @@ import java.util.function.Predicate
 data class UniverseServerConfig(
 	// in milliseconds
 	val clockUpdatePacketInterval: Long = 500L,
+	val universeStorageInterval: Long = 10000L,
 	val findStarterWorldParameters: StarterWorld,
 	val queuedFlightWaitTime: Double = 0.0,
 
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/WorldID.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/WorldID.kt
index 53f9c85a..ecf22450 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/WorldID.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/WorldID.kt
@@ -96,12 +96,10 @@ sealed class WorldID {
 			if (value.isBlank())
 				return Limbo
 
-			val parts = value.split(':')
-
-			return when (val type = parts[0].lowercase()) {
+			return when (val type = value.substringBefore(':').lowercase()) {
 				"nowhere" -> Limbo
 				"instanceworld" -> {
-					val rest = parts[1].split(':')
+					val rest = value.substringAfter(':').split(':')
 
 					if (rest.isEmpty() || rest.size > 3) {
 						throw IllegalArgumentException("Malformed InstanceWorld string: $value")
@@ -125,8 +123,8 @@ sealed class WorldID {
 					Instance(name, uuid, threatLevel)
 				}
 
-				"celestialworld" -> Celestial(UniversePos.parse(parts[1]))
-				"clientshipworld" -> ShipWorld(uuidFromStarboundString(parts[1]))
+				"celestialworld" -> Celestial(UniversePos.parse(value.substringAfter(':')))
+				"clientshipworld" -> ShipWorld(uuidFromStarboundString(value.substringAfter(':')))
 				else -> throw IllegalArgumentException("Invalid WorldID type: $type (input: $value)")
 			}
 		}
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/WorldServerConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/WorldServerConfig.kt
index e4e8f9f0..cb4c1aa4 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/WorldServerConfig.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/WorldServerConfig.kt
@@ -4,6 +4,9 @@ import ru.dbotthepony.kstarbound.math.vector.Vector2d
 import ru.dbotthepony.kstarbound.math.vector.Vector2i
 
 class WorldServerConfig(
+	val playerSpaceStartRegionSize: Vector2d,
+	val playerSpaceStartDistanceIncrement: Double,
+	val playerSpaceStartMaximumTries: Int,
 	val playerStartRegionMaximumTries: Int = 1,
 	val playerStartRegionMaximumVerticalSearch: Int = 1,
 	val playerStartRegionSize: Vector2d,
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonReader.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonReader.kt
index 938f71a2..618960a7 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonReader.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonReader.kt
@@ -9,6 +9,7 @@ import com.google.gson.JsonPrimitive
 import com.google.gson.JsonSyntaxException
 import com.google.gson.stream.JsonReader
 import com.google.gson.stream.JsonToken
+import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
 import ru.dbotthepony.kommons.io.readBinaryString
 import ru.dbotthepony.kommons.io.readSignedVarLong
 import ru.dbotthepony.kommons.io.readString
@@ -21,6 +22,10 @@ import java.io.InputStream
 import java.io.Reader
 import java.util.LinkedList
 
+fun ByteArray.readJsonElement(): JsonElement = DataInputStream(FastByteArrayInputStream(this)).readJsonElement()
+fun ByteArray.readJsonObject(): JsonObject = DataInputStream(FastByteArrayInputStream(this)).readJsonObject()
+fun ByteArray.readJsonArray(): JsonArray = DataInputStream(FastByteArrayInputStream(this)).readJsonArray()
+
 /**
  * Позволяет читать двоичный JSON прямиком в [JsonElement]
  */
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonWriter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonWriter.kt
index c6cf422f..12d065ec 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonWriter.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonWriter.kt
@@ -5,12 +5,19 @@ import com.google.gson.JsonElement
 import com.google.gson.JsonNull
 import com.google.gson.JsonObject
 import com.google.gson.JsonPrimitive
+import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
+import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
 import ru.dbotthepony.kommons.io.writeBinaryString
 import ru.dbotthepony.kommons.io.writeSignedVarLong
 import ru.dbotthepony.kommons.io.writeVarInt
+import java.io.DataInputStream
 import java.io.DataOutputStream
 import kotlin.math.absoluteValue
 
+fun JsonElement.writeJsonElement(): ByteArray = FastByteArrayOutputStream().let { DataOutputStream(it).writeJsonElement(this); it.array.copyOf(it.length) }
+fun JsonObject.writeJsonObject(): ByteArray = FastByteArrayOutputStream().let { DataOutputStream(it).writeJsonObject(this); it.array.copyOf(it.length) }
+fun JsonArray.writeJsonArray(): ByteArray = FastByteArrayOutputStream().let { DataOutputStream(it).writeJsonArray(this); it.array.copyOf(it.length) }
+
 fun DataOutputStream.writeJsonElement(value: JsonElement) {
 	when (value) {
 		is JsonNull -> write(BinaryJsonReader.TYPE_NULL)
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt
index c4f86dda..59d3b8b0 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt
@@ -15,6 +15,7 @@ import ru.dbotthepony.kommons.util.KOptional
 import ru.dbotthepony.kstarbound.math.vector.Vector2i
 import ru.dbotthepony.kstarbound.math.vector.Vector3i
 import ru.dbotthepony.kstarbound.Globals
+import ru.dbotthepony.kstarbound.Starbound
 import ru.dbotthepony.kstarbound.defs.WarpAction
 import ru.dbotthepony.kstarbound.defs.WarpAlias
 import ru.dbotthepony.kstarbound.defs.WarpMode
@@ -22,6 +23,7 @@ import ru.dbotthepony.kstarbound.defs.WorldID
 import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
 import ru.dbotthepony.kstarbound.defs.world.SkyType
 import ru.dbotthepony.kstarbound.defs.world.VisitableWorldParameters
+import ru.dbotthepony.kstarbound.json.builder.JsonFactory
 import ru.dbotthepony.kstarbound.network.Connection
 import ru.dbotthepony.kstarbound.network.ConnectionSide
 import ru.dbotthepony.kstarbound.network.ConnectionType
@@ -33,13 +35,14 @@ import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPac
 import ru.dbotthepony.kstarbound.server.world.ServerWorldTracker
 import ru.dbotthepony.kstarbound.server.world.WorldStorage
 import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
-import ru.dbotthepony.kstarbound.server.world.NativeWorldStorage
 import ru.dbotthepony.kstarbound.server.world.ServerSystemWorld
 import ru.dbotthepony.kstarbound.server.world.ServerWorld
 import ru.dbotthepony.kstarbound.world.SystemWorldLocation
 import ru.dbotthepony.kstarbound.world.UniversePos
 import java.util.HashMap
 import java.util.UUID
+import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
 import kotlin.properties.Delegates
 
 // serverside part of connection
@@ -108,15 +111,23 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 	}
 
 	private var remoteVersion = 0L
+	private var saveClientContextTask: Future<*>? = null
 
 	override fun onChannelClosed() {
 		playerEntity = null
 
+		saveClientContextTask?.cancel(false)
+		tracker?.remove("Connection channel closed")
+		tracker = null
+
+		saveClientContext()
 		super.onChannelClosed()
+
 		warpQueue.close()
 		server.channels.freeConnectionID(connectionID)
 		server.channels.connections.remove(this)
 		server.freeNickname(nickname)
+
 		systemWorld?.removeClient(this)
 		systemWorld = null
 
@@ -132,15 +143,19 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 		}
 	}
 
-	private val warpQueue = Channel<Pair<WarpAction, Boolean>>(capacity = 10)
+	private data class WarpRequest(val action: WarpAction, val deploy: Boolean, val ifFailed: WarpAction?)
+	private val warpQueue = Channel<WarpRequest>(capacity = 10)
 
 	private suspend fun warpEventLoop() {
 		while (true) {
-			var (request, deploy) = warpQueue.receive()
+			var (request, deploy, ifFailed) = warpQueue.receive()
 
 			if (request is WarpAlias)
 				request = request.remap(this)
 
+			if (ifFailed is WarpAlias)
+				ifFailed = ifFailed.remap(this)
+
 			LOGGER.info("Trying to warp ${alias()} to $request")
 
 			val resolve = request.resolve(this)
@@ -155,7 +170,12 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 					server.loadWorld(resolve).await()
 				} catch (err: Throwable) {
 					send(PlayerWarpResultPacket(false, request, false))
-					LOGGER.error("Unable to wark ${alias()} to $request", err)
+					LOGGER.error("Unable to warp ${alias()} to $request", err)
+
+					if (ifFailed != null) {
+						enqueueWarp(ifFailed)
+					}
+
 					continue
 				}
 
@@ -192,8 +212,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 
 	private var currentOrbitalWarpAction = KOptional<Pair<WarpAction, WarpMode>>()
 
-	// coordinates ship flight
-	private suspend fun shipFlightEventLoop() {
+	private suspend fun findStartingSystem(): UniversePos? {
 		shipWorld.sky.skyType = SkyType.ORBITAL
 		shipWorld.sky.startFlying(true, true)
 		var visited = 0
@@ -235,25 +254,46 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 		if (found == null) {
 			LOGGER.fatal("Unable to find starter world for $this!")
 			disconnect("Unable to find starter world")
-			return
+			return null
 		}
 
 		LOGGER.info("Found appropriate starter world at $found for ${alias()}")
+		return found
+	}
 
-		val worldPromise = server.loadSystemWorld(found.location)
+	private var systemWorldLocation: SystemWorldLocation = SystemWorldLocation.Transit
 
-		worldPromise.thenApply {
-			systemWorld = it
+	// coordinates ship flight
+	private suspend fun shipFlightEventLoop(initialLocation: Vector3i, inWorldLocation: SystemWorldLocation) {
+		val worldPromise = server.loadSystemWorld(initialLocation)
+		val loadWorld = worldPromise.await()
+		var actualInWorldLocation = inWorldLocation
 
-			if (!isConnected) {
-				it.removeClient(this)
+		var world: ServerSystemWorld
+
+		if (loadWorld == null) {
+			LOGGER.warn("Tried to put player to system world at $initialLocation, but we are unable to load it")
+			// we ended up nowhere, try to find new starter location
+			val find = findStartingSystem() ?: return
+			val tryAnother = server.loadSystemWorld(find.location).await()
+
+			if (tryAnother == null) {
+				// how?
+				disconnect("Unable to put player in system world")
+				return
+			} else {
+				actualInWorldLocation = SystemWorldLocation.Celestial(find)
+				world = tryAnother
 			}
+		} else {
+			world = loadWorld
 		}
 
-		var world = worldPromise.await()
-		var ship = world.addClient(this, location = SystemWorldLocation.Celestial(found)).await()
+		this.systemWorld = world
+		var ship = world.addClient(this, location = actualInWorldLocation).await()
 		shipWorld.sky.stopFlyingAt(ship.location.skyParameters(world))
-		shipCoordinate = found
+		shipCoordinate = UniversePos(world.location)
+		systemWorldLocation = actualInWorldLocation
 
 		run {
 			val action = ship.location.orbitalAction(world)
@@ -280,6 +320,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 				}
 
 				currentFlightJob = scope.launch {
+					systemWorldLocation = location
 					val coords = flight.await()
 					val action = coords.orbitalAction(world)
 					currentOrbitalWarpAction = action
@@ -315,24 +356,16 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 				}
 
 				LOGGER.info("${alias()} is flying to new system: ${UniversePos(system)}")
-				val newSystem = server.loadSystemWorld(system)
 
-				shipCoordinate = UniversePos(system)
 				currentOrbitalWarpAction = KOptional()
 
 				for (client in shipWorld.clients) {
 					client.client.orbitalWarpAction = KOptional()
 				}
 
-				newSystem.thenApply {
-					systemWorld = it
-
-					if (!isConnected) {
-						it.removeClient(this)
-					}
-				}
-
-				world = newSystem.await()
+				world = server.loadSystemWorld(system).await() ?: world
+				shipCoordinate = UniversePos(world.location) // update ship coordinate after we have successfully travelled to destination
+				this.systemWorld = world
 				ship = world.addClient(this).await()
 
 				val newParams = ship.location.skyParameters(world)
@@ -400,8 +433,8 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 		}
 	}
 
-	fun enqueueWarp(destination: WarpAction, deploy: Boolean = false) {
-		warpQueue.trySend(destination to deploy)
+	fun enqueueWarp(destination: WarpAction, deploy: Boolean = false, ifFailed: WarpAction? = null) {
+		warpQueue.trySend(WarpRequest(destination, deploy, ifFailed))
 	}
 
 	fun tick() {
@@ -445,12 +478,10 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 		}
 
 		isReady = false
-		tracker?.remove()
-		tracker = null
 
-		if (::shipWorld.isInitialized) {
-			shipWorld.eventLoop.shutdown()
-		}
+		tracker?.remove("Disconnect")
+		tracker = null
+		saveClientContext()
 
 		if (channel.isOpen) {
 			// say goodbye
@@ -479,6 +510,55 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 
 	private var countedTowardsPlayerCount = false
 
+	@JsonFactory
+	data class ClientContextData(
+		val shipCoordinate: Vector3i,
+		val systemLocation: SystemWorldLocation,
+		val returnWarp: WarpAction? = null
+	)
+
+	fun saveClientContext() {
+		if (server.isShutdown && !server.isSameThread())
+			return
+
+		val data = ClientContextData(shipCoordinate.location, systemWorldLocation, returnWarp)
+		server.writeClientContext(uuid!!, Starbound.gson.toJsonTree(data) as JsonObject)
+	}
+
+	private suspend fun loadDataAndDispatchEventLoops() {
+		val context = try {
+			Starbound.gson.fromJson(server.loadClientContext(uuid!!).await(), ClientContextData::class.java)
+		} catch (err: Throwable) {
+			LOGGER.warn("Exception deserializing player context for ${alias()}, considering fresh context", err)
+			null
+		}
+
+		shipUpgrades = shipUpgrades.addCapability("planetTravel")
+		shipUpgrades = shipUpgrades.addCapability("teleport")
+		shipUpgrades = shipUpgrades.copy(maxFuel = 10000, shipLevel = 3)
+
+		scope.launch { warpEventLoop() }
+
+		if (context == null) {
+			shipWorld.eventLoop.execute { shipWorld.sky.startFlying(true, true) }
+			enqueueWarp(WarpAlias.OwnShip)
+			val startingLocation = findStartingSystem() ?: return
+			scope.launch { shipFlightEventLoop(startingLocation.location, SystemWorldLocation.Celestial(startingLocation)) }
+		} else {
+			if (context.returnWarp != null) {
+				enqueueWarp(context.returnWarp, ifFailed = WarpAlias.OwnShip)
+			} else {
+				enqueueWarp(WarpAlias.OwnShip)
+			}
+
+			scope.launch { shipFlightEventLoop(context.shipCoordinate, context.systemLocation) }
+		}
+
+		saveClientContextTask = channel.eventLoop().scheduleWithFixedDelay(Runnable { saveClientContext() }, 0L, 10L, TimeUnit.SECONDS)
+
+		scope.launch { saveClientContext() }
+	}
+
 	override fun inGame() {
 		super.inGame()
 		announcedDisconnect = false
@@ -498,16 +578,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
 					shipWorld.sky.referenceClock  = server.universeClock
 					// shipWorld.sky.startFlying(true, true)
 					shipWorld.eventLoop.start()
-					enqueueWarp(WarpAlias.OwnShip)
-					shipUpgrades = shipUpgrades.addCapability("planetTravel")
-					shipUpgrades = shipUpgrades.addCapability("teleport")
-					shipUpgrades = shipUpgrades.copy(maxFuel = 10000, shipLevel = 3)
-					scope.launch { shipFlightEventLoop() }
-					scope.launch { warpEventLoop() }
-
-					if (server.channels.connections.size > 1) {
-						enqueueWarp(WarpAction.Player(server.channels.connections.first().uuid!!))
-					}
+					scope.launch { loadDataAndDispatchEventLoops() }
 				}
 			}.exceptionally {
 				LOGGER.error("Error while initializing shipworld for $this", it)
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt
index caf61573..2eb4d966 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt
@@ -1,6 +1,12 @@
 package ru.dbotthepony.kstarbound.server
 
+import com.github.benmanes.caffeine.cache.CacheLoader
+import com.github.benmanes.caffeine.cache.Caffeine
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
 import com.google.gson.JsonPrimitive
+import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
+import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
 import it.unimi.dsi.fastutil.objects.ObjectArraySet
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.SupervisorJob
@@ -10,6 +16,7 @@ import kotlinx.coroutines.future.asCompletableFuture
 import kotlinx.coroutines.future.await
 import kotlinx.coroutines.launch
 import org.apache.logging.log4j.LogManager
+import ru.dbotthepony.kommons.util.KOptional
 import ru.dbotthepony.kstarbound.math.vector.Vector3i
 import ru.dbotthepony.kstarbound.Globals
 import ru.dbotthepony.kstarbound.Starbound
@@ -18,6 +25,10 @@ import ru.dbotthepony.kstarbound.defs.world.AsteroidsWorldParameters
 import ru.dbotthepony.kstarbound.defs.world.FloatingDungeonWorldParameters
 import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters
 import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
+import ru.dbotthepony.kstarbound.json.readJsonElement
+import ru.dbotthepony.kstarbound.json.readJsonObject
+import ru.dbotthepony.kstarbound.json.writeJsonElement
+import ru.dbotthepony.kstarbound.json.writeJsonObject
 import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket
 import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
 import ru.dbotthepony.kstarbound.server.world.ServerUniverse
@@ -26,8 +37,13 @@ import ru.dbotthepony.kstarbound.server.world.ServerSystemWorld
 import ru.dbotthepony.kstarbound.server.world.WorldStorage
 import ru.dbotthepony.kstarbound.util.BlockableEventLoop
 import ru.dbotthepony.kstarbound.util.JVMClock
+import ru.dbotthepony.kstarbound.util.asStringOrNull
 import ru.dbotthepony.kstarbound.util.random.random
+import ru.dbotthepony.kstarbound.util.toStarboundString
+import ru.dbotthepony.kstarbound.util.uuidFromStarboundString
 import ru.dbotthepony.kstarbound.world.UniversePos
+import java.io.DataInputStream
+import java.io.DataOutputStream
 import java.io.File
 import java.sql.DriverManager
 import java.util.UUID
@@ -56,24 +72,90 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
 	val chat = ChatHandler(this)
 	val globalScope = CoroutineScope(Starbound.COROUTINE_EXECUTOR + SupervisorJob())
 
+	private val database = DriverManager.getConnection("jdbc:sqlite:${File(universeFolder, "universe.db").absolutePath.replace('\\', '/')}")
+
+	init {
+		database.autoCommit = false
+
+		database.createStatement().use {
+			it.execute("CREATE TABLE IF NOT EXISTS `metadata` (`key` VARCHAR NOT NULL PRIMARY KEY, `value` BLOB NOT NULL)")
+			it.execute("CREATE TABLE IF NOT EXISTS `universe_flags` (`flag` VARCHAR NOT NULL PRIMARY KEY)")
+			it.execute("CREATE TABLE IF NOT EXISTS `client_context` (`uuid` VARCHAR NOT NULL PRIMARY KEY, `data` BLOB NOT NULL)")
+		}
+	}
+
+	private val lookupMetadata = database.prepareStatement("SELECT `value` FROM `metadata` WHERE `key` = ?")
+	private val writeMetadata = database.prepareStatement("REPLACE INTO `metadata` (`key`, `value`) VALUES (?, ?)")
+	private val lookupClientContext = database.prepareStatement("SELECT `data` FROM `client_context` WHERE `uuid` = ?")
+	private val writeClientContext = database.prepareStatement("REPLACE INTO `client_context` (`uuid`, `data`) VALUES (?, ?)")
+
+	private fun getMetadata(key: String): KOptional<JsonElement> {
+		lookupMetadata.setString(1, key)
+
+		return lookupMetadata.executeQuery().use {
+			if (it.next()) {
+				KOptional(it.getBytes(1).readJsonElement())
+			} else {
+				KOptional()
+			}
+		}
+	}
+
+	private fun setMetadata(key: String, value: JsonElement) {
+		writeMetadata.setString(1, key)
+		writeMetadata.setBytes(2, value.writeJsonElement())
+		writeMetadata.execute()
+	}
+
+	fun loadClientContext(uuid: UUID): CompletableFuture<JsonObject?> {
+		return supplyAsync {
+			lookupClientContext.setString(1, uuid.toStarboundString())
+
+			lookupClientContext.executeQuery().use {
+				if (it.next()) {
+					it.getBytes(1).readJsonObject()
+				} else {
+					null
+				}
+			}
+		}
+	}
+
+	fun writeClientContext(uuid: UUID, context: JsonObject): CompletableFuture<*> {
+		return supplyAsync {
+			writeClientContext.setString(1, uuid.toStarboundString())
+			writeClientContext.setBytes(2, context.writeJsonObject())
+			writeClientContext.execute()
+		}
+	}
+
 	val settings = ServerSettings()
 	val channels = ServerChannels(this)
 	val lock = ReentrantLock()
 	var isClosed = false
 		private set
 
-	var serverUUID: UUID = UUID.randomUUID()
-		protected set
-
+	val serverUUID: UUID = uuidFromStarboundString(getMetadata("server_uuid").orElse { JsonPrimitive(UUID.randomUUID().toStarboundString()) }.asString)
 	val universeClock = JVMClock()
 
-	private val systemWorlds = HashMap<Vector3i, CompletableFuture<ServerSystemWorld>>()
-
-	private suspend fun loadSystemWorld0(location: Vector3i): ServerSystemWorld {
-		return ServerSystemWorld.create(this, location)
+	init {
+		universeClock.set(getMetadata("universe_clock").orElse { JsonPrimitive(0.0) }.asDouble)
+		setMetadata("server_uuid", JsonPrimitive(serverUUID.toStarboundString()))
+		database.commit()
 	}
 
-	fun loadSystemWorld(location: Vector3i): CompletableFuture<ServerSystemWorld> {
+	private val systemWorlds = HashMap<Vector3i, CompletableFuture<ServerSystemWorld?>>()
+
+	private suspend fun loadSystemWorld0(location: Vector3i): ServerSystemWorld? {
+		try {
+			return ServerSystemWorld.create(this, location)
+		} catch (err: Throwable) {
+			LOGGER.error("Exception loading system world at $location", err)
+			return null
+		}
+	}
+
+	fun loadSystemWorld(location: Vector3i): CompletableFuture<ServerSystemWorld?> {
 		return supplyAsync {
 			systemWorlds.computeIfAbsent(location) {
 				globalScope.async { loadSystemWorld0(location) }.asCompletableFuture()
@@ -82,7 +164,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
 	}
 
 	private suspend fun loadCelestialWorld(location: WorldID.Celestial): ServerWorld {
-		val file = File(universeFolder, location.pos.toString().replace(':', '_') + ".kworld")
+		val file = File(universeFolder, location.pos.toString().replace(':', '_') + ".db")
 		val firstTime = !file.exists()
 		val storage = LegacyWorldStorage.SQL(file)
 
@@ -197,21 +279,22 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
 		}
 	}
 
-	fun loadSystemWorld(location: UniversePos): CompletableFuture<ServerSystemWorld> {
-		return loadSystemWorld(location.location)
-	}
-
 	init {
 		scheduleAtFixedRate(Runnable {
 			channels.broadcast(UniverseTimeUpdatePacket(universeClock.time), false)
+			setMetadata("universe_clock", JsonPrimitive(universeClock.time))
 		}, Globals.universeServer.clockUpdatePacketInterval, Globals.universeServer.clockUpdatePacketInterval, TimeUnit.MILLISECONDS)
 
+		scheduleWithFixedDelay(Runnable {
+			database.commit()
+		}, Globals.universeServer.universeStorageInterval, Globals.universeServer.universeStorageInterval, TimeUnit.MILLISECONDS)
+
 		scheduleAtFixedRate(Runnable {
 			tickNormal(Starbound.TIMESTEP)
 		}, Starbound.TIMESTEP_NANOS, Starbound.TIMESTEP_NANOS, TimeUnit.NANOSECONDS)
 
 		scheduleAtFixedRate(Runnable {
-			tickSystemWorlds()
+			tickSystemWorlds(Starbound.SYSTEM_WORLD_TIMESTEP)
 		}, Starbound.SYSTEM_WORLD_TIMESTEP_NANOS, Starbound.SYSTEM_WORLD_TIMESTEP_NANOS, TimeUnit.NANOSECONDS)
 
 		isDaemon = false
@@ -250,7 +333,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
 	protected abstract fun close0()
 	protected abstract fun tick0(delta: Double)
 
-	private fun tickSystemWorlds() {
+	private fun tickSystemWorlds(delta: Double) {
 		systemWorlds.values.removeIf {
 			if (it.isCompletedExceptionally) {
 				return@removeIf true
@@ -260,15 +343,19 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
 				return@removeIf false
 			}
 
+			if (it.get() == null) {
+				return@removeIf true
+			}
+
 			scope.launch {
 				try {
-					it.get().tick(Starbound.SYSTEM_WORLD_TIMESTEP)
+					it.get()!!.tick(delta)
 				} catch (err: Throwable) {
 					LOGGER.fatal("Exception in system world $it event loop", err)
 				}
 			}
 
-			if (it.get().shouldClose()) {
+			if (it.get()!!.shouldClose()) {
 				LOGGER.info("Stopping idling ${it.get()}")
 				return@removeIf true
 			}
@@ -322,6 +409,8 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
 			it.cancel(true)
 		}
 
+		database.commit()
+		database.close()
 		universe.close()
 		close0()
 	}
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerSystemWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerSystemWorld.kt
index 4c5e5cae..506c4186 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerSystemWorld.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerSystemWorld.kt
@@ -264,6 +264,24 @@ class ServerSystemWorld : SystemWorld {
 			next = tasks.poll()
 		}
 
+		// safeguard for cases when client wasn't removed properly
+		ships.values.removeIf {
+			if (it.shouldRemove()) {
+				val packet = SystemObjectDestroyPacket(it.uuid)
+
+				ships.values.forEach { ship ->
+					if (ship !== it) {
+						ship.forget(it.uuid)
+						ship.client.send(packet)
+					}
+				}
+
+				true
+			} else {
+				false
+			}
+		}
+
 		entities.values.forEach { it.tick(delta) }
 		ships.values.forEach { it.tick(delta) }
 
@@ -384,6 +402,10 @@ class ServerSystemWorld : SystemWorld {
 			client.send(SystemWorldUpdatePacket(objects, ships))
 		}
 
+		fun shouldRemove(): Boolean {
+			return !client.isConnected
+		}
+
 		fun forget(id: UUID) {
 			netVersions.removeLong(id)
 		}
@@ -499,12 +521,19 @@ class ServerSystemWorld : SystemWorld {
 	companion object {
 		private val LOGGER = LogManager.getLogger()
 
-		suspend fun create(server: StarboundServer, location: Vector3i): ServerSystemWorld {
-			LOGGER.info("Creating new System World at $location")
-			val world = ServerSystemWorld(server, location)
-			world.spawnInitialObjects()
-			world.spawnObjects()
-			return world
+		suspend fun create(server: StarboundServer, location: Vector3i): ServerSystemWorld? {
+			val anything = server.universe.parameters(UniversePos(location))
+
+			if (anything == null) {
+				LOGGER.warn("Tried to create system world at $location, but nothing is there")
+				return null
+			} else {
+				LOGGER.info("Creating new System World at $location")
+				val world = ServerSystemWorld(server, location)
+				world.spawnInitialObjects()
+				world.spawnObjects()
+				return world
+			}
 		}
 
 		suspend fun load(server: StarboundServer, data: JsonElement): ServerSystemWorld {
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt
index 5e0121b6..a84f8801 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt
@@ -69,7 +69,7 @@ class ServerWorld private constructor(
 	val clients = CopyOnWriteArrayList<ServerWorldTracker>()
 	val shouldStopOnIdle = worldID !is WorldID.ShipWorld
 
-	private fun doAcceptClient(client: ServerConnection, action: WarpAction?) {
+	private suspend fun doAcceptClient(client: ServerConnection, action: WarpAction?) {
 		try {
 			isBusy++
 
@@ -108,7 +108,7 @@ class ServerWorld private constructor(
 		check(!eventLoop.isShutdown) { "$this is invalid" }
 
 		try {
-			val future = eventLoop.supplyAsync { doAcceptClient(player, action) }
+			val future = eventLoop.scope.async { doAcceptClient(player, action) }.asCompletableFuture()
 
 			future.exceptionally {
 				LOGGER.error("Error while accepting new player into world", it)
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt
index 6dfb20ff..0df23a6a 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt
@@ -380,8 +380,10 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
 
 			val playerEntity = client.playerEntity
 
-			if (playerEntity != null && world.worldID is WorldID.Celestial && setReturnWarp) {
+			if (playerEntity != null && (world.worldID is WorldID.Celestial || world.worldID is WorldID.ShipWorld && world.worldID.uuid == client.uuid) && setReturnWarp) {
 				client.returnWarp = WarpAction.World(world.worldID, SpawnTarget.Position(playerEntity.position))
+			} else if (setReturnWarp) {
+				client.returnWarp = null
 			}
 
 			if (nullifyVariables) {
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Clocks.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Clocks.kt
index 164c0776..10f92ae7 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Clocks.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Clocks.kt
@@ -111,6 +111,11 @@ class JVMClock : IClock {
 		baseline = nanos
 	}
 
+	fun set(seconds: Double) {
+		origin = System.nanoTime()
+		baseline = (seconds * 1_000_000_000L).toLong()
+	}
+
 	fun pause() {
 		if (!isPaused) {
 			baseline += System.nanoTime() - origin
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt
index d8594f4f..44aa886a 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt
@@ -49,7 +49,7 @@ fun uuidFromStarboundString(value: String): UUID {
 	val a = value.substring(0, 16)
 	val b = value.substring(16)
 
-	return UUID(a.toLong(16), b.toLong(16))
+	return UUID(java.lang.Long.parseUnsignedLong(a, 16), java.lang.Long.parseUnsignedLong(b, 16))
 }
 
 fun paddedNumber(number: Int, digits: Int): String {
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/SystemWorldLocation.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/SystemWorldLocation.kt
index 8fc2d3ec..b284cee3 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/SystemWorldLocation.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/SystemWorldLocation.kt
@@ -1,6 +1,10 @@
 package ru.dbotthepony.kstarbound.world
 
 import com.google.common.collect.ImmutableList
+import com.google.gson.JsonObject
+import com.google.gson.TypeAdapter
+import com.google.gson.annotations.JsonAdapter
+import com.google.gson.reflect.TypeToken
 import ru.dbotthepony.kommons.io.readUUID
 import ru.dbotthepony.kommons.io.writeUUID
 import ru.dbotthepony.kommons.util.KOptional
@@ -14,6 +18,9 @@ import ru.dbotthepony.kstarbound.defs.world.AsteroidsWorldParameters
 import ru.dbotthepony.kstarbound.defs.world.SkyParameters
 import ru.dbotthepony.kstarbound.io.readVector2d
 import ru.dbotthepony.kstarbound.io.writeStruct2d
+import ru.dbotthepony.kstarbound.json.builder.DispatchingAdapter
+import ru.dbotthepony.kstarbound.json.builder.JsonFactory
+import ru.dbotthepony.kstarbound.json.builder.JsonSingleton
 import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
 import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
 import ru.dbotthepony.kstarbound.util.random.random
@@ -30,6 +37,16 @@ sealed class SystemWorldLocation {
 	abstract suspend fun orbitalAction(system: SystemWorld): KOptional<Pair<WarpAction, WarpMode>>
 	abstract suspend fun skyParameters(system: SystemWorld): SkyParameters
 
+	enum class Type(val token: TypeToken<out SystemWorldLocation>) {
+		TRANSIT(TypeToken.get(Transit::class.java)),
+		CELESTIAL(TypeToken.get(Celestial::class.java)),
+		ORBIT(TypeToken.get(Orbit::class.java)),
+		ENTITY(TypeToken.get(Entity::class.java)),
+		POSITION(TypeToken.get(Position::class.java));
+	}
+
+	abstract val type: Type
+
 	protected suspend fun appendParameters(parameters: SkyParameters, system: SystemWorld, orbit: UniversePos): SkyParameters {
 		val planets = ArrayList<UniversePos>()
 
@@ -58,6 +75,7 @@ sealed class SystemWorldLocation {
 		return parameters
 	}
 
+	@JsonSingleton
 	object Transit : SystemWorldLocation() {
 		override fun write(stream: DataOutputStream, isLegacy: Boolean) {
 			stream.writeByte(0)
@@ -74,9 +92,13 @@ sealed class SystemWorldLocation {
 		override suspend fun skyParameters(system: SystemWorld): SkyParameters {
 			return Globals.systemWorld.emptySkyParameters
 		}
+
+		override val type: Type
+			get() = Type.TRANSIT
 	}
 
 	// orbiting around specific planet
+	@JsonFactory
 	data class Celestial(val position: UniversePos) : SystemWorldLocation() {
 		override fun write(stream: DataOutputStream, isLegacy: Boolean) {
 			stream.writeByte(1)
@@ -94,9 +116,13 @@ sealed class SystemWorldLocation {
 		override suspend fun skyParameters(system: SystemWorld): SkyParameters {
 			return SkyParameters.create(position, system.universe)
 		}
+
+		override val type: Type
+			get() = Type.CELESTIAL
 	}
 
 	// orbiting around celestial body
+	@JsonFactory
 	data class Orbit(val position: SystemWorld.Orbit) : SystemWorldLocation() {
 		override fun write(stream: DataOutputStream, isLegacy: Boolean) {
 			stream.writeByte(2)
@@ -117,8 +143,12 @@ sealed class SystemWorldLocation {
 			// (but that is still technically possible to outer-orbit a satellite)
 			return appendParameters(Globals.systemWorld.emptySkyParameters.copy(), system, position.target)
 		}
+
+		override val type: Type
+			get() = Type.ORBIT
 	}
 
+	@JsonFactory
 	data class Entity(val uuid: UUID) : SystemWorldLocation() {
 		override fun write(stream: DataOutputStream, isLegacy: Boolean) {
 			stream.writeByte(3)
@@ -150,8 +180,12 @@ sealed class SystemWorldLocation {
 
 			return sky
 		}
+
+		override val type: Type
+			get() = Type.ENTITY
 	}
 
+	@JsonFactory
 	data class Position(val position: Vector2d) : SystemWorldLocation() {
 		override fun write(stream: DataOutputStream, isLegacy: Boolean) {
 			stream.writeByte(4)
@@ -197,9 +231,13 @@ sealed class SystemWorldLocation {
 
 			return Globals.systemWorld.emptySkyParameters
 		}
+
+		override val type: Type
+			get() = Type.POSITION
 	}
 
 	companion object {
+		val ADAPTER = DispatchingAdapter("type", { type }, { token }, Type.entries)
 		val CODEC = nativeCodec(::read, SystemWorldLocation::write)
 		val LEGACY_CODEC = legacyCodec(::read, SystemWorldLocation::write)
 
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt
index 66397939..511216e4 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt
@@ -1,21 +1,16 @@
 package ru.dbotthepony.kstarbound.world
 
 import com.google.gson.Gson
-import com.google.gson.JsonObject
 import com.google.gson.JsonSyntaxException
 import com.google.gson.TypeAdapter
-import com.google.gson.TypeAdapterFactory
 import com.google.gson.annotations.JsonAdapter
-import com.google.gson.reflect.TypeToken
 import com.google.gson.stream.JsonReader
 import com.google.gson.stream.JsonToken
 import com.google.gson.stream.JsonWriter
 import ru.dbotthepony.kommons.gson.get
 import ru.dbotthepony.kstarbound.math.vector.Vector3i
-import ru.dbotthepony.kommons.gson.consumeNull
 import ru.dbotthepony.kommons.io.readVarInt
 import ru.dbotthepony.kstarbound.io.readVector3i
-import ru.dbotthepony.kommons.io.writeSignedVarInt
 import ru.dbotthepony.kommons.io.writeStruct3i
 import ru.dbotthepony.kommons.io.writeVarInt
 import ru.dbotthepony.kstarbound.Starbound
@@ -144,8 +139,8 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit:
 				val values = Starbound.ELEMENTS_ADAPTER.objects.read(`in`)!!
 				val location = values.get("location", vectors)
 				val planet = values.get("planet", 0)
-				val orbit = values.get("orbit", 0)
-				return UniversePos(location, planet, orbit)
+				val satellite = values.get("satellite", 0)
+				return UniversePos(location, planet, satellite)
 			}
 
 			if (`in`.peek() == JsonToken.STRING) {