commit d1d3dd943b491e839ed57cd263c57aeeaba736f2
parent 364b84de718e4fe30c33276a9afb09cec7d2c0ec
Author: William Casarin <jb55@jb55.com>
Date: Fri, 27 Feb 2026 13:57:15 -0800
nostrverse: reject out-of-order presence events
Track event_created_at on RoomUser and skip presence updates
whose created_at is older, preventing stale replaceable events
from overwriting newer positions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
2 files changed, 11 insertions(+), 0 deletions(-)
diff --git a/crates/notedeck_nostrverse/src/presence.rs b/crates/notedeck_nostrverse/src/presence.rs
@@ -189,20 +189,27 @@ pub fn poll_presence(
}
let velocity = nostr_events::parse_presence_velocity(note);
+ let created_at = note.created_at();
// Update or insert user
if let Some(user) = users.iter_mut().find(|u| u.pubkey == pubkey) {
+ // Skip stale events — replaceable events can arrive out of order
+ if created_at <= user.event_created_at {
+ continue;
+ }
// Update authoritative state; preserve display_position for smooth lerp
user.position = position;
user.velocity = velocity;
user.update_time = now;
user.last_seen = now;
+ user.event_created_at = created_at;
} else {
let mut user = RoomUser::new(pubkey, "anon".to_string(), position);
user.velocity = velocity;
user.display_position = position; // snap on first appearance
user.update_time = now;
user.last_seen = now;
+ user.event_created_at = created_at;
users.push(user);
}
changed = true;
diff --git a/crates/notedeck_nostrverse/src/room_state.rs b/crates/notedeck_nostrverse/src/room_state.rs
@@ -231,6 +231,9 @@ pub struct RoomUser {
pub is_self: bool,
/// Monotonic timestamp (seconds) of last presence update
pub last_seen: f64,
+ /// Nostr event created_at of the latest accepted presence event.
+ /// Used to ignore out-of-order replaceable events.
+ pub event_created_at: u64,
/// Runtime: renderbud scene object handle for avatar
pub scene_object_id: Option<ObjectId>,
/// Runtime: loaded model handle for avatar
@@ -248,6 +251,7 @@ impl RoomUser {
update_time: 0.0,
is_self: false,
last_seen: 0.0,
+ event_created_at: 0,
scene_object_id: None,
model_handle: None,
}