Room list, occupancy, scenes and other sessions

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-29 02:20:20 -07:00
1 parent 0a4268bb62
commit a229f8c5d0
1 file changed
+46 -11
+46 -11
View File
@@ -27,8 +27,10 @@ In order, as captured:
3. `test.getRoomState({room_instance_id})`: the scene: `room_hash`, and `room_state.room_contents`, a list of 3. `test.getRoomState({room_instance_id})`: the scene: `room_hash`, and `room_state.room_contents`, a list of
`[furniture instance ID, product ID, placement]`, where placement is a string of numbers (position, rotation, `[furniture instance ID, product ID, placement]`, where placement is a string of numbers (position, rotation,
scale, parent slot...). The client caches it by hash. scale, parent slot...). The client caches it by hash.
4. IMQ: subscribe to `/chat/<chatId>` (with property `listen_to_self: false`), then `inv:/room/room-<room instance 4. IMQ: subscribe to `/chat/<chatId>` (with property `listen_to_self: false`). The gateway answers with
id>` (contents not seen yet). `JoinedQueue` and creates three mounts: `control` and `messages` (message type) and `participants` (state type).
Then subscribe to `inv:/room/room-<room instance id>`, which has one `node` mount (message type); nothing arrived
on it in a session without room edits.
5. Chat commands sent on joining (below): `*msg SeatAssignment ...`, `*seat <n>`, `*resume <userId>` (or 5. Chat commands sent on joining (below): `*msg SeatAssignment ...`, `*seat <n>`, `*resume <userId>` (or
`*accept <userId>`). `*accept <userId>`).
6. `chat.getParticipants({chatId, userId})`, then again about every 15 seconds: `[{userId, seat, ...}]`. People 6. `chat.getParticipants({chatId, userId})`, then again about every 15 seconds: `[{userId, seat, ...}]`. People
@@ -39,8 +41,19 @@ Leaving: `chat.terminateChat(userId, chatId)`. Booting: `chat.bootOutOfChat({use
`chat.attemptInvite`, `acceptInvite`, `declineInvite`, `leaveInviteAwayNote`. Also seen: `test.getBuddyState`, `chat.attemptInvite`, `acceptInvite`, `declineInvite`, `leaveInviteAwayNote`. Also seen: `test.getBuddyState`,
`test.updateAvatarDownloadSize`. `test.updateAvatarDownloadSize`.
The room list subscribes to `room_occupancy/<room instance id>` for each room shown (mount `occupants`), for live ## The room list
occupancy counts.
- Rooms come from `client-dynamic.imvu.com/api/rooms/rooms_list_paginated.php` (signed in; `recent=1` for recently
visited, search parameters otherwise). The response is column-oriented JSON: one array per field
(`customers_room_id`, `name`, `customers_name`, `num_participants`, `max_users`, `participants`, `image_url`,
`description`, `rating`, `language`, `is_ap`, `is_av`, `is_vip`, ...), plus `number_of_rooms`, `rooms_per_page`
and welcome-room fields. The proxy can add hosted rooms by appending to every array.
- For live counts, the list subscribes to `room_occupancy/<room instance id>` for each room shown (the client
re-subscribes them as the list refreshes). Each is a state mount `occupants` whose properties are the people in
the room, keyed by user ID; the gateway sends `StateChange` from user `admin` when someone joins (value set) or
leaves (value empty).
- Joining by link (`imvu:room/...`) calls `test.getJoinPublicRoomInfo({publicroom_url})`, which returns the room
instance ID.
## IMQ ## IMQ
@@ -69,6 +82,8 @@ reconnect.
On `/chat/<chatId>`: On `/chat/<chatId>`:
- The gateway sends a sender's own messages back to them too (with the queue's next sequence number); the client
ignores its own because it subscribed with `listen_to_self: false`.
- Mount `messages`: JSON `{"userId", "chatId", "message", "to"}` (`to` 0 for everyone). The client takes the sender - Mount `messages`: JSON `{"userId", "chatId", "message", "to"}` (`to` 0 for everyone). The client takes the sender
from the gateway's `user_id` (the last path segment) and flags a mismatch with `userId` as forged. from the gateway's `user_id` (the last path segment) and flags a mismatch with `userId` as forged.
- Mount `control`: JSON with a `command`, such as `PlayAction` and `PlayCoopAction` (actions on avatars and - Mount `control`: JSON with a `command`, such as `PlayAction` and `PlayCoopAction` (actions on avatars and
@@ -88,7 +103,18 @@ handled:
- Other: `*imvu:gift`, `*imvu:showGift`, `*imvu:purchase`, `*imvu:try`, `*imvu:goto`, `*imvu:recommend`, - Other: `*imvu:gift`, `*imvu:showGift`, `*imvu:purchase`, `*imvu:try`, `*imvu:goto`, `*imvu:recommend`,
`*imvu:isPureUser`, `*imvu:txnBoot`, `*snap`, `*hiResSnap`, `*uploadSnap`. `*imvu:isPureUser`, `*imvu:txnBoot`, `*snap`, `*hiResSnap`, `*uploadSnap`.
Saving a scene: the owner's client calls `test.setRoomState({room_instance_id, room_contents, ...})`. ## The scene
- Encoding (`*imvu:setRoomState`): the letter `c`, then base64 (with line breaks) of zlib-compressed JSON:
`{"room_state": {"room_info": {"customers_id", "room_instance_id", "room_pid", "revision_id"}, "room_contents":
[[furniture instance ID, product ID, placement], ...]}}`. `room_pid` is the room shell's product. Placement is a
string of numbers: a format number, then position, a parent reference (which slot of which furniture item it sits
on), rotation and scale (the exact layout still to be worked out).
- The owner's client broadcasts the **whole scene** with `*imvu:setRoomState` when entering the room and after a
change, and saves it with `test.setRoomState({room_instance_id, room_contents, ...})`; others only accept it from
the owner. `getRoomState` returns the same contents with a `room_hash`.
- A hosted room's scene can carry community products the same way: they're product IDs like any other (the proxy's
local IDs on each machine, global IDs on the wire).
## What the hub must do for a hosted room ## What the hub must do for a hosted room
@@ -104,15 +130,24 @@ Through the proxy, which sees both channels:
client's op IDs) and never forwards them. client's op IDs) and never forwards them.
- **IDs**: hosted rooms need room instance IDs and chat IDs IMVU will never use, so the proxy can tell them apart - **IDs**: hosted rooms need room instance IDs and chat IDs IMVU will never use, so the proxy can tell them apart
(to be chosen: a reserved range, as for products). (to be chosen: a reserved range, as for products).
- **Room list**: hosted rooms must appear in the client's room list, which comes from a web API (not looked at yet). - **Room list**: add hosted rooms to `rooms_list_paginated.php` responses, answer `room_occupancy/` queues for them,
and answer `getJoinPublicRoomInfo` for their links.
- **Scenes**: store the scene from the owner's `setRoomState`, serve it from `getRoomState`, and relay the owner's
`*imvu:setRoomState` like any message.
The hub keeps the authoritative participant list, seats, scene and the room's moderation (owner, moderators, bans), The hub keeps the authoritative participant list, seats, scene and the room's moderation (owner, moderators, bans),
and checks every message's sender itself. and checks every message's sender itself.
## Other sessions (from the code, not driven)
- **Private rooms**: the same `getOrMakeChat` with `private: true`.
- **Chat Now** (random one-to-one) uses activity `Chat now-<room instance id>`. It pairs strangers, so it wasn't
driven with the test account.
- **Invitations**: `chat.attemptInvite`, `acceptInvite`, `declineInvite`, `leaveInviteAwayNote`, with
`getOrMakeChat` returning `waiting` until the other person answers. Needs a second account to capture.
## Not yet looked at ## Not yet looked at
- What's sent on `inv:/room/room-<id>`. - Messages on `inv:/room/room-<id>` (likely room edits made outside the client).
- The room list's API, and joining by link (`imvu:room/...`, which calls `getJoinPublicRoomInfo`). - The placement string's exact layout.
- Moving furniture in one's own room (a capture in My Room). - The furniture panel: dragging furniture in (a browser page; synthesized mouse drags don't trigger it).
- Private rooms, invitations and "Chat Now" (one-to-one).
- What `G2cCreateMount` and state mounts are used for in rooms.