From 75d8c7e3cbf3102042672cd9087cf9058531bb42 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:58:18 +0100 Subject: [PATCH 1/2] feat: per-realm scene-listener AoI, and reassignment without reconnecting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A scene listener announced one realm for the whole connection, and its parcel set was fixed until it reconnected. Neither holds for an authoritative server: it cohosts scenes from several realms at once, and its scene set changes while it runs. Replace the handshake's `realm` + `parcel_rects` with a repeated SceneListenerAoi, one entry per realm. Parcels only mean anything inside a realm — every world numbers its own from 0,0 — so a server cohosting cozyfarm.dcl.eth and towerofmadness.dcl.eth has two different scenes at 0,0, which a single flat parcel set cannot express. Keying the AoI by realm keeps that to one connection, which the wallet-unique session rule and the per-IP listener cap both require. Add SceneListenerUpdate (oneof 9) to replace the announced AoI in place on a live connection, so a scene loading or unloading costs neither a reconnect nor a re-authentication. --- proto/decentraland/pulse/pulse_client.proto | 38 ++++++++++++++++----- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/proto/decentraland/pulse/pulse_client.proto b/proto/decentraland/pulse/pulse_client.proto index aca97cbd..6498ccdf 100644 --- a/proto/decentraland/pulse/pulse_client.proto +++ b/proto/decentraland/pulse/pulse_client.proto @@ -74,17 +74,38 @@ message ParcelRect { sint32 max_z = 4; } -// Scene-listener connect: same signed-fetch auth chain as HandshakeRequest, plus an -// immutable parcel-set area of interest. No initial state — a listener is never a subject. +// One realm's slice of a scene listener's area of interest. Parcels only mean anything +// within a realm — every world numbers its own parcels from 0,0 — so a listener that +// observes several realms, as an authoritative server cohosting scenes from several worlds +// does, announces one of these per realm rather than one realm for the whole connection. +message SceneListenerAoi { + // Non-empty realm identifier — same rules as TeleportRequest.realm. + string realm = 1; + // This realm's parcels as inclusive rects. The sum of rect areas across every announced + // realm is capped server-side (SceneListener:MaxParcels) — overlaps count per rect, so + // announce disjoint rects. + repeated ParcelRect parcel_rects = 2; +} + +// Scene-listener connect: same signed-fetch auth chain as HandshakeRequest, plus a +// parcel-set area of interest per realm. No initial state — a listener is never a subject. message SceneListenerHandshakeRequest { // Signed-fetch headers JSON — identical shape to HandshakeRequest.auth_chain. bytes auth_chain = 1; - // AoI realm partition — same rules as TeleportRequest.realm. - string realm = 2; - // Announced AoI as inclusive parcel-coordinate rects; fixed for the connection - // lifetime. The sum of rect areas is capped server-side (SceneListener:MaxParcels) — - // overlaps count per rect, so announce disjoint rects. - repeated ParcelRect parcel_rects = 3; + // was `string realm` / `repeated ParcelRect parcel_rects`, one realm per connection + reserved 2, 3; + reserved "realm", "parcel_rects"; + // Announced AoI, one entry per realm; replaceable afterwards with SceneListenerUpdate. + repeated SceneListenerAoi aoi = 4; +} + +// Scene-listener AoI reassignment: replaces the area of interest announced at connect, in +// place, on an authenticated scene-listener connection. Only valid from a peer that +// authenticated with SceneListenerHandshakeRequest. +message SceneListenerUpdate { + // The new AoI, replacing (not extending) the current one — realms absent from it are no + // longer observed. Same rules and SceneListener:MaxParcels budget as the handshake's. + repeated SceneListenerAoi aoi = 1; } message ClientMessage { @@ -97,5 +118,6 @@ message ClientMessage { EmoteStop emote_stop = 6; TeleportRequest teleport = 7; SceneListenerHandshakeRequest scene_listener_handshake = 8; + SceneListenerUpdate scene_listener_update = 9; } } From af64bd8e4d9acc4ba545178d9f31a14531d28b79 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Thu, 27 Aug 2026 13:29:59 +0100 Subject: [PATCH 2/2] refactor: put the scene-listener AoI on handshake field 2, drop the reserved fields No scene listener was ever deployed on the single-realm handshake, so there is nothing on the wire to stay compatible with. Reusing the field numbers keeps the message at what it would have been written as fresh. --- proto/decentraland/pulse/pulse_client.proto | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/proto/decentraland/pulse/pulse_client.proto b/proto/decentraland/pulse/pulse_client.proto index 6498ccdf..8c68cd77 100644 --- a/proto/decentraland/pulse/pulse_client.proto +++ b/proto/decentraland/pulse/pulse_client.proto @@ -92,11 +92,8 @@ message SceneListenerAoi { message SceneListenerHandshakeRequest { // Signed-fetch headers JSON — identical shape to HandshakeRequest.auth_chain. bytes auth_chain = 1; - // was `string realm` / `repeated ParcelRect parcel_rects`, one realm per connection - reserved 2, 3; - reserved "realm", "parcel_rects"; // Announced AoI, one entry per realm; replaceable afterwards with SceneListenerUpdate. - repeated SceneListenerAoi aoi = 4; + repeated SceneListenerAoi aoi = 2; } // Scene-listener AoI reassignment: replaces the area of interest announced at connect, in