Questlight Studio
Docs / Co-op and online

23. Co-op and online

In short: RPG Saga Engine lets two to four players play one game together: one player hosts, friends find or are invited to the session, and everyone explores, talks and fights in the host's world. You do not write networking code. Your own systems go online by declaring their state and their actions, the same way they already do for saving.

23.1 Basics

23.1.1 The two layers

Co-op is built from two layers that you can use separately.

LayerModuleWhat it does
Online sessionsRSEOnlineGetting players into the same game: host, find, join, invite, leave, reconnect, clear error messages, a lobby screen. Works on a local network (LAN), on Steam and on Epic Online Services (EOS) behind one interface.
Co-opRSENetWhat the players share once they are in: the party, the inventory, the story, quests, the map, the clock and live battles. Every player drives a party member.

Both are optional. They load with the plugin and switch on only when the game runs a network session. Single player never pays for them, and it never notices the co-op rules. You can turn the whole thing off with the Online and Co-op feature (Project Settings → RPG Saga Engine → Features).

23.1.2 The model in one minute

  • The host is the authority. The host's machine runs the game logic: encounters, NPCs, quests, the clock. Guests show the host's state and send their actions to the host.
  • Actions are commands. A player's action goes through the command bus (Submit Command). In single player the bus applies it at once. On a guest it travels to the host, which checks it, applies it and sends the result back. One code path serves every case.
  • State is replicated by section. The state your systems already save (party, story, quests, world, map, time) is also what the host sends to guests. Each save section is sent, compressed, when it changes. A player who joins late receives everything once.
  • Players drive party members. A joining player takes a party member from the companion AI, the character chosen in the lobby if it is free. When a player leaves, the companion AI takes the member back at once. A player who comes back gets the same slot and, if it is still free, the same member.
  • World actors (explorers, companions, roaming enemies, NPCs) are ordinary replicated Unreal actors. Their logic runs on the host only.

The maximum is four players, host included. The session is a listen server: the host plays too, and no dedicated server is needed.

23.1.3 Try it on a local network in two minutes

LAN needs no account and no setup.

  1. Start the game twice as standalone games (Play → Standalone Game, twice), or on two PCs on the same network. Use a map with an exploration game mode. The plugin's example map works.
  2. In the first window open the console and type RSE.Online.Host. The log shows host listening on ... ok.
  3. In the second window type RSE.Online.AutoJoin. It searches until it finds the host and joins.
  4. The guest appears as a party member that the host's companion AI was driving. Walk around in both windows.
  5. RSE.Online.Status lists the session and the players. RSE.Net.Status lists the players, their members and positions, the state sections and the traffic. RSE.Net.Stats 10 measures the bandwidth for ten seconds.

For a screen instead of commands, RSE.Online.Lobby opens the built-in lobby: Host, Find, a list of sessions to join, the players and your character choice. With the default settings the main menu also offers Host Game and Join Game, and the in-game menu offers Host co-op game, Invite friends, Join a game and Leave co-op game.

23.1.4 The co-op rules you get for free

