Skip to content

Server Authority

Summary: All gameplay-critical operations use server-authoritative patterns with Server_ prefixed RPCs. Authority checks use HasAuthority() and GetLocalRole(). Item operations use GUID-based validation to detect desync and prevent duplication. Clients use optimistic prediction with server correction.

Table of Contents


Why Server Authority

The Trust Problem

In multiplayer games, clients cannot be trusted. A malicious client could: - Claim to have items they do not possess - Report damage values higher than possible - Move faster than allowed - Execute actions on cooldown

Server authority solves this by making the server the single source of truth for all gameplay state.

Design Principles

Principle Implementation
Server owns state All authoritative data lives on server
Clients request Clients send RPCs asking server to act
Server validates Every request is verified before execution
Server broadcasts Results replicate to all clients

Authority Model

Authority Flow

+----------------+                    +----------------+
|    CLIENT      |                    |    SERVER      |
+----------------+                    +----------------+
        |                                     |
        | 1. User Action                      |
        |     (click item)                    |
        |                                     |
        | 2. HasAuthority()?                  |
        |     NO                              |
        |                                     |
        | 3. Server_RPC() ------------>       |
        |                                     |
        |                             4. Validate Request
        |                                - Item exists?
        |                                - Player owns it?
        |                                - Space available?
        |                                     |
        |                             5. Execute or Reject
        |                                     |
        | <--------------------------- 6. Replicate Result
        |                                     |
        | 7. OnRep_ Callback                  |
        |     UI Updates                      |
        |                                     |

Authority Hierarchy

Server (HasAuthority = true)
    |
    +-- Owns all gameplay state
    +-- Validates all requests
    +-- Executes all mutations
    +-- Broadcasts results
    |
    v
Clients (HasAuthority = false)
    |
    +-- ROLE_AutonomousProxy (owning client)
    |       - Local prediction allowed
    |       - Receives corrections
    |
    +-- ROLE_SimulatedProxy (other clients)
            - Interpolates replicated state
            - No local prediction

Server RPC Patterns

Standard Pattern

All server RPCs follow a consistent pattern:

Client Call Path:
    if (!HasAuthority())
        Server_DoAction(params)
        return optimistic_result

Server Execution:
    Server_DoAction_Implementation(params)
        - Validate request
        - Execute if valid
        - Reject if invalid (MarkArrayDirty for correction)

RPC Contract Table

RPC Parameters Validation Result
Server_TryAddItemToContainer UItemObject* Item valid, space available Item added or rejected
Server_SwapItems Items, Indices, Target Items at claimed positions Swap or correction
Server_PlaceItemById FGuid, Index GUID lookup, space check Place or correction
Server_DropItem UItemObject*, Count Item owned, count valid Item dropped
Server_ApplyDamage AActor*, FHitResult Target valid, in range Damage applied
Server_Move FVector Direction magnitude Movement applied

GUID-Based RPCs

Item pointers can be spoofed. GUID-based RPCs are more secure:

Client:
    Item->InitializeInstanceId()           // Generate GUID locally
    Server_PlaceItemById(Item->GetInstanceId(), TargetIndex)

Server:
    UItemObject* Item = Registry->FindItemByInstanceId(ItemId)
    if (Item)
        PlaceItem_Implementation(Item, TargetIndex)

Authority Checks

Check Methods

Method Returns True When Use Case
HasAuthority() Actor owned by this process Server-only logic
GetLocalRole() == ROLE_Authority This is the server Same as HasAuthority
GetLocalRole() == ROLE_AutonomousProxy This is the owning client Client prediction
GetLocalRole() == ROLE_SimulatedProxy This is a remote client Interpolation

Authority Check Patterns

Pattern 1: Server-Only Execution
+-------------------+
| if (!HasAuthority) |-----> return / send RPC
+-------------------+
         |
         v (server continues)
    Execute Logic

Pattern 2: Role-Based Behavior
+--------------------------------+
| switch (GetLocalRole())        |
|   ROLE_Authority: full logic   |
|   ROLE_AutonomousProxy: predict|
|   ROLE_SimulatedProxy: display |
+--------------------------------+

Where to Check Authority

Location Why
Component initialization Server grants abilities, applies attributes
State mutations Only server modifies authoritative state
Collision processing Server handles all hit detection
Container creation Server creates subobjects

Validation Patterns

Pointer Validation

Macro Use Case Behavior
ensure() Expected-valid pointers Logs if null, continues
check() Must-be-valid pointers Crashes if null
IsValid() Runtime checks Returns bool

Validation Flow

Server_SwapItems_Implementation(HoverItem, HoverIndex, ClickedItem, ClickedIndex, TargetIndex)
    |
    +-- Basic Validation
    |       IsValid(HoverItem)?
    |       IsValid(ClickedItem)?
    |
    +-- Authority Lookup
    |       HoverEntry = FindEntryAtIndex(HoverIndex)
    |       ClickedEntry = FindEntryAtIndex(ClickedIndex)
    |
    +-- Desync Check
    |       HoverEntry->Item == HoverItem?
    |       ClickedEntry->Item == ClickedItem?
    |
    +-- Space Validation
    |       IsSpaceAvailable(dimensions, target, excludes)?
    |
    +-- Execute or Reject

Desync Detection

Why Desync Happens

Desync occurs when client and server state diverge: - Network latency - Prediction errors - Packet loss - Race conditions

