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.
- Validate the authoritative LS and every initialized/ready PlayerState.
- Call
begin_house_freeze. - Replicate
{canonical selection, bLocked=true, FreezeAttemptId}and capture immutable team assignments. - Wait for every owner to confirm its final selection.
- Fan aggregate freeze success to every owner, which creates its native ticket.
- Wait for complete matchmaking admission, match formation, and Edgegap.
- 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