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 — the unreliable lane
Section titled “Presence — the unreliable lane”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 harmlessroom.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.setbefore 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 — the reliable lane
Section titled “Events — the reliable lane”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.
Why not just one lane?
Section titled “Why not just one lane?”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.
A rule of thumb
Section titled “A rule of thumb”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.
- Quickstart — both lanes in a working example.
- Wire protocol — how the lanes are framed on the wire.