World & Networking

aether-network

QUIC transport and multiplayer networking

The aether-network crate provides Aether's multiplayer networking layer. Built on the QUIC protocol, it handles reliable and unreliable data transport, entity state synchronization, voice communication, and the prediction/reconciliation loop required for responsive multiplayer VR experiences.

QUIC Transport Overview

QUIC was chosen as the transport protocol for several reasons:

  • Multiplexed streams allow reliable and unreliable data to share a single connection without head-of-line blocking.
  • Built-in encryption via TLS 1.3 removes the need for a separate security layer.
  • Connection migration handles network changes gracefully, which is important for mobile and wireless VR headsets.
  • Low-latency handshakes with 0-RTT reconnection reduce join times.

The transport layer abstracts QUIC details behind a channel-based API, so higher-level systems work with logical channels rather than raw streams.

Key Types

NetworkTransport

The main entry point for networking. Manages connections, channels, and the send/receive loop.

use aether_network::{NetworkTransport, TransportConfig};

let config = TransportConfig {
    bind_address: "0.0.0.0:7777".parse().unwrap(),
    max_connections: 64,
    ..Default::default()
};

let mut transport = NetworkTransport::new(config).await?;

Connection

Represents a connection to a remote peer. Each connection has a unique identifier and tracks round-trip time, packet loss, and bandwidth metrics.

// Server: accept incoming connections
while let Some(connection) = transport.accept().await {
    println!(
        "Client connected: id={}, rtt={:.1}ms",
        connection.id(),
        connection.rtt_ms(),
    );
}

// Client: connect to a server
let connection = transport.connect("192.168.1.100:7777".parse()?).await?;

Channel

Channels provide logical separation for different types of data. Each channel can be configured for reliability, ordering, and priority.

use aether_network::{Channel, ChannelConfig, Reliability};

let state_channel = transport.create_channel(ChannelConfig {
    name: "entity_state",
    reliability: Reliability::Unreliable,
    ordered: false,
    priority: 10,
});

let events_channel = transport.create_channel(ChannelConfig {
    name: "game_events",
    reliability: Reliability::Reliable,
    ordered: true,
    priority: 5,
});

Features

Delta Compression

Entity state updates are delta-compressed against the last acknowledged state for each client. Only changed fields are transmitted, significantly reducing bandwidth for worlds with many entities.

use aether_network::DeltaCompression;

// Delta compression is enabled by default on state channels.
// Configure the baseline acknowledgment window:
let state_sync = StateSyncConfig {
    delta_compression: true,
    baseline_ack_window: 32, // number of baselines to track per client
    ..Default::default()
};

Interest Management

Not every client needs updates for every entity. The interest management system filters outgoing state updates based on spatial proximity, visibility, and custom relevance rules.

use aether_network::InterestManagement;

let interest = InterestManagement::new()
    .with_spatial_radius(100.0)    // only send entities within 100 units
    .with_update_rate_falloff(true) // reduce update rate for distant entities
    .with_priority_boost(|entity| {
        // Always send updates for entities the player is looking at
        entity.has::<LookedAt>()
    });

world.insert_resource(interest);

Voice Channels

Built-in voice communication uses Opus encoding and spatial attenuation. Players hear others based on distance and direction in the virtual world.

use aether_network::VoiceConfig;

let voice = VoiceConfig {
    codec: Codec::Opus,
    sample_rate: 48000,
    spatial: true,
    max_distance: 30.0,
    attenuation_model: AttenuationModel::InverseSquare,
    ..Default::default()
};

transport.enable_voice(voice);

Client-Side Prediction

To maintain responsiveness despite network latency, the client applies inputs locally before receiving server confirmation. The prediction system records input history and replays it when corrections arrive.

use aether_network::Prediction;

// The prediction system is configured per entity type:
world.insert(player, Prediction {
    enabled: true,
    max_buffered_inputs: 30,
    reconciliation_threshold: 0.01, // position error threshold to trigger correction
});

Server Reconciliation

The server is authoritative. When the server's state diverges from the client's predicted state beyond the configured threshold, the client snaps or interpolates to the corrected position and replays subsequent inputs.

use aether_network::Reconciliation;

world.insert_resource(Reconciliation {
    snap_threshold: 5.0,       // snap if error exceeds 5 units
    interpolation_speed: 10.0, // smooth corrections below snap threshold
});

Basic Usage

A minimal server that accepts connections and broadcasts entity state:

use aether_ecs::World;
use aether_network::{NetworkPlugin, NetworkRole, TransportConfig};

let mut world = World::new();

world.add_plugin(NetworkPlugin {
    role: NetworkRole::Server,
    config: TransportConfig {
        bind_address: "0.0.0.0:7777".parse().unwrap(),
        max_connections: 32,
        ..Default::default()
    },
});

// The NetworkPlugin registers systems that handle:
// - Accepting new connections
// - Receiving client inputs
// - Broadcasting entity state with delta compression
// - Running interest management filters
// - Processing voice data

loop {
    world.tick();
}

A minimal client that connects and processes state updates:

use aether_ecs::World;
use aether_network::{NetworkPlugin, NetworkRole, TransportConfig};

let mut world = World::new();

world.add_plugin(NetworkPlugin {
    role: NetworkRole::Client,
    config: TransportConfig {
        server_address: Some("192.168.1.100:7777".parse().unwrap()),
        ..Default::default()
    },
});

loop {
    world.tick();
}