Skip to content

Backend House Launch Protocol KB

Scope

This is the current source of truth for the House launch protocol across the listen server (LS), owning clients, Nakama matchmaking admission, Edgegap deployment, and DS-travel commit. The broader multiplayer lifecycle is in House lifecycle overview; inventory and live-selection formats are in backend inventory and selection.

Authority

  • The LS validates live selection intents, assigns canonical revisions, replicates ready/lock state, and starts the launch operation.
  • Nakama owns House membership, the frozen roster, confirmation and admission records, deployment state, and the immutable deployment snapshot.
  • Each owning client confirms only its own selection and creates only its own native matchmaker ticket.
  • The DS consumes the deployment-scoped snapshot and accepts no live House selection mutation.

The LS never supplies an authoritative roster or player count to Nakama.

Persistent state

House.ActiveFreezeAttemptID is the membership barrier. A freeze-attempt record contains the immutable roster/hash and team assignments, per-user confirmation and admission state, ticket IDs, deadlines, deployment state/ID, and terminal failure details. Mutations use optimistic storage versions. Confirmation is idempotent by (FreezeAttemptId, NakamaUserId).

Live selection

The owning client predicts locally and trailing-edge debounces UE submission to the LS for 0.5 seconds. The LS validates it and assigns the canonical revision. Accepted UE broadcasts use a one-second latest-value coalescer. Dirty selections from every player share one house_set_selection_batch call per five-second window.

There is no player_set_selection_confirm RPC. The only direct owning-client selection RPC to Nakama is the one-shot confirm_final_selection during freeze.

Start House Game

The LS Blueprint calls Start House Game (Async) after storing game-mode options through gm_set_pending_options with the canonical house_id. The node owns one complete pre-travel launch and prevents concurrent launch operations.

  1. Validate the authoritative LS and every initialized/ready PlayerState.
  2. Call begin_house_freeze.
  3. Replicate {canonical selection, bLocked=true, FreezeAttemptId} and capture immutable team assignments.
  4. Wait for every owner to confirm its final selection.
  5. Fan aggregate freeze success to every owner, which creates its native ticket.
  6. Wait for complete matchmaking admission, match formation, and Edgegap.
  7. Complete only after Nakama publishes committed connection-info.

Local validation reports all offending display names for the failed phase. The node polls get_house_freeze_status every two seconds as a missed-notification fallback; three consecutive polling transport errors are terminal. Every Error path uses TANK_PRINT before the LS-only Blueprint Error output, so it reaches the categorized UE log, viewport console, and onscreen debug overlay.

Freeze confirmation

begin_house_freeze validates host authority, House lease/status, the online canonical roster, and absence of another attempt. It creates a 30-second pending attempt and sets the membership barrier.

The generated ID is replicated in the same canonical struct as the selection lock. This prevents RepNotify from observing a new lock with a null or previous attempt ID. Each client handles a valid new attempt once and calls confirm_final_selection with its catalog version, selection epoch/revision, and equipped JSON. Nakama validates membership, expiry, inventory ownership, canonical state, and the unchanged roster.

OnFinalSelectionConfirmationComplete is only that client's RPC result. It does not authorize matchmaking. Once the entire roster confirms, Nakama locks persistent selections, writes the frozen snapshot, marks the attempt completed, and sends the LS house-freeze-complete (1010). The LS reliably invokes OnHouseFreezeSucceeded(FreezeAttemptId) on every frozen member; Blueprint creates the native ticket from this aggregate event.

Matchmaker admission

Every ticket must contain both:

Query:           properties.houseId:<HouseId>
String property: houseId -> <HouseId>

BeforeRt("MatchmakerAdd") derives the authenticated user and House ID, verifies membership in the completed frozen roster, rejects stale attempts, and records admission pending. AfterRt("MatchmakerAdd") records admission success and the returned ticket ID.

Admission completes only after every frozen member reaches the successful after-hook. A client-local/native failure that never reaches it appears as a missing admission and becomes a deadline failure. Nakama records successful ticket IDs to validate match formation. Ticket removal remains owning-client cleanup driven by OnHouseGameLaunchFailed; there is no aggregate MatchmakerRemove acknowledgement path. Only the complete frozen roster may form the House match.

Deployment and commit

