Skip to content

The two-lane model

Every Silt room gives each peer two lanes over one WebTransport connection. Choosing the right lane for each message is the core skill of building on Silt.

Presence Events
Transport WebTransport datagrams One long-lived bidi stream
Delivery Unreliable, droppable Reliable, no drop
Ordering Latest-wins (no order) Ordered
Rate High — the ~20Hz firehose Discrete, as needed
SDK room.presence.set(state) room.events.send(event)
Receive room.on("presence", cb) room.on("event", cb)
Use for Cursor / camera / position Chat, actions, state changes

Presence carries a peer’s latest state, published as fast as you like. It is unreliable on purpose: if a datagram is lost, Silt does not retransmit it, because the next presence.set already supersedes it. Old positions have no value once a newer one exists.

// publish 20+ times a second; drops are harmless
room.presence.set({ x, y, heading });
room.on("presence", (peerId, state) => {
// state is whatever the sender last set — latest-wins
});
  • Latest-wins: the server keeps each peer’s last presence and relays it to everyone else. A new peer’s join snapshot includes each peer’s last presence.
  • Droppable: calling presence.set before the connection is ready (or between reconnects) is a no-op, not an error. The last value you set is re-published automatically when the connection comes back.
  • Presence callbacks fire for other peers, never your own echoes.

Events are reliable and ordered. They flow on a single bidirectional stream per peer, so they arrive in the order you sent them and nothing is dropped.

room.events.send({ type: "absorb", target: "bob" });
room.on("event", (peerId, event) => {
// delivered in order, exactly as sent
});

room.events.send throws if the room isn’t currently connected, so you know when a critical message couldn’t be queued.

A plain WebSocket gives you only the reliable lane. Push a 20Hz position firehose through it and a single lost packet head-of-line-blocks everything behind it: the connection stalls while TCP retransmits a position you no longer care about. Every later message — including the ones that mattered — waits.

Silt’s two lanes are independent. A lost presence datagram never blocks a reliable event, and a flood of presence updates never delays a chat message. That separation is only possible because WebTransport runs on QUIC, which gives you both an unreliable datagram channel and independent reliable streams over a single connection.

If losing this message is fine because a newer one is coming, it’s presence. If it has to arrive, in order, it’s an event.