- Force-join's RoomUpdate carries the room's role grants, so it no
longer wipes them from the client's cached room.
- The consumable push reports IsTransferable the same as the inventory
list does.
- A rejected private-instance join sends the owner at most one "wants
to join" message per requester and instance every 5 minutes.
- SubscribeToPlayers ignores sets over 5,000 ids and keeps the current
subscriptions.
- The subscriber exclusion list looks up online friends' connections in
one pipelined Redis batch instead of one call per friend.
Most pushes were still shaped for the 2020 watch, so the 2023 client
parsed them as empty or dropped them without any error.
- RelationshipChanged carries the relationship row, to both players, on
friend request / accept / remove / add and on favorite / mute / ignore.
Friend requests and accepts also land in the notifications inbox.
- ModerationKick uses the client's flat moderation block, with the
reason in VoteKickReason (the only field the dialog shows).
- Bans, account deletion, password resets and second-session takeover
are a Logout with a numeric reason instead of a kick to the dorm; the
old string reason failed the client's parse.
- SubscribeToPlayers is honoured: each connection joins a presence group
per subscribed player, and presence (only presence) reaches subscribers
as well as friends. The hub allows 180 s of client silence, since the
client reconnects at most 3 times per login.
- Admin broadcast sends its text as a coach message and can start the
client's own maintenance countdown; ServerMaintenance only reads
StartsInMinutes.
- ConsumableMapping, StorefrontBalance, gift, player event, level-up,
RoomUpdate, profile-image and PlayerLeftChat pushes carry the client's
DTOs. "Join rejected" is the client's RequestGameInvite message.
Contract in docs/recroom-2023-notifications.md; push frames are checked
by NotificationContractTests through a capturing hub context.
Every change below was checked against the 2023-03-21 client disassembly;
file:line evidence is in the code comments and in
docs/recroom-2023-client-api-complete.md.
Presence
- matchmake/none means "online, in no room" (its only caller is
TryGoToOfflineRoom, which ignores the reply). The server used to keep the
old room or invent a dorm, so the next heartbeat looked out of sync and
the client cancelled its load to chase that room.
- The join watchdog no longer clears presence 30 s into a long room load;
it is a 3 minute backstop that drops the room only.
- One fan-out (PresenceBroadcastService) for every transition: deferred
joins, matchmake/none, logout, failed joins, visibility changes, SignalR
connect/disconnect. isOnline is sent on its own; pushes never go to the
subject, because a self push moves the player.
- Online-status visibility (Public / Friends / Favorites / Appear offline)
is stored and honoured by GET /player and by the live pushes.
Profile
- PUT account/me/{emoji,personalpronouns,identityflags,bannerimage} were
404; added with the client's real form keys and persisted (new Players
columns plus migration). Every account read now returns them, and edits
push AccountUpdate to everyone.
Outfit menu
- Saved outfits kept OutfitSelectionsV2 and CustomAvatarItems, so loading a
slot restores what was saved instead of a stale outfit.
- isCreationAllowedForAccount answers the {Success, Error} envelope the
client parses; minPriceForPublicItem is a bare int; gifts/generate accepts
form bodies; lockeditems returns the full owned-item shape; colourways no
longer share their base item's AvatarItemId.
RecNetResult writes
- About 40 writes look status-only but parse the body as {Success, Error}.
An empty 200 showed "Failed to ..." after the server applied the change,
and a body without Success threw "Error message cannot be null". Clubs,
announcements, keepsakes, comments/read, subscriptions, purchase offers
and privacy settings now answer RecNetResult.
- Login token and device-code grants are real; remoteauth approves the
device code; createlogintoken returns a bare string.
- New: incentivizedreferrals/referrals, account/bulk/{email,phone},
GET strings, account/me/confirmphone, PlayMenuCarouselOrdering_1.
Route inventory and coverage test extended to match.
README now describes the 2023 branch.
OnDisconnectedAsync told friends a player went offline, but nothing ever
pushed the opposite transition — PlayerCameOnline existed in
NotificationService with zero callers. The pair was asymmetric, so a SignalR
reconnect (network blip, replica restart) greyed the player out on every
friend's list for the rest of the session even though they were still
playing. Only re-entering a room, which pushes presence via /goto, brought
them back.
AddConnectionAsync already computed the first-connection transition to
maintain the online set but returned void; it now reports it, mirroring
RemoveConnectionAsync.
The push is deliberately conditional on a room already being known. The
presence payload derives isOnline from roomInstance being non-null, so
pushing with no room reads as OFFLINE — worse than staying quiet. On a fresh
boot there is no room yet and the /goto that follows sends the real presence
anyway, so this only fires on reconnects, where the cached room is exactly
what friends need to see again.
Targets Rec Room 2020.12.18 client.
Re-import after adding:
- DorkNet.ClientMod/dorknet-clientmod.json.template — config schema
the launcher's manifest-driven patcher renders into UserData/.
- tools/build-patcher-zip.ps1 — produces the dorknet-clientpatch-{key}.zip
Release asset the launcher fetches.
- .github/workflows/release.yml — auto-publishes both Release assets
on v1-december-* tag push.
Scrubbed via maintainer-side scrub-for-public.ps1: same drop rules as
the prior import, plus build-patcher-zip.ps1 added to the tools/
allow-list so it survives the scrub.