Social
Playing together (Game design §13, Technical specs §9.6, §9.9): friends and where they are, parties of up to four that land in a room together and share their missions, the chat (a room's channel, a party's, direct messages, quick-chat and emotes), the griefing controls (mutes, votes to kick, reports of players, safe zones, a ghost after coming back), the lobbies that gather a multiplayer mission's players and take them from run to run across levels (Quick play and the playlists among them, seats filled under way, the phone's Jobs app), and each account's Respect, conduct and hidden skill. Who runs what:
| Part | Where | On Nakama |
|---|---|---|
| Friends, mutes | the API (apps/api/src/social*.ts), behind a friend backend | Nakama's friends (/v2/friend) and a storage object of the player's (foi/mutes) |
| Presence, parties, the feed | the API's process (SocialHub) | the same |
| Lobbies, Quick play | the API's process (LobbyHub, Matchmaker); their runs in the store | the same |
| Respect, conduct, skill | the API (apps/api/src/respect.ts, its rules standing.ts), the store | the same (the store's tables, never Nakama's) |
| The party's channel, direct messages | the API's feed | also kept in Nakama's chat channels by the runtime module's RPC (apps/nakama) |
| The room's channel, the roster, votes to kick | the game server (apps/server/src/room-social.ts), over the room's socket | the same; the module's hook moderates any message a client sends straight to a room's channel |
| Safe zones, ghosts, player damage, party rules | the simulation (packages/sim/src/griefing.ts), deterministic, hashed | the same |
| A party's shared mission | the mission runtime (packages/missions) | the same |
With the dev accounts (FOI_AUTH=dev, npm run dev:platform, the tests) the friend graph and the mutes live in memory and in .foi-data/social.json beside the file store; nothing needs Docker.
Friends and presence
On the legend's Social tab a player asks another by name (Add friend); the other sees the request and accepts it (Accept, which is asking back), or either removes it. A friend's row says whether they are online and in which game's room (playing title); Join joins that room (POST /rooms/:id/join), Invite to party and Message are there too. A page says where it is (PUT /social/presence {room}) when it starts in a room the platform opened, or when the Social tab is picked, then every 20 seconds; a player not heard from for a minute is offline.
Parties
Start a party makes one the player leads; the leader invites friends (four players at most, invitations counted); an invited player joins from the tab (Join) or declines; a member leaves (Leave the party), the lead passing to the next member, the last one out ending it. When the leader opens a room (Play) or joins one, every other member's page is sent a URL that joins it (the feed's room event) and goes there: the party lands together. Each member's join token names the party; the game server numbers the room's parties (Character.party), so its members are never carjacked by each other nor put out of a car another carjacks, and under playerDamage: rivals never hurt by each other.
Inside the room the party plays its missions together (Missions): one mission at a time, held by the first member and stepped once with every member's facts. Anyone of them reaching a zone, driving the mission's vehicle, picking up its item or carrying its items advances everyone; a hold (survive, defend, lose) lasts while any member stands; the tracker, the notices and the phone's messages reach every member; the reward is paid to each whole, or split (rules.partyRewards). Each member sees the others on the minimap wherever they are (a green ring, from the mission's tracker, even beyond the 250 m the snapshots reach), the mission's targets in the world and on the minimap, and their own route line to the target. When the first member leaves, the next takes the mission over. A player without a party plays alone, as before. To try a party without the platform, join a level server whose debug commands are on (a test-* level, FOI_DEBUG=1) from two windows with the same party parameter on the server's address (?server=ws://localhost:7777/?party=crew in the page's address).
Chat
Enter opens the chat when the keys are the game's (closed, it takes no key: its lines never stop the car); Enter sends the line, Esc closes it and hands the keys back. A line goes on the channel picked: the room's, the party's, or a friend's (direct). The quick-chat phrases (Hello!, Thanks!, Follow me.…) and the emotes (waves, laughs, shrugs…) are fixed ids that each reader's language words, never typed, never moderated. An emote also plays over its player's character, for everyone who sees it (Animation). Closed, the chat shows the last six lines for fifteen seconds; open, all of them. The room also says who came, who left and who was voted out.
| Channel | Carried by | Moderated |
|---|---|---|
| Room | the game server: a room command (say, quick, emote) in, chat lines out | in a public room: each line, before anyone else reads it |
| Party | the API: POST /social/messages {to: "party", text}, the members' feeds | no (a private conversation) |
| Direct | the API: POST /social/messages {to: <friend's id>, text}, both players' feeds | no |
A room the API opens for a public game is public: its open request names where its chat is checked (moderationUrl, the API's POST /moderation/chat, asked with the room secret). The game server asks about every line; one the moderator refuses, or one it could not check, reaches nobody but its sender, who sees it marked hidden by moderation. A playtest and a game shared by link are private: not moderated (GAME_DESIGN §10.4). The default room of a standalone server has no platform: not moderated either. A player says at most eight lines in ten seconds.
Griefing controls
- Mute: on the Social tab's list of the room's players. A muted player's lines leave the reader's chat on every channel; the mute is kept with the account (
PUT /social/mutes/:id), or by the page alone for a player without one. - Vote to kick (Vote out): in a room of three or more, any player starts a vote against another; the others see it in the chat (Yes, No); the starter's vote counts, the target has none; a majority of the room's players (more than half, the target counted) within 30 seconds puts the target out: disconnected, and their account refused by that room from then on (close code 4403).
- Report a player: into the moderators' queue with the game's reports (
POST /players/:id/reports), the player's name and the room kept; the moderators decide it like any report (Platform). - Safe zones: a game's zones its
rules.safeZonesname. Inside one, no player hurts another (shots, blasts, a car running them down, and stealth's fists, darts and knockouts: the hit deals nothing, the hit log says no damage, nobody falls asleep or down) and no car is carjacked; NPCs still can. - A ghost after coming back: a player who comes back from lying dead (or arrested) is a ghost for the game's
rules.ghostSeconds(3 by default): cars and people pass through them, nothing hurts them, and everyone sees them see-through; then solid again.
The game's rules decide the rest: playerDamage (off, rivals: only players outside one's party, on; a dart, a punch's knockout or stun and every other non-lethal hit between players obeys it like a shot), respawn (hospital: the nearest spawn point; mission: where the player's mission started), maxPlayers, police (Missions).
Runs, lobbies and Respect
A multiplayer mission (Missions) is played in a run of its own: a room of the level for the players a lobby gathered, seated on their teams. A run of a party can still be opened straight from the API (POST /games/:id/missions/:mission/runs, its leader asking: the teams filled in turn); the game pages open lobbies.
Lobbies
A lobby gathers the players of one mission of one game, in the platform (apps/api/src/lobbies.ts, its routes in lobby-routes.ts; Platform), and takes them on from run to run, across levels.
- Opening one. A multiplayer mission's Play on a game page opens a lobby for it, the player its host (public for a public or curated game, private for a game shared by link; a solo mission's Play still opens its run at once). A party's leader brings the party in, on one team: every member's page asks ("leader joined a lobby": Join goes to its page, Stay leaves it), never in a run under way; a member seated in a run, or in another lobby counting down or voting, stays where they are and gets the lobby as an invitation instead (the lobby says who is still in a run). A party with a member the host kicked is refused as a whole, saying who. The game page lists the mission's public lobbies with a Join each.
- Its page (
?lobby=<id>,apps/client/src/ui/LobbyPage.tsx) shows everything: the mission's card (its title, game, level, creator and rating, and its rules in plain words: players, teams, lives, friendly fire, joining late, the time limit, what a death does, the retries), the teams' slots with each member's name, party, host and ready state and the places open, Join for a reader not in it, Ready, Switch to another team (with one's party, when it has room for them all), Leave, the host's Start (enabled once every team has its minimum) and Kick (the kicked player may not come back), and the invitation: the lobby's link to copy and the friends to Invite (their feed, the Social tab's list and the Play together screen's; one invitation per friend while it holds, ten minutes, ten a minute at most). A lobby holds up to the mission's most players, more than a party; a party stays on one team. Whoever has the link may read the lobby and join it; a reader not in it sees the members' names and the counts, never their accounts, parties nor the run. Changes reach the members' pages together, a few times a second at most, each push carrying the lobby as the member sees it. Once the reader is no longer in it (kicked, dropped as gone from the page, the lobby closed) the page says so with the way back to Play together, and nothing else. The platform holds 500 lobbies at most. - Starting. Once every team has its minimum the lobby is in its ready check; it starts by itself when every member is ready, at once when its last place fills, or when the host starts it early. A 10 s countdown follows, the run's room opened at its start so its world warms while the players wait; nobody joins during it. When it ends every member is sent their join URL (the feed's
runevent) and their page goes to their seat; the run's page remembers the lobby (inLobbyin its address). Before every run the lobby's game is read again: one taken down or gone (or its mission no longer in it) closes the lobby, telling its members why. A member still seated in another run is left out of this one, told why; when no game server takes the run (the city busy, or no answer within a minute), the countdown says so and asks again every few seconds for two minutes, then gathers again: the lobby is never lost (a playlist's lobby that moved on to that mission goes back to vote instead: below). A run opened for a countdown that is over by then (stopped, the lobby closed, or answered after its time) is abandoned at once (counting for nothing, its room closed). A member who leaves the lobby while its run is live stays in the run: that is the room's business (a minute to come back, then the seat gone, which frees them for another run). - After the run. When the run's result reaches the platform, every run page that came from the lobby goes back to it three seconds later (the winner seen on screen first; its Back to the lobby goes at once). The lobby shows the results (the outcome, the winner, each seat's credit, downs, catches, caught, takedowns, race place and idle time) and the vote on what comes next, for 20 s: the same mission, the game's next multiplayer mission, a mission on another level that takes the group (from the playlists), or Replay (the same mission and teams, counting down at once). A majority decides at once; once everyone voted, or when the time is out, the most votes win, the host's breaking a tie (nobody voted: the same mission; a playlist's lobby moves on along its playlist instead, below). Members whose page is gone (heard from by neither the lobby nor the feed for a minute) have no say and are dropped, told so; the game picked is read again (gone: the lobby stays on its own mission, saying so); the lobby moves on (its teams made again when the mission's differ) and gathers again. A run page goes back to the lobby only while its player is still in it; otherwise its results lead to the game's page.
- Finding play. The Play together screen (
?together: from the main menu's Play online, the game pages and the page of games) has Quick play's playlists (Co-op, Versus, Races, Anything), each playlist's missions across levels with Open a lobby (a lobby that remembers its playlist), the player's own lobby, the invitations, and the public lobbies with a Join each. In the city the phone has the same in its Jobs app (below).
Finding play
- The playlists (
apps/api/src/playlists.ts,GET /playlists): Co-op, Versus, Races and Any, each a rotation of missions across levels: the curated games' multiplayer missions and the published ones whose version qualified for Respect (20 qualifying players played it and 5 rated it 3 or more: below; never with Respect off), the levels taken in turn by name, one mission of each before the next of any, so a playlist's next mission is on another level whenever one has any. Co-op is the co-op missions, Races the versus missions with a race between the players, Versus the other versus ones, Any all of them. - Quick play places the player (with their party, its leader asking) in the fullest public lobby of the playlist gathering with room for them all on one team; else, a player alone, in a seat of a public lobby's run under way of the playlist that takes players then (below; a party is never split over a run's open seats); else in a new public lobby on the playlist's next mission. Those it places in a lobby are ready, so a lobby of strangers starts at its minimum.
- A lobby of a playlist (one Quick play opened, or one opened from the Play together screen's playlist) remembers it and shows it on its card. After its run, its vote's offers come from the playlist: the game's next mission of the playlist, and the playlist's next mission on another level that takes the group; and when nobody votes it moves on along the playlist (to that other level's mission, else the game's next) rather than playing the same mission again. That is the continuous play: a group goes from level to level without leaving its lobby. It is never stranded there: when the run of the mission it moved on to cannot open (the city busy until the countdown gave up, a refusal, the mission gone meanwhile), the lobby goes back to the mission it came from and its last results, saying why, and votes again (never offered the mission that failed): its members vote, replay or leave as after any run.
- Seats filled under way. A run whose mission takes players under way (
joinInProgress: untilCheckpointuntil its first checkpoint, oralways: Missions) has open seats its room's status says. Quick play seats a player alone there: in the run of a public lobby of their pool whose mission is of the playlist, the player neither kicked from it nor refused by its Respect gate (both asked again as the seat is filled,RunContext.admitLate), on the team with a seat open that has the fewest players (then, Respect shown, the least skill of the members on it). The seat is kept in the store first, then asked of the run's server (5 s at most): a refusal (its answer of 4xx) takes the store's row back; no answer keeps it, the seat counted the player's only while the run's room lists it (RoomStatus.late), so a seat the server never made frees the player at once and its row matches nothing. When the result comes, the seats there from the start must match the run's roster exactly, those filled under way are matched by account: one the server reports and the store never kept is ignored (and logged), never paid; one the store kept and the result lacks never came in. The seat goes to the roster with the player's Respect tier, the player becomes a member of the lobby and is sent their join URL (the feed'srun), and Quick play's answer says the way into the run (LobbyView.join): the page goes straight there, the lobby kept for the way back. The lobby's members see the new member; those whose seats are gone for good leave the lobby, told so. A player who left a run is never seated in it again. - The phone's Jobs app. In the city (a hub's room, never a run's) the phone has a Jobs tile: the player's invitations to lobbies, the public lobbies that need players across the playlists, the missions of the level the player is on (from the missions' index) and the other levels' multiplayer missions (Any's rotation), and Quick play by playlist. Picking a lobby opens its page (
?lobby=); Quick play goes to its lobby, or straight into the run that seated the player; a solo mission opens its run at once; a multiplayer one its fullest public lobby gathering, or a new one. Its data comes from the platform through the page (renderer/jobs.ts: read when its page is shown and every 5 s while it stays), never from the game server: without the platform (a server played on its own) it says that jobs need the platform. What arrives goes on top and the digit keys count from the bottom of the list, so neither the pointer nor a key lands on another entry when one is added (Phone).
Respect plugs in here (below): a lobby asks its gate before a member joins and again at its run's start (LobbyDeps.admit; a member it refuses then is left out), shows each member's tier, balances a versus mission's teams by the members' hidden skill, and Quick play matches players within a pool (QuickPlayGate: the conduct pool) and prefers the versus lobbies nearest the group's skill.
Respect
Game design §14 says what it is; the rules are one module of pure functions (apps/api/src/standing.ts, its constants RESPECT, CONDUCT, TIERS, POOL), the hidden skill another (skill.ts), and the service that keeps and applies them respect.ts (its routes respect-routes.ts). Three numbers per account, kept in the store (player_standing), each change a line of its ledger (standing_ledger: what it added, the conduct it moved and why):
| Who sees it | Moves with | Used for | |
|---|---|---|---|
| Respect | everyone: a tier, and the number | settled runs, endorsements; never down | the account's page, the lobbies and the run's roster (the tier), gates, the leaderboard |
| Conduct | the player (their band and why it moved), the admins and the moderators (the number) | what the game server measures of a run, vote-kicks, upheld reports, an admin's adjustment | scaling Respect, the low-conduct pool |
| Skill | nobody | versus runs (Glicko-2) | balancing a versus lobby's teams, Quick play's nearest lobby |
Only accounts hold standing, never guests: a guest plays, sees everyone's tiers, and earns and holds nothing (a run's seat keeps whether it was a guest's: RunPlayer.guest). A creator's playtest, an abandoned run and a lost one change nothing.
A run paid. When a run's result reaches the platform (or the sweep finds it lost: nothing), the service pays it before its lobby shows the results, once per account whatever happens after (its ledger line is run:<run>:<account>, each account's award one transaction of the store's); the run is then marked paid (Run.paidAt), and one whose payment failed is paid by the sweep (every minute, the last day's runs). Each seat's Respect, step by step (the results show every step):
- The base: 10 a minute of the run as it was played (its
seconds), capped by the mission's attempt clock when it has one and by 20 minutes, with no floor (a 10-second run pays about 2); times its difficulty,1 + (1 − its completion rate), up to 2 for a curated mission and 1.5 for a creator's, once its version (the result'smissionHash) has 20 counted runs, 1 before; pro rata by the seat's span (from when it joined to when it was given up: the whole run for a seat there from its start to its end), so a seat filled under way earns its own share of the run, and its results say so ("Your part of the run: 60 % of it"). A span under the minimum (MIN_SPAN: 30 s or a fifth of the run's seconds, whichever is more, never more than the run) counts for nothing: no Respect, no clean run for conduct, no skill, no qualifying play, no slot of the day's repeat; its results say the part was too short to count. Leaving early is charged all the same. - The outcome: complete or won ×1, the winning team of a versus run ×1.2, a draw ×0.9, a versus match lost ×0.8, a failed co-op run ×0.5.
- Presence: a span of the minimum at least, connected for half of it, idle under a quarter of it, and there at the run's end; else nothing (and no clean run for conduct). A seat that left early is not there, its place taken or not: the leaver whose seat was filled is charged for abandoning, as any leaver.
- The bonus: up to +30 % by the seat's share of its team's credit (the objectives it did, the enemies it hit, the rivals it caught and put down; a leaver's credit stays its team's). A seat with no credit at all gets half of its share on a completed or won run (or a draw), nothing on a failed co-op run or a lost match: being there is not enough.
- The day's repeat: the account's paid runs that UTC day so far, of that curated mission (any version), or of any mission of that creator's (any of their games): the first ×1, the second ×0.5, the third ×0.25, every one after ×0.1. A creator's new mission resets nothing.
- Qualified: a creator's mission pays only once 20 distinct qualifying players have played its current version and 5 of them rated it 3 or more on average; a qualifying player was there for the run, is not the mission's creator and held tier Local (500 Respect, earned elsewhere) before it, so accounts made for the purpose count for nothing. A run counts in its version's tally (
mission_stats,mission_players: its counted runs, completions and distinct qualifying players) only when a qualifying player played it and the creator did not; a new version starts over. A curated mission is qualified from the start. - Conduct: the band's factor, Good ×1, Warning ×0.5, Low ×0; rounded to whole points.
Conduct is 0 to 10,000, a new account 7,500; Good from 7,000, Warning from 4,000, Low under it. A clean run (present, no misconduct) is +100, up to 10,000. A run's misconduct costs at most 1,500 together:
| Misconduct | Cost | What the game server reports |
|---|---|---|
| Abandoning: left early and not back, not voted out (never a seat filled under way whose player never came in) | −300 | leftEarly, no kickedBy |
| Each teammate killed | −400 | teamKills |
| More than half one teammate's health done to them in the run | −150 | teamHitPeak (the most one teammate took, as a share of their health) |
| Each attempt failed by the seat's hit on one of the mission's people (sabotage) | −500 | sabotage (the missions' runtime: an escort's VIP or a non-lethal target lost within 10 s of the seat's hit) |
| Idle for most of the run | −200 | afkS over half the run |
| Voted out a second time in 7 days, each time by 3 players or more outside one's party, more than half of those in the room, none of the first vote's voters in the second | −300 | kickedBy, kickVoters and kickRoom (the yes votes from outside the seat's party, their accounts, and the players outside it in the room) |
Friendly fire is off by default: teammates' damage and kills only exist where a mission turns it on, and one crash costs nothing. A report of a player a moderator upholds (POST /moderation/reports/:id/uphold) costs 1,000, once a report; an admin may adjust an account's conduct with a note (POST /admin/accounts/:id/conduct). Both go to the audit log (uphold, conduct). Conduct recovers by clean runs alone, never by time. The player sees their band and the last reasons it moved (clean, abandon, teamKill, teamDamage, sabotage, idle, kicked, report, adjusted), never the number.
Endorsements. From a run's results a player may endorse up to two others (POST /runs/:id/endorse {account}), once the run is paid (before, "its Respect is still being counted"): never a member of their own party, whatever the teams; a teammate, or in versus a rival too (sportsmanship), when both were there for the run; once each, and once a UTC day from one player to the same other (friends who queue apart are no party: the pair's halving brakes them). Asked again for the same player and run, it answers the same and completes its ledger line if that was ever missing. An endorsement is worth 5, weighted by the endorser's band (Good 1, Warning 0.5, Low 0), halved again for each endorsement between the pair (either way) in the last 7 days, and scaled like the receiver's run (its day's repeat, its qualification, the receiver's band); three count per run and receiver, a fourth adds nothing. The endorsed hear of it (the feed's endorsed event) and see it in their results. A guest neither gives nor gets one.
A mission's rating. A run's qualifying players rate its mission's version from the results (POST /runs/:id/rating {stars}, 1 to 5; again replaces it): there for the run, holding tier Local, and never its creator. The ratings are what a creator's mission needs to qualify.
Tiers and gates. Five tiers: Unverified (0), Local (500), Trending (2,000), Verified (6,000), Iconic (15,000). A mission's play.respect.min is a tier (Missions): a lobby is refused to a player under it, at its creation, at a join and at its run's start, saying the tier's name and what the player has ("“Heist” asks for Respect tier 3 (Trending, 2,000 Respect): you are tier 2 (Local, 640)"). A creator's mission asks for tier 2 at most until it qualifies (its publication says so, as a warning); the lobby's card shows the tier asked now. The lobbies answer at once from what the service keeps of the accounts they were asked to look at.
Skill and the teams. A versus run moves the Glicko-2 rating of the seats that were there (a seat that left early or idled is left out of the update and of the composites: its leaving is conduct's business): a run of teams with each team as one player, the mean of its members; a free-for-all as games between every two players, the better placed winning. A versus lobby's teams are balanced by it: a player (or a party, kept together) joining goes on the team with room that has the fewest members, then the one whose ratings add up to the least, and teams made again place the largest parties first, the strongest first among those of a size; a countdown never starts on teams further apart in size than making them again would leave them (they are made again first). Quick play prefers, among versus lobbies, those whose members' mean rating is nearest the group's (in steps of 200).
The low-conduct pool. While at least 16 players are in Quick play's lobbies (gathering or counting down, the one asking counted), Quick play puts the Low band in a pool of their own (low) and everyone else in all: each placed only in lobbies of their pool, a lobby Quick play opens keeping its opener's. With fewer, everyone is in all, and so is every lobby opened meanwhile. A game page's lobbies and invitations are never pooled.
Seasons and the leaderboard. A season is a UTC quarter (2026Q4). GET /leaderboard?season=&limit= lists the accounts by the Respect earned in a season (the current one by default, any quarter when asked, the answer naming the current and the three before it; all: all time): names and tiers, never accounts, the reader's own line marked; the web client's ?leaderboard page shows the season and all time. Respect itself never resets.
What the player reads. GET /me/standing: their Respect, tier, the next tier and what it still needs, the season's Respect, their band and why it moved lately; GET /runs/:id/standing: what a run paid them step by step, the endorsements they got and may still give (and to whom), and their rating of its mission; GET /players/:id/standing: another player's name, Respect and tier. The account bar, a lobby's members and a run's roster show the tier (the run's seats carry it: RunSeatSpec.tier). In the web client (apps/client/src/ui/RespectParts.tsx): a lobby's results, and a game's page back from a run (?game=<id>&run=<run>), show "+N Respect" with each step that changed it, the endorsements got, Endorse beside each player one may still endorse and the mission's stars; the account bar's tier links to the player's standing (?profile: Respect, tier, the next, the season's, the conduct band in a sentence and why it moved, the export as a file); the admins' page shows an account's conduct and adjusts it with a note.
Privacy. What is kept of an account's standing: its Respect, conduct and skill, every ledger line (each run's steps and the conduct's reasons, the admins' and moderators' notes), the endorsements it gave and got, and its ratings of missions; conduct is computed from what the game server measures of each run and from the reports the moderators decide. GET /me/standing/export hands the player all of it, the conduct's number and the skill rating included (their right of access to their own data; the pages show the band alone). An account deleted on request has its standing erased by an admin (DELETE /admin/accounts/:id/standing {note}, in the audit log: erase), and its place among the missions' players is kept under an anonymous id (the counts stay). The service keeps at most 10,000 accounts' standings in memory and nothing of an account that has none (GET /players/:id/standing answers 404 for it).
FOI_RESPECT (Platform): on (the default) shows and gates; shadow computes and keeps everything but shows nothing (the players' routes answer 404, the lobbies show no tiers, nobody is gated or pooled, the teams are made as without it), to tune the numbers on real runs; off computes nothing either, the runs and lobbies as before it. With FOI_DEBUG=1 the tests set a standing directly (PUT /debug/standing/:id {respect?, conduct?, skill?}, an admin's session or a request from the same machine); never on a live platform.
Routes
| Route | What |
|---|---|
GET /social/friends | Friends and requests: {id, name, state: friend|sent|received, online, room: {id, title, game} | null} |
POST /social/friends {name} or {id}, DELETE /social/friends/:id | Ask (or accept); remove, cancel, decline |
PUT /social/presence {room} | Where the page is (null: no room) |
GET /social/party | {party: {id, leader, members, invited} | null, invites: [{party, from}]} |
POST /social/party, /invite {account}, /join {party}, /decline {party}, /leave | The party |
POST /social/messages {to, text} | The party's channel (to: "party") or a friend |
GET /social/events?after=n&wait=s | The feed: message, friends, party, invite, room, lobby, run, creation, endorsed events after n, waiting up to 25 s for one |
GET /social/mutes, PUT and DELETE /social/mutes/:id | Mutes |
POST /players/:id/reports {reason, text?, name?, room?} | A report of a player |
POST /moderation/chat {texts, room?} | (the room secret) A public room's line checked: {allowed, reasons} |
POST /lobbies, GET /lobbies, /lobbies/mine, /lobbies/:id, its join, leave, ready, team, start, vote, kick, invite; POST /lobbies/quick; GET /playlists | The lobbies, Quick play and the playlists (Platform) |
GET /me/standing, /me/standing/export, /players/:id/standing, /leaderboard, /runs/:id/standing; POST /runs/:id/endorse, /runs/:id/rating | Respect (Platform) |
Testing
apps/api/src/social.test.ts (both friend backends, the hub, the routes), apps/server/src/room-social.test.ts and party.test.ts (the room's side, parties and votes in a room), packages/missions/src/runtime-party.test.ts (a party's mission, deterministic), packages/sim/src/griefing.test.ts (player damage, safe zones, ghosts), apps/nakama/src/index.test.ts (the module), apps/client/src/ui/social-view.test.ts and apps/client/src/renderer/social.test.ts; the lobbies in apps/api/src/lobbies.test.ts, matchmaking.test.ts and missions-index.test.ts, the page's side in apps/client/src/ui/lobby.test.ts; Respect in apps/api/src/standing.test.ts (every rule, and its properties over random runs: Respect never decreases, no account gains from another's loss, farming converges to the 10 % floor, conduct stays in range), skill.test.ts (Glicko-2's published example) and respect.test.ts (runs paid once, the repeat, conduct, guests, endorsements, ratings, gates, the pool, teams by skill, the leaderboard, the audit log, export and erasing, off and shadow), the store's side in stores.test.ts, a run's sabotage in packages/missions/src/runtime-run.test.ts. End to end: tests/e2e/specs/platform/social.spec.ts (E2E-210 to E2E-219, with the platform API and the word list) and the mini level test-safe-zones (E2E-220 to E2E-223: the chat's keys, a safe zone, a ghost); the lobbies and Quick play in tests/e2e/specs/platform/lobbies.spec.ts (E2E-658 to E2E-662); Respect in tests/e2e/specs/platform/respect.spec.ts (E2E-663 to E2E-669) and the privacy policy's words in E2E-624; continuous play (a playlist's lobby moving on to another level, seats filled under way and none past a checkpoint, a late seat's Respect pro rata, the phone's Jobs app) in tests/e2e/specs/platform/continuous.spec.ts (E2E-680 to E2E-684). The playlists and the seats filled under way in apps/api/src/playlists.test.ts and lobbies-fill.test.ts, a seat's span in standing.test.ts and respect.test.ts, the run's side in apps/server/src/run-host.test.ts, runs.integration.test.ts and versus.integration.test.ts, the Jobs app in apps/client/src/ui/jobs-view.test.ts, renderer/jobs.test.ts and gui/blocks.test.ts. The Compose stack's npm run stack:smoke makes two Nakama accounts friends and sends a line through a public room's moderation.