---
title: "Recover from disconnects"
description: "Reconnect players or continue a match after its host leaves."
seo:
  noindex: true
---

{/* Copyright (c) 2026 Betide Studio. All Rights Reserved. */}

Separate a dropped connection from an intentional exit. UCIK remembers the last lobby, session, and validated address across restarts; a saved destination may no longer exist.

## Rejoin the last match

**Blueprint**

<Accordion>
  <AccordionItem title="Rejoin the last match">
<BlueprintViewer title="Rejoin the last match" src="/blueprints/crossplay-rejoin-last-match.txt" />
  </AccordionItem>
  <AccordionItem title="Clear a match after leaving">
<BlueprintViewer title="Clear a match after leaving" src="/blueprints/crossplay-clear-last-match.txt" />
  </AccordionItem>
</Accordion>

**C++**

Game module dependencies: `UCIKCore`, `BetideCore`.

```cpp
#include "Engine/GameInstance.h"
#include "Travel/UCIKTravelSubsystem.h"
#include "UCIKCore.h"

void ForgetMatchAfterLeaving(UGameInstance &GameInstance)
{
    if (auto *Service = GameInstance.GetSubsystem<UUCIKTravelSubsystem>())
        Service->ClearLastMatch();
}

void ReconnectToLastMatch(UGameInstance &GameInstance)
{
    auto *Service = GameInstance.GetSubsystem<UUCIKTravelSubsystem>();
    if (!Service)
        return;

    if (!Service->HasLastMatch())
        return;
    Service->RejoinLastMatch(
        true, FBetideCallback<FUCIK_ConnectInfo>::CreateWeakLambda(
                  &GameInstance, [](const TBetideResult<FUCIK_ConnectInfo> &Result) {
                      if (!Result.bOk)
                      {
                          UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                          return;
                      }
                      UE_LOG(LogUCIK, Log, TEXT("Rejoin destination resolved"));
                  }));
}
```

Rejoin tries the last lobby, then session, then address. If it fails, return to a usable menu. Call **Clear Last Crossplay Match** after the player deliberately leaves; it clears memory and disk.

## Host migration

Choose **Host Migration Mode** in **Betide → Crossplay**:

| Mode | Behavior |
| --- | --- |
| Off | No recovery; Unreal returns to the default map. |
| Manual | Your game reacts to host-loss/election events and controls hosting/reconnection. |
| Seamless | UCIK elects a host, reconnects members, and restores marked actor state. |

## Manual host handover

Use a GameInstance Blueprint and set migration mode to **Manual**. Choose either the explicit election flow or **Run Crossplay Host Migration**; bind before host loss and stop watching when leaving the match. **Reconnect To Migrated Crossplay Host** succeeds when travel is issued; **On Migration Completed** confirms recovery.

**Blueprint**

<Accordion>
  <AccordionItem title="Hand over ownership">
<BlueprintViewer title="Hand over ownership" src="/blueprints/crossplay-handover-host.txt" />
  </AccordionItem>
  <AccordionItem title="Handle host election explicitly">
<BlueprintViewer title="Handle host election explicitly" src="/blueprints/crossplay-manual-host-migration.txt" />
  </AccordionItem>
  <AccordionItem title="Use the default migration flow">
<BlueprintViewer title="Use the default migration flow" src="/blueprints/crossplay-run-host-migration.txt" />
  </AccordionItem>
</Accordion>

**C++**

Game module dependencies: `UCIKCore`, `BetideCore`.

<Accordion>
  <AccordionItem title="Hand over ownership">

```cpp
#include "Engine/GameInstance.h"
#include "HostMigration/UCIKHostMigrationSubsystem.h"
#include "UCIKCore.h"

void HandOverToMember(UGameInstance &GameInstance, const FUCIK_PlayerHandle &PreferredMember)
{
    auto *Migration = GameInstance.GetSubsystem<UUCIKHostMigrationSubsystem>();
    if (!Migration)
        return;
    Migration->HandOverHost(
        PreferredMember,
        FBetideCallback<FUCIK_PlayerHandle>::CreateWeakLambda(
            &GameInstance, [](const TBetideResult<FUCIK_PlayerHandle> &Result) {
                if (!Result.bOk)
                {
                    UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                    return;
                }
                UE_LOG(LogUCIK, Log, TEXT("New owner: %s"), *Result.GetValue().ToString());
            }));
}
```

  </AccordionItem>
  <AccordionItem title="Handle host election">

