Table of contents
Remote Avatar Replica Binding And Interpolation
Remote avatar replication binds authoritative AOI player snapshots to stable Bevy avatar entities, reconstructs appearance and animation state, and smooths snapshot movement without changing server-owned positions.
Ownership And Data Flow
The path has three owners:
- AOI client state owns visible player snapshots, interest-bucket membership, snapshot age, and the
RealtimeReplicaDirectory. - Player-preview runtime owns
RemotePlayerAvatar, its render root and paperdoll state, and the movement interpolation ledger. - The replica directory records the bridge between them: network entity id, snapshot source, bucket, age, Bevy entities, interpolation diagnostics, and missing/stale reasons.
The normal flow is:
server AOI snapshot
-> client staging and ordered snapshot apply
-> interest buckets / players_by_entity_id
-> replica directory snapshot entry
-> desired RemotePlayerAvatar keyed by network entity_id
-> scene-layer placement and interpolated transform
-> render binding + interpolation state written back to the directory
The local player is also represented in the directory when it is not present in an interest bucket, but remote-avatar sync filters the local entity id and never spawns a second avatar for it.
Replica Directory Binding
RealtimeReplicaDirectory is keyed by PlayerSnapshot.entity_id. Each entry records the player snapshot, source (LocalPlayer, legacy VisiblePlayers, or InterestBucket), optional bucket id and snapshot age, render entity/root, interpolation state, and diagnostic reasons.
Interest updates maintain all bucket memberships for an entity. If an entity appears in more than one bucket, refresh_interest_entity selects the highest bucket id currently present. Removing that membership falls back to the remaining bucket; removing the final membership removes the visible-player and directory entry unless the entity is the local player.
Snapshot upsert refreshes player/source/age data and clears stale or missing reasons while preserving an existing render binding and interpolation state. This is essential: an AOI delta must update an existing avatar rather than orphaning its Bevy entities.
Remote sync builds a desired id set from visible players, sorted by entity id. A keyed entity index reuses the first live avatar for an id and ignores entities already scheduled for despawn. Missing desired ids spawn one RemotePlayerAvatar; ids no longer desired despawn both the render root and avatar entity. Edit-mode entry also removes all remote avatars.
Appearance, Animation, And Scene Placement
Each remote avatar starts from the local preview's runtime/render template but stores its own appearance, playback, environment, motion, and render entities. Replicated appearance and gameplay state are applied through the shared player-preview replication adapters.
On each sync:
- character name and replicated gameplay/appearance are refreshed;
- the avatar root is recreated if its entity became invalid;
- the root is parented to the resolved player scene layer;
- hidden player layers make the avatar hidden without deleting it;
- precise millitile coordinates are used when
has_precise_positionis true, otherwise tile coordinates are used; - Z is always resolved from the authored scene layer;
- replicated locomotion/action state selects and advances the remote animation.
Visual render plans are cached by remote entity id and a signature derived from template, appearance, and playback state. Render entity handles are intentionally excluded from that signature.
Interpolation And Snap Thresholds
The movement ledger stores authoritative samples with tile, world translation, velocity, snapshot tick id, and optional network time. Samples at or below the latest authoritative tick are ignored.
Current tuning is source-defined:
| Setting | Value |
|---|---|
| Minimum snapshot interval | 0.05s |
| Maximum snapshot interval | 0.20s |
| Default interval without timing | 0.12s |
| Maximum extrapolation | 0.10s |
| Teleport threshold | More than 2 tiles on either axis |
| Position precision | 1000 millitiles per tile |
The first sample snaps to authority. Later samples also snap when:
- the raw interval since the previous sample is greater than
0.20s; - either tile-axis delta is greater than two tiles;
- the scene-layer Z translation changed;
- previous tile or translation state is unavailable.
Otherwise the renderer deliberately trails by one sample interval: it interpolates from the previous translation to the current translation over the clamped interval. After reaching the current sample it extrapolates for at most 0.10s, using authoritative velocity when nonzero or velocity derived from the two translations. Z always comes from the current authoritative sample.
For a single sample, extrapolation begins only after the default 0.12s interval and is still capped at 0.10s. A tile jump of exactly two tiles is eligible for blending; the snap comparison is strictly greater than two.
Lifecycle And State Transitions
AOI enter
An interest-bucket update inserts the player and directory entry. The next avatar sync spawns/reuses the keyed avatar, initializes replicated appearance/gameplay state, creates its root, and records a render binding.
Snapshot update
The directory updates the snapshot without losing the binding. When snapshot tick or tile changes, the motion ledger records a newer authoritative sample. The frame transform is interpolated or snapped according to the thresholds above, and interpolation diagnostics are written back to the directory.
AOI leave
Bucket membership removal deletes the directory entry after the final membership is gone. Remote sync sees the id absent from its desired set and schedules the root and avatar for despawn. Scheduled entities are excluded from the keyed reuse index during that frame.
Full sync, transfer, or disconnect
Full sync clears the old interest projection before applying its replacement buckets. Transfer preparation and server disconnect clear interest buckets, visible-player indexes, the replica directory, and snapshot staging. This prevents old-zone avatars from remaining bound in the destination scene.
Missing render root
If an avatar has no usable render root after synchronization, the connection marks its directory entry with missing_reason = "remote avatar has no render root yet" and clears render/interpolation binding. A later successful snapshot upsert or render binding clears the missing reason.
Extending Replica Presentation
When adding replicated avatar data or presentation:
- Add it to
PlayerSnapshotor the nested replicated appearance/gameplay contract and update wire codecs. - Apply it through
apply_replicated_appearance_stateorapply_replicated_gameplay_state, keeping preview-only state out of the wire shape. - Include every visual-affecting value in the remote render signature so cached plans invalidate.
- Keep avatar identity keyed by network
entity_id; character name and character id are metadata, not Bevy lifecycle keys. - Preserve directory render bindings on snapshot upsert.
- Define cleanup for AOI leave, full sync, transfer, disconnect, and editor mode.
- If movement tuning changes, update
MovementInterpolationConfig, remote constants, snap tests, and diagnostics together.
Rules And Invariants
- At most one live remote avatar is selected for each network entity id.
- The local network entity id must be filtered from remote-avatar spawning.
- Snapshot presence, not legacy spawn/despawn packets, owns current remote-player presence.
- Snapshot upsert must preserve a valid render binding; final AOI removal must remove the directory entry.
- Interpolation never writes a predicted position back into
PlayerSnapshotor AOI state. - Stale authoritative sample ticks must not rewind motion.
- Layer changes and long/missing snapshot intervals snap instead of blending across incompatible space.
- Root visibility follows scene player-layer visibility.
- Root and avatar cleanup must both run when they are distinct entities.
Validation And Debugging
Run focused coverage with:
cargo test -p aether bucket_updates_refresh_replica_directory_from_touched_memberships
cargo test -p aether replica_directory_upsert_preserves_render_binding
cargo test -p aether apply_state_snapshot_datagram_preserves_replica_render_binding_on_bucket_delta
cargo test -p aether remote_avatar_motion_interpolates_then_extrapolates_from_authoritative_snapshots
cargo test -p aether remote_avatar_motion_snaps_large_teleports_instead_of_blending
cargo test -p aether remote_avatar_entity_index_skips_scheduled_entities_and_keeps_first_available_match
The client emits useful placement diagnostics:
applied realtime state snapshot on clientincludes snapshot/tick ids, received and resulting player counts, local entity id, and packet label;evaluated desired remote avatars on clientreports desired count and local id;spawned remote avatar on clientreports entity id, character name, root, and desired count;updated remote avatar placement on clientreports tile, root, translation, and visibility.
For a missing or jittering avatar, inspect one directory entry and compare:
snapshot_source,bucket_id, andage_ms;- snapshot entity id, tile, precise position, velocity, and the global last snapshot tick/network time;
render_entity,render_root,missing_reason, andstale_reason;- interpolation authoritative/desired translations, interval, and last applied tick;
- scene player-layer visibility and the resolved root parent.
Typical diagnoses:
- Directory entry absent: inspect AOI membership, staging/repair, and final bucket removal.
- Entry present with no render entity: inspect avatar spawn/root creation and
missing_reason. - Avatar duplicates: inspect keyed entity reuse and entities already scheduled for despawn.
- Repeated snapping: raw snapshot gaps exceed
0.20s, tile deltas exceed two, or layer Z is changing. - Smooth motion behind authority: one-interval interpolation delay is intentional.
- Overshoot after movement stops: inspect replicated velocity; extrapolation is bounded to
0.10s.
mark_stale_missing_ids and stale_reason exist in the directory model, but the current remote-avatar render path actively writes only missing-root diagnostics. Do not assume stale reasons are populated unless the caller explicitly marks them.