Spatial Zoning

How Aether partitions worlds across server processes with zone splitting, ghost entities, cross-zone physics, and the portal system for cross-world travel.

Large VR worlds cannot fit on a single server. Aether's zoning system partitions the world into spatial zones, each managed by a separate server process. The aether-zoning crate handles zone topology, entity handoff between zones, ghost entities at boundaries, and a portal system for cross-world teleportation.

Overview

Zoning addresses two challenges:

  • Horizontal scaling -- Splitting a single world across multiple servers so that each handles a manageable number of entities and players.
  • Cross-world travel -- Moving players between entirely separate world instances hosted on different servers.

The system is composed of five areas:

AreaResponsibility
PartitionK-d tree zone splitting and merge heuristics
AuthoritySingle-writer ownership tracking per entity
Ghost entitiesCross-boundary entity mirroring for rendering and collision
Handoff protocolEntity transfer between zones with sequence fencing
Portal systemCross-world teleportation with URL addressing

Zone Splitting and Merging

Aether uses a K-d tree to partition world space. Each leaf node is a zone assigned to a server process. When a zone becomes overloaded (too many entities or players), the system splits it along an axis. When adjacent zones are underutilized, they merge.

Split Heuristics

The KdTree::choose_axis routine selects the best split axis based on entity distribution:

use aether_zoning::partition::{KdTree, SplitPolicy};

let policy = SplitPolicy {
    max_entities_per_zone: 500,
    split_axis_preference: AxisPreference::LongestExtent,
    merge_threshold: MergeThreshold {
        min_combined_entities: 100,
        cooldown_ms: 30_000,
    },
};

let mut tree = KdTree::new(world_bounds, policy);

// When entity count exceeds the threshold, split
if let Some(split_result) = tree.evaluate_split(zone_id) {
    tree.apply_split(split_result)?;
    // Two new child zones are created, entities are redistributed
}

The split result includes the chosen axis, the split coordinate, and the entity assignment for each new child zone.

Merge Policy

Adjacent zones can merge when their combined entity count drops below min_combined_entities. A cooldown timer prevents rapid split/merge oscillation.

Authority and Ownership

Each entity has a single authoritative zone at any given time. The NetworkIdentity type tracks which zone owns an entity:

use aether_zoning::authority::NetworkIdentity;

let identity = NetworkIdentity {
    entity_id: 42,
    authority_zone: zone_a_id,
    sequence: 1,
};

// Only the authoritative zone may modify this entity's state
assert!(identity.is_authoritative_in(zone_a_id));

The sequence number increments on each authority transfer, preventing stale updates from being applied after a handoff.

Ghost Entities

When an entity is near a zone boundary, neighboring zones need a read-only copy for rendering and collision detection. These copies are called ghost entities.

use aether_zoning::ghost::{GhostCache, GhostEntity};

let mut cache = GhostCache::new();

// The authoritative zone pushes ghost state to neighbors
cache.upsert(GhostEntity {
    entity_id: 42,
    source_zone: zone_a_id,
    position: Vec3::new(100.0, 0.0, 50.0),
    rotation: Quat::IDENTITY,
    visual_data: entity_visual_snapshot,
});

// The receiving zone reads the ghost for rendering
if let Some(ghost) = cache.get(42) {
    renderer.draw_ghost(ghost);
}

Ghost entities are automatically created and removed as entities move toward or away from zone boundaries. They are read-only in the receiving zone -- only the authoritative zone can modify the real entity.

Cross-Zone Entity Handoff

When an entity crosses a zone boundary, ownership transfers from the source zone to the destination zone. The HandoffEnvelope carries the entity state and transfer metadata:

use aether_zoning::protocol::{HandoffEnvelope, HandoffResult};

let envelope = HandoffEnvelope {
    entity_id: 42,
    source_zone: zone_a_id,
    target_zone: zone_b_id,
    entity_state: serialized_state,
    sequence_fence: current_sequence,
    timeout_ms: 5_000,
};

// Send envelope to the target zone
let result = handoff_coordinator.initiate(envelope).await?;

match result {
    HandoffResult::Accepted { new_sequence } => {
        // Entity is now owned by zone B
    }
    HandoffResult::Rejected { reason } => {
        // Entity remains in zone A
    }
    HandoffResult::TimedOut => {
        // Retry or keep entity in zone A
    }
}

The sequence fence ensures that no stale updates from the source zone are applied after the handoff completes.

Cross-Zone Physics and Combat

When interactions span zone boundaries, the target zone has final authority. For physics, the initiating zone proposes the interaction and the target zone decides the outcome via CrossZonePhysicsDecision. For combat, the target server authoritatively resolves damage via CrossZoneCombatDecision, preventing latency-based exploits.

Portal System

Portals enable players to travel between entirely different world instances. Unlike zone handoffs (which are intra-world), portals handle cross-world teleportation.

The aether:// URL Scheme

Every world, zone, and spawn point is addressable via an aether:// URL:

aether://worlds.aether.io/my-world/zone-3?spawn=10,0,5&instance=abc123

Components:

  • host -- Server authority (e.g., worlds.aether.io)
  • world_id -- Unique world identifier
  • zone_id -- Optional target zone
  • spawn -- Optional spawn coordinates
  • instance -- Optional specific instance ID
use aether_zoning::aether_url::AetherUrl;

let url = AetherUrl::parse("aether://worlds.aether.io/my-world?spawn=10,0,5")?;
assert_eq!(url.host(), "worlds.aether.io");
assert_eq!(url.world_id(), "my-world");
assert_eq!(url.spawn(), Some(Vec3::new(10.0, 0.0, 5.0)));

Portal Activation

A portal entity defines a source and destination URL, a physical shape for proximity detection, and an activation mode:

use aether_zoning::portal::{Portal, ActivationMode, PortalShape};

let portal = Portal::new(
    AetherUrl::parse("aether://local/current-world")?,
    AetherUrl::parse("aether://worlds.aether.io/destination-world")?,
    PortalShape::Sphere { radius: 3.0 },
    ActivationMode::ProximityAndConfirm,
);

The activation flow progresses through states:

Idle -> Proximity (player enters radius)
     -> Activating (player confirms)
     -> TransitionStarted (handoff begins)
     -> Completed (arrived at destination)

If the handoff fails, the portal enters a cooldown before returning to Idle.

Session Handoff

Cross-world travel requires transferring the player's session to the target server. A SessionHandoffEnvelope carries an opaque SessionToken (authorizing the player on the destination), a serialized PlayerStateSnapshot, source and destination URLs, and a timeout. The target server validates the token and admits the player with their transferred state.

Asset Prefetching

When a player approaches a portal, the system emits PrefetchHint items (asset URL, priority, estimated size) into a PrefetchQueue. The queue deduplicates hints and orders them by priority so the client can begin downloading the most important destination assets before the transition starts.

Key Types

TypeDescription
KdTreeK-d tree zone partitioning with split and merge operations
SplitPolicyConfiguration for split thresholds and axis preference
NetworkIdentityEntity ownership with zone authority and sequence tracking
GhostEntityRead-only cross-boundary entity copy
GhostCacheStorage and lifecycle management for ghost entities
HandoffEnvelopeEntity state transfer between zones with sequence fencing
AetherUrlParsed aether:// URL addressing worlds, zones, and spawn points
PortalPortal entity with activation state machine
SessionHandoffEnvelopeCross-world session transfer with token and player state
PrefetchQueuePriority-ordered asset download hints for portal transitions