The group plays the host's story together. These rules are built in. Each has settings and a replaceable policy class, so you change a rule without touching the framework.

  • Dialogs. The one who starts a dialog speaks and everyone watches it. Story choices are made by the host, by the speaker or by a vote (setting in the dialog settings, or the choice node's Decider). The dialog box shows who speaks and who decides.
  • Story scenes play on every machine. Guests' input is locked, and only the host can skip (or a vote, or anyone, by the Scene Skip Rule).
  • The leash. Guests may roam up to Leash Radius from the host (or from the center of the group). Past it they get a warning, and after Leash Grace Seconds they are brought back beside the group. It is off during scenes, dialogs, battles and travels. Place an RSE Co-op Map Settings actor in a map to change the leash there.
  • Zone travel. The host's travel starts a short countdown on every screen and takes everyone along. A guest at an exit asks the host, who answers a question box.
  • Notices. Refused actions show their reason to the player who tried them. Everyone sees "the host is saving", "Player 2 joined", "the companion AI takes over".
  • Battles are live. While a session has more than one player, grid Tactics and ATB encounters are fought in Free Tactics on the map where the party stands. Each player commands the unit of the member they drive. Real-time Action encounters stay as they are. See Free Tactics (Live) and Action battles.
  • Companions and equipment. Every player may order companions the AI drives, never a member another player drives. A guest changes only the equipment and Pacts of the member they play. Both are settings.

23.2 How it works

23.2.1 Sessions

A session goes through a small state machine owned by the session subsystem:

host:    Idle -> Creating -> Hosting
search:  Idle -> Searching -> Idle
client:  Idle -> Joining -> Connected
drop:    Connected -> Reconnecting -> Joining -> Connected
end:     Leaving -> Idle

Only one operation runs at a time. A second one is refused with Busy rather than mixed up with the first. Every step has a timeout (Operation Timeout Seconds), and every failure has its own player-facing message: the service is unavailable, you are not signed in, the session is full, the host runs another version, the host refused new players during a battle, the connection was lost, and so on.

The online service is a provider: LAN, Steam, EOS, or a class of your own. The session flow, the version check, the reconnecting and the lobby are the same for all of them.

23.2.2 Commands

Every player action is a command: a small serializable struct, a validator and an applier.

  1. A widget or a Blueprint submits the command (Submit Command).
  2. On the host or in single player the bus calls the handler's Validate, then Apply, at once.
  3. On a guest the bus sends the command to the host. The host fills in the sender from the connection, never from the packet, so a client cannot pretend to be someone else. It validates, applies and returns the result.
  4. The system's own events fire as usual, and the state section it changed replicates.

Commands refer to content by id (tags, primary asset ids, soft references), never by pointer. Commands that need randomness take their seed from the game's command stream, not from the global random generator, so the host's result is reproducible.

23.2.3 State sections

A state section is one named block of serializable state, the same block the save system writes. The owner marks it changed when its state changes. The network layer then sends it when the revision grew and the bytes differ from what was last sent. A section has a scope:

ScopeSent to
Shared (default)everyone
PerPlayerits owner only (tutorial progress, for example)
HostOnlynobody (difficulty, seeds)

A change you forget to mark is still caught by a safety net, the change detection, a little later and at some cost. Mark your changes anyway.

23.2.4 Live battles

The host's machine runs the battle. Every machine shows it with its own camera, HUD and menus. The host streams the battle's numbered events and state to each guest, and a guest that falls behind catches up at a higher playback rate. A battle's start snapshot travels in confirmed chunks. When another player's unit acts, your HUD says who is choosing, then shows the action. After Turn Timeout Seconds the host may command an unanswered unit for one turn (default), or the AI plays it. A player who leaves mid-battle is replaced by the AI at once, and a player who joins watches until the battle ends.

Real-time Action battles use their own stream for co-op: the host is authoritative for damage, the guest moves its own character with client prediction, plays its swing at once and claims its hits through the command bus. Its transport budgets are in Co-op Network → Action Battle.

23.2.5 Zone travel

The host's travel is a server travel. Guests reconnect to the new map and keep their slots, behind a short loading screen. Seamless travel is not used. A guest cannot start a travel on its own.

23.3 Key classes and assets

Class / assetModuleRole
URSESessionSubsystemRSEOnlineThe one object for the session flow: host, find, join, leave, invites, lobby, state and errors. Blueprint and C++.
URSESessionProviderRSEOnlineBase of an online service. URSESessionProvider_OSS works with any Online Subsystem. URSESessionProvider_LAN, _Steam and _EOS derive from it.
URSEOnlineSettingsRSEOnlineProject Settings → RPG Saga Engine → Online.
URSELobbyWidget, URSELobbyViewModelRSEOnlineThe built-in lobby screen and the data a replacement screen reads.
URSEOnlineMenuEntryRSEOnlineAdds Host Game, Join Game and the in-game entries (a URSEMenuExtension).
URSENetSettingsRSENetProject Settings → RPG Saga Engine → Co-op Network.
ARSENetGameMode, ARSENetGameState, ARSENetPlayerStateRSENetThe exploration game mode with the co-op components built in. Use it for maps meant for co-op.
URSENetSubsystem, URSENetWorldSubsystemRSENetThe transport: commands up, results and events down. URSENetWorldSubsystem::SendToPlayer, SendToGuests and RegisterEventHandler carry your own messages.
URSENetPlayerComponentRSENetOn each player state: the slot (GetPlayerId) and the player's own state sections.
URSENetBattleSubsystem, URSENetActionBattleSubsystemRSENetThe live-battle and Action-battle streams.
URSECommandSubsystem, FRSECommand, URSECommandHandlerRSECoreThe command bus and its building blocks.
URSESaveSubsystem, RSEStateSections::MarkChangedRSECoreThe state sections (the same ones the save system writes).
URSEPlayers, URSEPlayerProvider, URSEAuthorityPolicyRSEWho the players are (GetControllerFor, GetPawnFor) and when the co-op rules apply (HasAuthority, IsMultiplayerSession).
URSECoopNoticeSubsystemRSEOne notice path for every player.
URSECoopRulesSettings, URSECoopFlowSettingsRSECore / RSEThe co-op rules and the flow settings (leash, travel, scene skip, texts).
ARSECoopMapSettingsRSEA per-map actor for the leash radius and the guests' start spacing.

There are no data assets to create for co-op. Your first work is in settings.

23.4 Settings

23.4.1 Online

Project Settings → RPG Saga Engine → Online (URSEOnlineSettings):

SettingWhat it doesDefault
Provider ClassThe service: LAN (Null subsystem), Steam, Epic Online Services, or your own provider class.LAN
Fallback To LANWhen the chosen service is not available (Steam not running, EOS not configured), use LAN instead of failing.on
Game VersionPlayers join only hosts with the same version. Empty uses the project version.empty
Max PlayersPlayers in a session, host included (2 to 4).4
VisibilityPublic, Friends Only or Invite Only. LAN sessions are always visible on the local network.Friends Only
LAN PortThe game port the host listens on.7777
Host MapThe map the host opens when hosting. Empty stays on the current map and opens it to players (drop-in).empty
Join In Progress PolicyA player who joins during a battle: spectate until it ends, join immediately, or be refused.Spectate
Keep Slots For ReconnectA player who drops keeps the slot and character until a new player needs the place.on
Operation Timeout SecondsAny step (create, search, join, travel, leave) that takes longer fails with a clear message.30 s
Auto Reconnect, Reconnect Attempts, Reconnect Delay SecondsA client whose connection drops rejoins the same session.on, 3, 3 s
Lobby Widget Class, Lobby Z Order, Lobby Panel WidthThe lobby screen.built-in

For one run the command line -RSESessionProvider=LAN (or Steam, EOS, or a class path) overrides the provider.

23.4.2 Co-op network

Project Settings → RPG Saga Engine → Co-op Network (URSENetSettings):

SettingWhat it doesDefault
Enable CoopInstalls the co-op layer whenever the game runs a network session. Off means your project replicates on its own.on
Section Send IntervalHow often the host looks for changed state sections.0.1 s
Change Detection IntervalHow often the host compares every section's bytes to catch unmarked changes. 0 = never.0.5 s
Min Section Resend IntervalA section that keeps changing is sent at most this often. The latest state always goes out.0.2 s
Compress Sections, Compression Min Bytes, Compression FormatSections above the minimum are compressed.on, 256, Oodle
Max Section BytesA larger section is not replicated and is reported as an error. Split that state.256 KB
Max Command BytesLarger commands are refused. A much larger packet disconnects the sender.4096
Max Commands Per SecondPer guest. More are refused, not applied.30
Max Queued CommandsCommands a guest keeps while it is still connecting.32
Broadcast Result TypesCommand types whose results every player receives, for notices.none
Player Provider Class, Authority Policy ClassWho the players are, and when co-op rules apply (from the second player). Replaceable.built-in
Assign Member On JoinA joining player takes a party member from the companion AI. Off means you assign members yourself.on
Class RelevancyPer actor class: cull distance, update rate and priority.built-in

The Battle and Action Battle categories hold the stream tuning (intervals, chunk sizes, the Action mirror's byte budget, claim limits). The defaults are measured. Leave them alone until you have a reason to change them.

23.4.3 Rules and flow

WhereMain values
Co-op Rules (URSECoopRulesSettings)Default Encounter Conversion (Free Tactics), Companion Orders (Any Player), Guest Equipment (Own Character), the policy classes
Co-op Flow (URSECoopFlowSettings)Leash on, Host anchor, 40 m, 4 s grace; Scene Skip Rule (Host Only); Group Travel Countdown (3 s); every notice text
Free Tactics (Co-op)Companion Units (AI), Turn Timeout Seconds (45), Turn Timeout Action (Host Takes Over), Pull In Far Players (on), Reconnect Retakes Unit At Once (off)

Per encounter, the Co-op Conversion field chooses what happens in a session: fight it live, use a Co-op Alternative made for co-op, or keep its mode for a set piece. The encounter's validation warns when something would not convert.

See Appendix B for every property.

23.5 Blueprint usage

23.5.1 Host, find and join

All session nodes live on the RSE Session Subsystem (Get Game Instance → Get Subsystem (RSE Session Subsystem)) and in three latent nodes.

Host from a menu button:

  1. On the button's On Clicked, call Host Session (Async). Leave Params at their defaults to use the project settings, or fill the optional fields to override max players, visibility, label or map.
  2. On Success: hide the menu. The game is now hosting and other players can find it.
  3. On Failure: pass the Error to Get Session Error Message and show the text in a message box.

Find and join:

  1. Call Find Sessions (Async). On Success gives the results. Each has a label, map, players, ping and whether it can be joined (Is Session Joinable tells you).
  2. Show them in a list. When the player picks one, call Join Session (Async) with its Result Index.
  3. On Success fires once the client is connected and the map has loaded.

Other nodes on the subsystem: Leave Session (also cancels a search or a reconnect), Show Invite UI, Query Friends, Invite Friend, Set Preferred Character, Open Lobby Screen, Set Battle In Progress, Is In Session, Is Host, Is Multiplayer, Get Session State, Get Last Error. Events to bind: On State Changed, On Session Error, On Sessions Found, On Lobby Changed, On Player Joined, On Player Left, On Player Admission Changed, On Friends Received, On Invite Accepted.

23.5.2 Submit a command from a widget

  1. Pick the command struct. New command structs are declared in C++ (see below). The framework's own commands, such as the party and quest commands, are ready to use.
  2. In the widget: Get Game Instance → Get Subsystem (RSE Command Subsystem).
  3. Make Instanced Struct with the command struct and its fields.
  4. Submit Command. The returned result says Applied, Rejected or Forwarded (a guest sent it to the host).
  5. Bind On Command Result to show the final outcome. On a guest it arrives later, after the host answered.

23.5.3 A handler in Blueprint

  1. Create a Blueprint class from RSE Command Handler.
  2. Set Command Type to the command struct. Set Permission (Any Player or Host Only) and, for host-only commands, a Denied Reason. Untick Uses Seed if the command has no randomness.
  3. Override Validate (return false with a reason the player can read) and Apply (change the state).
  4. List the class in Project Settings → RPG Saga Engine → Commands → Handler Classes.

23.5.4 Notices

Call Notify (one player) or Notify All on the RSE Co-op Notice Subsystem. Bind On Notice to draw the notice in your own widget.

23.5.5 Authority checks

Use Has Game Authority (or Has Authority from the players library) before logic that must run once (spawning, rewards, timers). It is true in single player and on the host, false on a guest. Is Multiplayer Session tells you whether the co-op rules apply.

23.6 C++ usage

23.6.1 Making your system's state replicate

If your system already saves through the save system, it is already replicated. Register the section, set its scope, and mark it when it changes:

// In your subsystem's Initialize (the same registration as for saving):
URSESaveSubsystem* Save = Collection.InitializeDependency<URSESaveSubsystem>();
auto Section = Save->RegisterStruct<FMyReputationState>(TEXT("Reputation"), 1, this, [this]() { return &State; });
Section->Scope = ERSEStateScope::Shared;   // Shared, PerPlayer or HostOnly

// Whenever the state changes (on the authority):
RSEStateSections::MarkChanged(this, TEXT("Reputation"));

On a guest the section arrives and is applied through the same code as a load. Listen to URSESaveSubsystem::OnSectionChanged (Blueprint: On State Section Changed) to refresh the screen.

23.6.2 Making your system's actions commands

USTRUCT()
struct FMyCmd_Praise : public FRSECommand
{
    GENERATED_BODY()
    UPROPERTY() FPrimaryAssetId Town;
};

// In Initialize:
URSECommandSubsystem* Commands = Collection.InitializeDependency<URSECommandSubsystem>();
Commands->RegisterHandler<FMyCmd_Praise>(
    [this](const FRSECommandContext& Context, const FMyCmd_Praise& Cmd, FText& OutReason)
    {
        return Cmd.Town.IsValid();                       // never change state here
    },
    [this](const FRSECommandContext& Context, const FMyCmd_Praise& Cmd)
    {
        State.Add(Cmd.Town, 1);                          // Context.Instigator is the sender
        RSEStateSections::MarkChanged(this, TEXT("Reputation"));
    },
    ERSECommandPermission::AnyPlayer, this);

// From a widget or gameplay code:
Commands->Submit(FMyCmd_Praise{ TownId });

Context.Instigator is the player id of the sender, set by the host's transport. For a public function that must keep its signature in single player, RSECommands::SubmitOrApply wraps the pattern: single player applies at once, a guest sends the command.

23.6.3 Messages from the host to a player

Any USTRUCT can travel from the host to one player or to all guests:

// Inside any class that has a world:
URSENetWorldSubsystem* NetWorld = GetWorld()->GetSubsystem<URSENetWorldSubsystem>();
NetWorld->RegisterEventHandler(FMyEvent_Praised::StaticStruct(), this,
    [this](const FInstancedStruct& E) { ShowToast(E.Get<FMyEvent_Praised>()); });

NetWorld->SendToPlayer(Context.Instigator, FInstancedStruct::Make(FMyEvent_Praised{ /*...*/ }));
// or NetWorld->SendToGuests(...)

The framework's own screens work this way: screens opened by a player's action (shop, code lock, lockpick), puzzle results and dialog votes.

23.6.4 Your own actors

Actors in the world follow the usual Unreal rules: replicated properties and OnRep functions. Put the game logic behind URSEPlayers::HasAuthority(this) so it runs on the host only. Find a player from a command with URSEPlayers::GetControllerFor(PlayerId) and GetPawnFor(PlayerId).

Add RSENet to your module's dependencies only if you use its types directly. Systems that stay on the bus and the state sections need no dependency on it.

23.6.5 Extension points

SubclassTo change
URSESessionProvider (or URSESessionProvider_OSS)Add another online service. Implement Host, Find, Join and Leave, answer each request with its Report... call, and pick the class in Provider Class. A Blueprint child of the OSS provider with another Subsystem Name is often enough for a new platform.
URSEPlayerProvider, URSEAuthorityPolicyWho the players are, and when co-op rules apply.
URSELeashPolicy, URSEGroupTravelPolicy, URSECoopEncounterPolicyThe leash, group travel and encounter conversion rules.
URSEMenuExtensionYour own entries in the main and in-game menus.
URSELobbyViewModel / URSELobbyWidgetA custom lobby screen. Derive a Widget Blueprint from RSE Screen Widget, read the view model and call its actions (Host, Find, Join, Leave, Invite, Cycle Character). Set it as Lobby Widget Class.

23.7 Advanced

23.7.1 Steam

  1. Enable the engine plugin Online Subsystem Steam, then restart the editor.
  2. Add to Config/DefaultEngine.ini:
[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="/Script/SocketSubsystemSteamIP.SteamNetDriver",DriverClassNameFallback="/Script/OnlineSubsystemUtils.IpNetDriver")

[OnlineSubsystem]
DefaultPlatformService=Steam

[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480
bInitServerOnClient=true

[/Script/SocketSubsystemSteamIP.SteamNetDriver]
NetConnectionClassName="/Script/SocketSubsystemSteamIP.SteamNetConnection"
  1. Set Online → Provider Class to Steam.
  2. Test with two PCs and two Steam accounts. Steam does not work inside the editor viewport: package the game or run it standalone.

App ID 480 is Valve's public test app. It is fine for development, but every developer using it sees the others' sessions (RPG Saga Engine lists sessions of other games and versions as incompatible and does not let you join them). Use your own App ID before release. If Steam is not running, the game falls back to LAN and the log says why.

23.7.2 Epic Online Services

  1. Create a product in the Epic Developer Portal: a product, a sandbox, a deployment, and a client policy that allows peer-to-peer and lobbies. Write down the Product, Sandbox, Deployment and Client IDs and the Client Secret.
  2. Enable the engine plugin Online Subsystem EOS.
  3. In Project Settings → Plugins → Epic Online Services add an artifact with those IDs and a 64-character hexadecimal encryption key. Tick Use EAS and/or Use EOS Connect.
  4. Add to Config/DefaultEngine.ini:
[OnlineSubsystem]
DefaultPlatformService=EOS

[OnlineSubsystemEOS]
bEnabled=true

[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="/Script/SocketSubsystemEOS.NetDriverEOS",DriverClassNameFallback="/Script/OnlineSubsystemUtils.IpNetDriver")

[/Script/SocketSubsystemEOS.NetDriverEOS]
bIsUsingP2PSockets=true
  1. Set Provider Class to Epic Online Services.
  2. Players must be signed in. For development, run the EOS Developer Authentication Tool and start each game with -AUTH_TYPE=developer -AUTH_LOGIN=localhost:<port> -AUTH_PASSWORD=<credential name>. The provider signs in automatically before hosting or searching and reports Not signed in when it cannot.

23.7.3 What was tested

Be honest with your players and your testers about this. The two-process LAN test (Tools/Dev/coop_lan_smoke.ps1 in the repository) covers join, a guest command, both players moving, a state change, leaving, zone travel, dialogs and scenes, and live battles including a player leaving mid-battle. Steam and EOS have been exercised on a local network only, not between distant machines. Four-player sessions under bad network conditions and hosting on a Steam Deck are not yet tested. Competitive PvP is not part of this release.

23.7.4 Bandwidth

Measured on the example map, two players, LAN:

WhatMeasuredBudget
Joining (every state section, once)about 5 KB sent60 KB
Exploring, per connection4 to 6 KB/s average15 KB/s average, 40 KB/s peak
A guest commandabout 0.3 KB
A live battle, the battle streamabout 0.9 KB/s5 KB/s

If your own state is large, keep sections small and split them (Max Section Bytes reports offenders in the log).

23.7.5 Test without a second machine

  • RSE.Net.SimulateMultiplayer 1 makes the default authority policy report a multiplayer session, so the co-op time and pause rules apply. RSE.Net.ForceProxy 1 makes it report no authority, as on a client: logic timers stop. Both are for debugging and tests.
  • RSE.Net.LogTraffic 1 logs every section and command. RSE.Net.Dev.Walk X Y Seconds, RSE.Net.Dev.SwitchLeader, RSE.Net.Dev.Kick Slot (host), RSE.Net.Dev.ScriptWhen guest|synced|noguest ScriptFile drive a test without clicks.
  • RSE.Coop.Dev.Prompt [Host|Speaker|Vote], RSE.Coop.Dev.Advance, RSE.Coop.Dev.Choose <Option>, RSE.Coop.Dev.Status and RSE.Coop.Dev.Notify <Text> test the co-op dialog, leash and notices.
  • Start two processes with -ExecCmds="RSE.Online.Host" and -ExecCmds="RSE.Online.AutoJoin 60" (add -RSESessionProvider=LAN -nosteam). A label (RSE.Online.Host [Max] [Label], RSE.Online.AutoJoin [Seconds] [Label]) keeps test sessions apart.

All console commands are listed in Appendix C.

23.7.6 Rules for network-ready systems

Whenever you add a system, follow the same rules the framework follows:

  • Actions go through commands. UI and input do not change shared state directly. They submit a command.
  • One owner per piece of state. Others read it and listen to its events. Do not keep private copies that can drift.
  • Presentation feeds on events. A client that only receives the events must be able to show the same thing.
  • Control of a character is swappable. The local player, the AI or a remote player take the same path. Never assume one local player: avoid GetPlayerController(0) in logic.
  • Determinism. Use URSESeedSource::NextSeedFor(this, Stream) or the seed in the command context, never the global random functions, for anything that affects the game.
  • Guard effects. Logic with effects in the game runs behind URSEPlayers::HasAuthority (or URSEAuthoritySubsystem::HasAuthorityFor).
  • No RPCs in framework-style code. A system goes online through its save section and its commands. Actors of your own can use ordinary replication.

23.8 Troubleshooting

SymptomCause and fix
The online service is not availableThe provider's subsystem is not loaded: the engine plugin is not enabled, DefaultPlatformService is not set, Steam is not running, or the game was started with -nosteam. The log line RSEOnline: ... is not available (...) names the reason. With Fallback To LAN on, the game uses LAN instead.
LAN finds nothingBoth games must be on the same subnet. Allow the game through the firewall: UDP for the LAN beacon port (14001) and the game port (7777 by default).
The host is running a different versionSet the same Game Version (or project version) on both builds.
Steam: sessions of other games appearApp ID 480 is shared. They are listed as incompatible and cannot be joined. Use your own App ID.
EOS: Not signed inStart the game with the developer authentication arguments, or sign in through your own login flow before hosting.
Your system works for the host but not for guestsIts state is not a state section, or the owner never calls MarkChanged. Check RSE.Net.Status for the section, and read the log for Max Section Bytes errors.
A guest's action does nothingIt is not a command, or the handler's Validate refused it. Turn on Commands → Log Refused Commands (or RSE.Commands.Log 1) and read the reason.
A guest is refused with a rate messageMore than Max Commands Per Second arrived. Lower the rate of the sender, or raise the setting.
Timers and spawns run on guests tooThe logic is not behind URSEPlayers::HasAuthority.
A guest keeps being pulled backThe leash. Raise Leash Radius, change the anchor, or place an RSE Co-op Map Settings actor.

23.9 See also