Table of contents
Client Multiplayer Connection Flow And Session Cleanup
Client multiplayer connection flow coordinates HTTP authentication, realm and character selection, handoff, game handshake, QUIC realtime startup, reconnect status, and teardown. MultiplayerConnectionState is the persistent Bevy resource that carries those phases across standalone screens.
Ownership And Data Flow
MultiplayerConnectionState owns:
- login endpoint and client compatibility inputs;
- account login response and access token;
- realm list and selected realm index;
- character roster and selected character index;
- handoff token response;
- game handshake response;
- optional live
RealtimeSessionHandle; - one optional asynchronous
PendingJoband player-facing status.
MultiplayerUiState is intentionally separate. Chat draft, menus, dialogue bubbles, keybind capture, emote state, and telemetry history are presentation concerns and do not establish login or world authority.
The shared multiplayer Tokio runtime uses a dedicated two-worker thread pool. Bevy starts work through IoTaskPool, while poll_pending_job reduces completion back into the connection resource on the update schedule.
Connection Paths
Login only
start_login_flow optionally registers the account, authenticates, fetches realms, and fetches characters. Success populates authenticated state but does not request a handoff or start realtime.
Selected character entry
start_character_connect requires an authenticated account, selected realm, and selected character. It requests v1/select-character, follows bounded game-server handshake redirects, and submits GameHello plus the handoff token to v1/handshake.
An accepted handshake must include both a game session ticket and RealtimeSessionActivation. The client selects a healthy QUIC gameplay endpoint, applies activation locally, sends realtime hello, validates the welcome session token, and reports transport ready only after that welcome arrives.
Quick connect
start_quick_connect performs authentication and world entry in one pending job using the first returned realm and first returned character. Empty realms or characters fail the job.
Character create and delete
Create and delete require authenticated state and return a replacement character roster. They do not create a realtime session. Selection is clamped to the resulting roster length when the network snapshot is applied.
Only one pending job can exist. Every start method returns without doing work while another job is present.
Lifecycle And State Transitions
The connection reducer handles a small event model:
| Event | Result |
|---|---|
Started |
Applies the flow cleanup scope, stores the task/stage, and sets a pending status |
Pending |
Keeps the job and refreshes its pending status |
Succeeded |
Merges only populated NetworkSnapshot fields and derives authenticated or connected status |
Failed |
Applies cleanup for the failed stage and stores Error: ... |
RuntimeUnavailable |
Applies the flow cleanup scope and stores the runtime error |
ValidationFailed |
Changes status only; no network task starts |
Reset |
Drops the pending job, clears the authenticated session scope, and returns to Idle |
The realtime transport itself transitions through Connecting, Live, Reconnecting, Transferring, DisconnectedByServer, TimedOut, and ClosedByUser. Reconnect attempts retain only commands allowed by the reconnect journal policy. Cross-instance transfer replaces endpoint/session activation and clears the old replicated world baseline.
Cleanup Scopes
Cleanup is deliberately scoped:
WorldEntry
This scope requests shutdown on the existing realtime handle and clears handoff, handshake, and realtime state. It preserves login response, realms, character roster, and selections.
It is used for connect, create-character, and delete-character starts/failures, and for zone-transfer recovery. A failed world-entry attempt can therefore return to character or realm selection without forcing reauthentication.
AuthenticatedSession
This scope includes world-entry cleanup and also clears realms, login response, character roster, and selection indexes. It is used for authenticate and quick-connect starts/failures and full reset/logout recovery.
Validation errors do not apply cleanup because no operation started. Starting a new flow applies cleanup before the asynchronous task runs, preventing stale handoff or realtime handles from surviving into the new attempt.
World-Entry Readiness
World entry is ready only when both conditions are true:
- the HTTP game handshake exists and has
accepted = true; - realtime transport health is
Liveand its world projection contains the authoritative local player.
A session ticket, a realtime handle, an accepted handshake alone, or a live transport without a local player is not sufficient. The standalone loading screen waits for world_entry_ready() before loading the assigned scene and entering gameplay.
This distinction protects against showing the world during the gap between handshake, activation, QUIC welcome, and local-player authority.
Disconnect And Logout
Clean logout queues a realtime disconnect and sets Disconnecting from realtime session... only when has_realtime_session() reports a live transport with an authoritative local player. In that case, the standalone shell waits until transport is ClosedByUser and the last message is no longer disconnect requested, then unloads the scene, resets connection and multiplayer UI state, and returns to login. During connecting, reconnecting, transfer, or an already-closed state, the shell skips that wait and proceeds with reset because there is no fully established realtime session to drain.
reset_runtime_session is stronger: it discards any pending job, requests transport shutdown through handle cleanup, clears authenticated state, and returns to Idle. Unexpected runtime connection loss uses this full reset and unloads the scene. Zone-transfer failure instead uses world-entry cleanup so account and roster state survive for retry.
Extending The Flow
When adding a connection operation:
- Add a
MultiplayerConnectionFlowandPendingJobStageonly if its lifecycle differs from an existing operation. - Decide whether failure invalidates only world entry or the full authenticated session.
- Validate required local state before spawning a task. Validation failure should not partially clear a usable session.
- Return a
NetworkSnapshotwhoseOptionfields represent the exact state updated by the operation. - Keep task completion inside
reduce_connection_flow; do not update status and cleanup ad hoc from screens. - Define loading/menu disabled behavior for the pending stage.
- Add success, failure, runtime-unavailable, and reset tests, plus a readiness test if world entry changes.
Rules And Invariants
- At most one connection job is pending.
- Selected realm and character indexes are always clamped after list replacement.
- A successful handshake must publish a session ticket and activation before realtime connect starts.
- Realtime welcome must acknowledge the activation session token.
- World-entry readiness requires accepted handshake, live transport, and authoritative local player.
- World-entry cleanup must preserve authenticated account and roster state.
- Authenticated-session cleanup must request realtime shutdown before dropping the handle.
- A rejected handshake may be retained for status, but it must not produce a realtime handle.
- UI flow state is not evidence of network authority.
Validation And Debugging
Run focused connection tests with:
cargo test -p aether has_realtime_session_requires_authoritative_local_player
cargo test -p aether world_entry_ready_requires_an_accepted_handshake
cargo test -p aether world_entry_ready_requires_live_authoritative_realtime_player
cargo test -p aether status_helpers_format_errors_and_cleanup_failed_job_stages
cargo test -p aether realtime_disconnect_finished_requires_disconnect_request_to_complete
Diagnose a stuck flow by checking these fields in sequence:
pending_job.stageandstatus: is Bevy still polling the intended task?login_response, realms, and characters: did HTTP authentication/bootstrap complete?handoffandhandshake.accepted: did selection and game handshake complete, or is redirect/rejection visible?- Handshake
sessionandactivation: an accepted response without either is a hard client error. - Realtime transport
health, endpoint, reconnect attempt, last packet, and last message. - World projection local player: transport
Livewithout this player remains loading by design.
Common status interpretations:
Error: no authenticated account is available: a selected-character operation was called before login state existed.- Handshake accepted but loading never finishes: inspect QUIC endpoint health, welcome token validation, transport health, and local activation player.
- Login vanished after a connect failure: the operation was probably classified as authenticate/quick-connect instead of world-entry scope.
- Logout hangs at disconnecting: inspect whether the transport reached
ClosedByUserand replaced thedisconnect requestedmessage. - Transfer recovery returns to login: use world-entry recovery rather than full reset for a recoverable transfer failure.
Concrete Flow Example
For selected-character entry, the expected state progression is:
authenticated roster
-> ConnectCharacter started (old handoff/realtime cleared)
-> handoff token
-> accepted game handshake with ticket + activation
-> QUIC hello/welcome token match
-> transport Live + authoritative local player
-> world_entry_ready
Any failure before the last line clears only handoff, handshake, and realtime state, leaving the authenticated roster available for another selection.