After match formation, Nakama marks deployment deploying, consumes pending game options, and requests Edgegap. On readiness it rechecks that the attempt is still active, finalizes the immutable snapshot under the exact (house_id, deployment_id), reads it back, then marks deployment committed. Only then does it send connection-info (1001) to the matched roster.

The notification contains IP/DNS, port, InstanceId, HouseId, and FreezeAttemptId. UE stores that ID in CommittedHouseGameLaunchAttemptId immediately before starting DS travel.

Irreversible boundary

Committed connection-info is the irreversible LS-to-DS boundary. Before it, aggregate rollback to the House is allowed. After it:

  • aggregate failure for the attempt is ignored;
  • selection/ready state is not rolled back;
  • client leave is rejected;
  • client and LS House switching are rejected;
  • intentional LS close/reopen is rejected; and
  • direct backend leave, join/open-another-House, and voluntary close are rejected.

The common error is Game travel has already started. Nakama checks DeploymentStatus == "committed"; UE checks the GameInstance-lifetime committed attempt ID. Automatic backend cleanup remains allowed. Any later connection or travel failure uses individual disconnect/fresh-House recovery.

Pre-commit aggregate failure

Confirmation, roster, admission, match, Fleet Manager, deployment, snapshot, or commit-status failure before connection-info is terminal for the whole attempt.

For a healthy LS, Nakama fails the attempt and releases its membership barrier. UE clears replicated attempt IDs/locks, resets every ready flag, and reliably fans OnHouseGameLaunchFailed(FreezeAttemptId, FailureStage, FailureCode, ErrorMessage) to every owner. This event is the common Blueprint ticket/UI cleanup entry point, including best-effort removal of that client's ticket. The LS node's Error output runs after native rollback and its single-flight gate is already released.

Failure is deduplicated by attempt ID at OnlineManager and PlayerController, so notification and polling cannot invoke cleanup twice.

If the LS is the failed participant, Nakama closes the canonical House before sending house-launch-failed (1011) directly to the frozen roster. Former members use fresh-House disaster recovery without relying on LS owner RPCs.

Membership behavior

State Client leave/switch LS close/switch
Not ready, no freeze Allowed subject to role/capacity Subject to host-role rules
Ready Rejected until Not Ready Direct switch rejected by departure validation
Freeze active before commit Rejected locally and by Nakama Voluntary switch rejected; close is launch-state dependent
Failure rollback complete Allowed again Allowed subject to host-role rules
Connection-info committed Always rejected Always rejected

Client departure is serialized: Nakama canonical removal precedes EOS cleanup. A Nakama rejection leaves EOS untouched. EOS failure after canonical removal is catastrophic and enters fresh-House recovery.

Signals

Signal Meaning
house-freeze-complete (1010) Freeze terminal state; LS also polls status
house-launch-failed (1011) Direct pre-commit aggregate failure fan-out
connection-info (1001) Snapshot/deployment committed; DS travel authorized
house-closed (1030) Intentional host closure after authoritative deletion
OnFinalSelectionConfirmationComplete This client's confirmation result only
OnHouseFreezeSucceeded Aggregate confirmation complete; create ticket
OnHouseGameLaunchFailed Common client launch-failure cleanup entry point

Implementation map

  • UE wrapper: Source/Metal_terra/Private/Core/AsyncActions/AsyncAction_StartHouseGame.cpp
  • UE freeze: Source/Metal_terra/Private/Core/AsyncActions/AsyncAction_FreezeHouseSelectionSnapshot.cpp
  • UE rollback/departure gates: Source/Metal_terra/Private/Core/Subsystems/OnlineManagerSubsystem.cpp
  • UE notifications/commit: Source/Metal_terra/Private/Core/Subsystems/NakamaConnectionSubsystem.cpp
  • UE client events: Source/Metal_terra/Private/Core/TankPlayerController.cpp
  • Nakama membership: DevOps/Nakama/data/modules/edgegap/house.go
  • Nakama freeze: DevOps/Nakama/data/modules/edgegap/selection_freeze_attempt.go
  • Nakama admission: DevOps/Nakama/data/modules/edgegap/matchmaker_admission.go
  • Nakama deployment: DevOps/Nakama/data/modules/edgegap/matchmaker.go
  • Nakama snapshot: DevOps/Nakama/data/modules/edgegap/selection_snapshot.go
  • Nakama disconnect cleanup: DevOps/Nakama/data/modules/edgegap/sessions.go