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:
| Area | Responsibility |
|---|---|
| Partition | K-d tree zone splitting and merge heuristics |
| Authority | Single-writer ownership tracking per entity |
| Ghost entities | Cross-boundary entity mirroring for rendering and collision |
| Handoff protocol | Entity transfer between zones with sequence fencing |
| Portal system | Cross-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
| Type | Description |
|---|---|
KdTree | K-d tree zone partitioning with split and merge operations |
SplitPolicy | Configuration for split thresholds and axis preference |
NetworkIdentity | Entity ownership with zone authority and sequence tracking |
GhostEntity | Read-only cross-boundary entity copy |
GhostCache | Storage and lifecycle management for ghost entities |
HandoffEnvelope | Entity state transfer between zones with sequence fencing |
AetherUrl | Parsed aether:// URL addressing worlds, zones, and spawn points |
Portal | Portal entity with activation state machine |
SessionHandoffEnvelope | Cross-world session transfer with token and player state |
PrefetchQueue | Priority-ordered asset download hints for portal transitions |