Store `FMigrationBindings` on your GameInstance. Choose the default flow or the explicit host/wait/reconnect sequence with `bUseDefaultFlow`.

```cpp
#include "Engine/GameInstance.h"
#include "HostMigration/UCIKHostMigrationSubsystem.h"
#include "UCIKCore.h"

struct FMigrationBindings
{
    FDelegateHandle Elected;
    FDelegateHandle Completed;
    FDelegateHandle Failed;
};

void StopWatchingHostElection(UGameInstance &GameInstance, FMigrationBindings &Bindings)
{
    if (auto *Migration = GameInstance.GetSubsystem<UUCIKHostMigrationSubsystem>())
    {
        Migration->OnHostElectedNative.Remove(Bindings.Elected);
        Migration->OnMigrationCompletedNative.Remove(Bindings.Completed);
        Migration->OnMigrationFailedNative.Remove(Bindings.Failed);
    }
    Bindings = {};
}

void WatchHostElection(UGameInstance &GameInstance, FMigrationBindings &Bindings,
                       bool bUseDefaultFlow)
{
    StopWatchingHostElection(GameInstance, Bindings);
    auto *Migration = GameInstance.GetSubsystem<UUCIKHostMigrationSubsystem>();
    if (!Migration)
        return;
    const TWeakObjectPtr<UUCIKHostMigrationSubsystem> WeakMigration(Migration);
    Bindings.Completed = Migration->OnMigrationCompletedNative.AddWeakLambda(
        &GameInstance, [WeakMigration](const FUCIK_MigrationContext &) {
            if (WeakMigration.IsValid())
                UE_LOG(LogUCIK, Log, TEXT("Migration phase: %d"),
                       static_cast<int32>(WeakMigration->GetPhase()));
        });
    Bindings.Failed = Migration->OnMigrationFailedNative.AddWeakLambda(
        &GameInstance, [](const FUCIK_MigrationContext &, const FBetideError &Error) {
            UE_LOG(LogUCIK, Warning, TEXT("%s"), *Error.GetLogString());
        });
    Bindings.Elected = Migration->OnHostElectedNative.AddWeakLambda(
        &GameInstance, [WeakMigration, bUseDefaultFlow](const FUCIK_MigrationContext &Context) {
            if (!WeakMigration.IsValid())
                return;
            if (bUseDefaultFlow)
            {
                WeakMigration->RunMigration();
                return;
            }
            if (Context.bLocalIsElected)
            {
                WeakMigration->BecomeHost(
                    Context.MapName,
                    FBetideCallback<FUCIK_ConnectInfo>::CreateWeakLambda(
                        WeakMigration.Get(), [](const TBetideResult<FUCIK_ConnectInfo> &Result) {
                            if (!Result.bOk)
                                UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                            else
                                UE_LOG(LogUCIK, Log, TEXT("New host ready"));
                        }));
                return;
            }
            WeakMigration->WaitForHost(
                30.0f,
                FBetideCallback<FString>::CreateWeakLambda(
                    WeakMigration.Get(), [WeakMigration](const TBetideResult<FString> &Result) {
                        if (!WeakMigration.IsValid())
                            return;
                        if (!Result.bOk)
                        {
                            UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                            return;
                        }
                        WeakMigration->ReconnectToHost(FBetideCallback<bool>::CreateWeakLambda(
                            WeakMigration.Get(), [](const TBetideResult<bool> &Reconnect) {
                                if (!Reconnect.bOk)
                                    UE_LOG(LogUCIK, Warning, TEXT("%s"),
                                           *Reconnect.Error.GetLogString());
                                else
                                    UE_LOG(LogUCIK, Log, TEXT("Reconnect travel requested"));
                            }));
                    }));
        });
}
```

  </AccordionItem>
</Accordion>

For **Seamless**, add **UCIK Migration State Component** to actors that must survive. Mark custom state as **SaveGame**. The component restores enabled transforms, velocity, SaveGame properties, and references between marked actors; it is not a copy of every runtime object.

<Accordion>
  <AccordionItem title="Migration limits and testing">

Migration uses a lobby/session this process actually joined, not just a record loaded from disk. Provider-native owner election is used where available; otherwise UCIK ranks peers. Direct-IP peers need reachable addresses registered through **Register Crossplay Migration Peer**.

Only the elected host publishes the new ready state and migration epoch. Do not rewrite `UCIK_` host/connection attributes from members. Test both a graceful handover and a killed host process, including restored pawns, timers, inventory, and your failure screen.

  </AccordionItem>
</Accordion>
