Networking
Aether's QUIC-based transport layer with delta compression, interest management, voice channels, and client-side prediction reconciliation.
Aether's networking layer provides low-latency, encrypted communication between clients and servers. Built on QUIC via the quinn crate, it offers multiplexed reliable streams and unreliable datagrams in a single connection, with built-in TLS 1.3 encryption and connection migration. This guide covers the transport architecture, state synchronization, interest management, and voice system.
Overview
The networking system is split across two crates:
aether-network-- Policy abstractions for interest management, delta coding, prediction, voice, and theRuntimeTransporttrait.- QUIC transport backend -- A concrete
RuntimeTransportimplementation usingquinnfor socket I/O.
This separation means the engine's networking logic is transport-agnostic. You can swap in a different transport (e.g., InMemoryTransport for testing) without changing the synchronization or interest code.
QUIC Transport
Architecture
The transport is organized into composable components:
use aether_network::quic::{QuicTransport, QuicConfig};
let config = QuicConfig::from_env();
let transport = QuicTransport::new(config).await?;
Internally, QuicTransport manages:
QuicServer-- Accepts incoming client connections and maintains a connection map.QuicClient-- Connects to a server and holds a single connection.QuicConnection-- Wraps aquinn::Connectionwith methods for reliable and unreliable messaging.- TLS certificate handling (self-signed for development, file-based for production).
Handshake Protocol
Every connection begins with a handshake that authenticates the client:
Client -> Server: [MAGIC "AETH"] [VERSION 1] [CLIENT_ID 8 bytes] [TOKEN_LEN 2 bytes] [TOKEN N bytes]
Server -> Client: [MAGIC "AETH"] [VERSION 1] [STATUS 1 byte] [SERVER_TICK 8 bytes]
Status codes: 0 = OK, 1 = Rejected, 2 = Version Mismatch.
The handshake completes before any game data flows, ensuring both sides agree on protocol version and authentication.
Connection Lifecycle
Connections progress through a state machine: Disconnected -> Connecting -> Handshaking -> Connected. From Connected, a lost connection enters Reconnecting, which either retries within the timeout or drops to Disconnected.
Data Channels
Messages are sent through two delivery modes:
use aether_network::transport::{Reliability, RuntimeTransport};
// Reliable ordered delivery via QUIC bi-directional stream
transport.send(client_id, &rpc_bytes, Reliability::ReliableOrdered).await?;
// Unreliable delivery via QUIC datagram
transport.send(client_id, &state_bytes, Reliability::UnreliableDatagram).await?;
Reliable messages use length-prefixed bi-directional QUIC streams. Unreliable messages use the QUIC datagram extension, with automatic fallback to an unreliable stream if datagrams are not supported or the message exceeds MAX_DATAGRAM_SIZE (1,200 bytes).
Delta Compression
To minimize bandwidth, Aether sends only the changes between successive state snapshots rather than full entity state each tick.
XOR-Based Diffing
The delta codec computes a byte-level XOR diff between the previous and current state:
use aether_network::delta::DeltaCodec;
let codec = DeltaCodec::new();
let delta = codec.encode_delta(&previous_state, ¤t_state);
// On the receiving end
let reconstructed = codec.apply_delta(&previous_state, &delta);
assert_eq!(reconstructed, current_state);
Quantized Encoding
Position and rotation values are quantized to reduce payload size:
| Data | Encoding | Precision |
|---|---|---|
| Position | Fixed-point, 1 mm step | Sub-millimeter |
| Rotation | 10 bits per component | ~0.35 degree resolution |
This quantization is applied before delta encoding, further reducing the diff size since small movements produce minimal byte changes.
Interest Management
Not every client needs every entity update. The interest management system assigns entities to distance-based priority buckets relative to each client:
| Bucket | Update Rate | Description |
|---|---|---|
| Critical | Every tick | Entities the player is directly interacting with |
| High | Every 2 ticks | Nearby entities within close range |
| Medium | Every 4 ticks | Entities at moderate distance |
| Low | Every 8 ticks | Distant but visible entities |
| Dormant | No updates | Out of range, no data sent |
use aether_network::interest::{InterestManager, BandwidthBudget};
let mut interest = InterestManager::new();
let budget = BandwidthBudget::new(64_000); // 64 KB/s per client
// Each tick, compute priorities for a client
let priorities = interest.compute_priorities(client_id, &entity_positions);
// Select the top-N entities that fit within the bandwidth budget
let selected = interest.select_within_budget(&priorities, &budget);
The system uses top-N prioritization: after assigning buckets, it selects the most important entities that fit within the client's bandwidth budget.
Voice Channels
Aether supports spatial voice communication using the unreliable datagram channel:
use aether_network::voice::{VoiceConfig, JitterBuffer};
let config = VoiceConfig::from_env();
let mut jitter_buffer = JitterBuffer::new(config);
// Incoming voice datagrams are buffered to smooth out jitter
jitter_buffer.push(voice_datagram);
// The audio system reads smoothed samples
if let Some(frame) = jitter_buffer.pop() {
audio_output.play(frame);
}
Voice datagrams carry metadata including the sender ID, sequence number, and spatial position for 3D audio spatialization. The jitter buffer reorders packets and fills gaps to produce smooth audio output.
Client-Side Prediction
The prediction system works with the world runtime's InterpolationBuffer to hide latency:
use aether_network::prediction::{PredictionQueue, ReconciliationStatus};
let mut prediction = PredictionQueue::new();
// Client predicts the result of local input
prediction.push_predicted(tick, input, predicted_state);
// When the server confirms a tick
match prediction.reconcile(server_tick, &server_state) {
ReconciliationStatus::Consistent => {
// Prediction was correct, discard old entries
}
ReconciliationStatus::Corrected { delta } => {
// Apply correction and replay subsequent inputs
}
}
The reconciliation flow:
- The client applies inputs locally and stores predicted states.
- The server processes the same inputs authoritatively and broadcasts results.
- The client compares server state to its prediction for that tick.
- If they match, old predictions are pruned.
- If they diverge, the client rewinds to the server state and replays all inputs since that tick.
Configuration
All networking parameters are controlled via environment variables:
| Variable | Default | Description |
|---|---|---|
AETHER_NET_BIND_ADDR | 0.0.0.0:4433 | Server bind address |
AETHER_NET_SERVER_ADDR | 127.0.0.1:4433 | Client connect address |
AETHER_NET_CERT_PATH | (self-signed) | TLS certificate PEM path |
AETHER_NET_KEY_PATH | (self-signed) | TLS private key PEM path |
AETHER_NET_CONNECT_TIMEOUT_SECS | 10 | Connection timeout |
AETHER_NET_IDLE_TIMEOUT_SECS | 30 | Idle connection timeout |
AETHER_NET_RECONNECT_TIMEOUT_SECS | 30 | Reconnect window |
Key Types
| Type | Description |
|---|---|
QuicTransport | QUIC-based RuntimeTransport implementation |
QuicConfig | Connection settings loaded from environment variables |
QuicConnection | Wrapper around quinn::Connection with send/recv methods |
RuntimeTransport | Trait abstracting reliable and unreliable message delivery |
DeltaCodec | XOR-based delta compression encoder/decoder |
InterestManager | Distance-based entity priority bucketing |
BandwidthBudget | Per-client bandwidth allocation for interest selection |
PredictionQueue | Client-side prediction state with reconciliation |
JitterBuffer | Voice datagram reordering and smoothing buffer |
Reliability | Enum: ReliableOrdered or UnreliableDatagram |