Detection Strategy

Client Claims                 Server Reality
+----------------+           +----------------+
| Item A at 0    |           | Item A at 0    | <-- Match
| Item B at 5    |           | Item C at 5    | <-- DESYNC!
+----------------+           +----------------+
                                    |
                                    v
                             MarkArrayDirty()
                             Force full resync

Correction Flow

Server detects desync:
    1. Log warning for debugging
    2. MarkArrayDirty() on FastArray
    3. Return without executing
    4. Client receives corrected state via replication
    5. Client OnRep_ rebuilds local state

Client Prediction

Optimistic Return Pattern

Clients assume success for better UX:

Client:
    Server_TryAddItem(Item)
    return true           // Optimistic - assume it works
    [Show item in UI immediately]

Server (if fails):
    MarkArrayDirty()      // Force correction

Client (on next replication):
    OnRep_InventoryList() // Corrects UI

Movement Interpolation

Remote players use smooth interpolation instead of snapping:

OnRep_ReplicatedServerPosition():
    |
    +-- GetLocalRole() == ROLE_SimulatedProxy?
            |
            NO --> Do nothing (local player)
            |
            YES --> Interpolate:
                    CurrentLocation --VInterpTo--> ServerPosition
                                    (smooth over time)

Prediction Limits

Can Predict Cannot Predict
Local movement Other player actions
Ability activation Damage results
UI state Item creation
Visual effects Container changes

Anti-Cheat Patterns

Server-Only Damage

Damage is never calculated on clients:

Server:
    AProjectileBase::OnCollisionOverlap
        |
        +-- HasAuthority()? NO --> return
        |
        +-- ProcessHit
                ApplyDamageToTarget(Target)
                ApplyPoiseDamage(Target)
                TriggerImpactEffects(Hit)

The One Deliberate Exception: Dev Cheat Relay

AEternalPlayer::Server_ExecuteCheat lets a client hand a command string to the server, which is exactly the shape "clients never drive server-side state" forbids. It exists because authority-requiring dev cheats are otherwise unreachable from a client on a dedicated session. Four guards keep it from becoming a hole:

Guard Effect
Requires a server-side CheatManager to exist Cheats must be enabled on that server (always in PIE; -EnableCheats otherwise). That existence check is the authorization gate
Dispatch through ProcessConsoleExec on the cheat manager Only declared cheat functions are reachable — never arbitrary engine console commands
256-character command cap Bounds abuse of the reliable channel
Compiled out of Shipping The relay does not exist in a shipping build at all

Treat it as a dev-build affordance, not a precedent — nothing in gameplay should take a command string from a client.

Collision Authority

Only the server processes meaningful collisions:

Projectile Collision Flow:
    +------------------+
    | OnCollisionOverlap |
    +------------------+
            |
            v
    +------------------+
    | HasAuthority()?  |----NO----> return (client ignores)
    +------------------+
            | YES
            v
    +------------------+
    | State == Active? |----NO----> return
    +------------------+
            | YES
            v
    +------------------+
    | HandleHit()      |
    +------------------+

Container Creation Authority

Subobjects can only be created on the server:

GetGlyphContainerForPlate(StonePlateItem):
    |
    +-- HasAuthority()?
            |
            NO --> return nullptr (clients cannot create)
            |
            YES --> CreateContainer()

Source References

Authority Checks

  • UItemContainerComponent::AddItemAtPosition_Implementation - Source/ProjectEternal/Private/Inventory/Components/ItemContainerComponent.cpp
  • AEternalCharacter::InitAbilityActorInfo - Source/ProjectEternal/Private/Character/EternalCharacter.cpp
  • AProjectileBase::OnCollisionOverlap - Source/ProjectEternal/Private/Combat/Projectiles/ProjectileBase.cpp

Server RPCs

  • UItemContainerComponent::Server_SwapItems_Implementation - Source/ProjectEternal/Private/Inventory/Components/ItemContainerComponent.cpp
  • UItemContainerComponent::Server_PlaceItemById_Implementation - Source/ProjectEternal/Private/Inventory/Components/ItemContainerComponent.cpp
  • AEternalPlayer::Server_Move_Implementation - Source/ProjectEternal/Private/Character/EternalPlayer.cpp

Validation

  • UItemContainerComponent::CanAddItemAt_Implementation - Source/ProjectEternal/Private/Inventory/Components/ItemContainerComponent.cpp
  • Desync detection in Server_SwapItems - Source/ProjectEternal/Private/Inventory/Components/ItemContainerComponent.cpp

GUID Operations

  • UItemObject::InitializeInstanceId - Source/ProjectEternal/Private/Inventory/Items/ItemObject.cpp
  • UPlayerGlyphComponent::TryEquipStonePlateAtSlot - Source/ProjectEternal/Private/Glyph/Components/PlayerGlyphComponent.cpp


Recent Changes

Date Change Reason
- Initial documentation Document server authority patterns
2026-07-03 Fixed PlayerGlyphComponent.cpp path; stripped line numbers from Source References Accurate, non-brittle references

Future Considerations

Enhancement Benefit Complexity
Rate Limiting Prevent RPC spam Low
Movement Validation Detect speed hacks Medium
Audit Logging Track suspicious activity Low
Hit Verification Server-side replay validation High
GUID Encryption Prevent GUID spoofing Medium