Network layerserver-authoritative · C0

Replication

Multiplayer and multi-user-editor support built into the data layer: change tracking, delta encoding, interest management, authority, events/RPCs, snapshots, and client-side prediction. Server-authoritative from the ground up, with per-field conditions driving everything.

01

Change Tracking

Every mutation during Simulate is recorded.

02

Delta Encoding

Field deltas encoded per component schema.

03

Interest Mgmt

RelevanceSet filters what each client sees.

04

Authority

AuthorityTable resolves who may write what.

05

Events / RPCs

One-shot messages outside state deltas.

06

Snapshots

Full state baselines for resync and joins.

07

Prediction

Client-side prediction + reconciliation.

Schema first

Declare which fields replicate, how they encode, and under what conditions. The handshake serializes the whole schema set so every client can decode any delta it receives. Hand-rolled schemas compose with SchemaBuilder. Or use #[derive(Replicate)] and let the macro register real field accessors for you.

schema.rs
1use pulsar_scenedb::{ReplicationRegistry, ReplicationEncoding, ReplicationCondition,
2 EventChannel, SchemaBuilder, Component};
3
4let mut registry = ReplicationRegistry::new();
5
6let builder = registry.register::<Transform>();
7registry.insert(
8 builder.whole_field("matrix", ReplicationEncoding::Pod, ReplicationCondition::Always)
9);
10
11let builder = registry.register::<Health>();
12registry.insert(
13 builder.whole_field("value", ReplicationEncoding::DeltaCompressed, ReplicationCondition::SimulatedOnly)
14);
15
16// Serialize schemas for the connection handshake.
17let handshake = registry.handshake_message();
18let remote_registry = ReplicationRegistry::from_handshake(&handshake).unwrap();

One tick, per-client outputs

Track changes during Simulate, drain into a Delta at the frame boundary, then filter and encode per client. Relevance, conditions, and event direction all enforced in one pass.

server_tick.rs
1fn server_tick(
2 world: &mut World,
3 witness: &CpuSimulateWitness,
4 registry: &ReplicationRegistry,
5 authority: &AuthorityTable,
6 clients: &[ClientId],
7 spatial_cells: &[SpatialCell],
8 entity_cell_map: &EntityCellMap,
9 liveness: &LivenessSnapshot,
10 scratch: &mut Scratchpad,
11) -> Vec<(Delta, Vec<EventBatch>)> {
12 // 1. Track all changes and drain into a Delta in one call.
13 let mut tracker = ChangeTracker::new();
14 let delta = witness.run_tracked(world, &mut tracker, |world, tracker| {
15 run_systems(world, tracker);
16 });
17
18 // 2. Build per-client outputs.
19 let mut outputs = Vec::new();
20 for &client in clients {
21 // Spatial relevance, resolved to ECS entities via EntityCellMap.
22 let relevance = RelevanceSet::from_frustum_mapped(
23 spatial_cells, &client_frustum(client), liveness, scratch, entity_cell_map,
24 );
25
26 // Filter by relevance + conditions.
27 let view = relevance.filter(&delta, authority, registry, client);
28
29 // Build event batch with direction enforcement.
30 let batch = events_to_batch(&view, delta.frame, registry, ClientId(0), client);
31
32 outputs.push((delta.clone(), batch.into_iter().collect()));
33 }
34 outputs
35}

Prediction & reconciliation

The reconciler keeps a history ring buffer of server snapshots and a queue of unacknowledged local inputs. When a server delta arrives, it discards acknowledged inputs and replays the remaining predicted inputs on top of the corrected world.

client.rs
1let mut reconciler = Reconciler::new();
2
3// Each local tick, push the player's input.
4reconciler.push_input(ClientInput {
5 frame: local_frame,
6 entity: player_entity,
7 component: component_id::<Movement>(),
8 field_data: vec![(0, serialize_movement(&input))],
9});
10
11// When a server delta arrives, apply it, then reconcile.
12server_delta.apply(&mut world, &registry).unwrap();
13reconciler.reconcile(&server_delta, &mut world, |world, input| {
14 apply_input_to_world(world, input);
15});

Pattern library

Server-authoritative projectile

position/velocity replicated Always · on_impact is a Multicast event

Client-authoritative input

move_direction ClientAuthority delta-compressed · on_jump is a ClientToServer event

Multi-user editor metadata

selected Shared · custom_properties Shared, serialized Vec

Visibility-gated game state

world_position Always · minimap_blips OwnerOnly · fog_of_war_reveal SkipOwner · proxy_mesh SimulatedOnly GpuHandle

Wire encodings

EncodingValueWhat travels
Pod0Plain memcpy, the fastest path
Serialized1Self-framing owned/heap data (String, Vec<T>, Option<T>)
GpuHandle2Only the 8-byte handle index travels, the resource never moves
DeltaCompressed3Stateful delta compression per field
Event4One-shot RPC, never in state deltas
Opaque5Engine-defined byte blob

Want the full replication deep-dive?

Replication Docs