multiplayer-game by rivet-dev/skills
npx skills add https://github.com/rivet-dev/skills --skill multiplayer-game重要提示:在进行任何操作之前,你必须阅读此技能目录下的 BASE_SKILL.md 文件。其中包含了关于调试、错误处理、状态管理、部署和项目设置的重要指导。这些规则和模式适用于所有 RivetKit 工作。以下所有内容均假设你已经阅读并理解了该文件。
使用 RivetKit 构建多人游戏的模式,旨在作为一个实用的检查清单,你可以根据游戏类型进行调整。
从 GitHub 上的一个工作示例开始,并将其适配到你的游戏中。不要从零开始构建匹配和生命周期流程。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| Agar.io, Slither.io, surviv.io |
| 开放世界 | GitHub | Minecraft 生存服务器,类 Rust 世界,MMO 区域/区块世界 |
| 派对 | GitHub | Fall Guys 私人房间,自定义游戏房间,社交派对会话 |
| 物理 2D | GitHub | 俯视角物理格斗游戏,2D 竞技场游戏,平台格斗游戏 |
| 物理 3D | GitHub | 物理沙盒会话,3D 竞技场游戏,移动游乐场 |
| 天梯 | GitHub | 国际象棋天梯,竞技卡牌游戏,决斗竞技场天梯队列 |
| 回合制 | GitHub | 国际象棋通信对局,Words With Friends,异步棋盘游戏 |
| 放置类 | GitHub | Cookie Clicker, Idle Miner Tycoon, Adventure Capitalist |
| 模式 | 适用场景 | 实现指导 |
|---|---|---|
| 固定实时循环 | 大逃杀,竞技场,IO 风格,开放世界,天梯 | 在 run 中使用 sleep(tickMs) 运行,并在 c.aborted 时退出。 |
| 动作驱动更新 | 派对,回合制 | 仅在动作/事件发生时进行状态变更和广播,而非按计划更新。 |
| 粗略离线进度 | 任何具有放置类进度的模式 | 使用 c.schedule.after(...) 并设置粗略的时间窗口(例如 5 到 15 分钟),并根据经过的挂钟时间应用追赶计算。 |
对于简单游戏,从自定义运动学逻辑开始。当你需要关节、堆叠物体、高碰撞密度或复杂形状(旋转多边形、胶囊体、凸包、三角形网格)时,切换到完整的物理引擎。
每个模拟选择一个引擎。将仅限前端的库排除在后端模拟路径之外,并将服务器状态视为权威。
对于非物理的空间查询,使用专用索引,而不是简单的 O(n^2) 检查:
| 索引类型 | 推荐 |
|---|---|
| AABB 索引 | 对于兴趣区域、可见性和非碰撞器实体,动态集合使用 rbush,静态或准静态集合使用 flatbush。 |
| 点索引 | 对于最近邻或半径内查询,使用 d3-quadtree。 |
| 模型 | 何时使用 | 实现 |
|---|---|---|
| 混合(客户端移动,服务器战斗) | 射击游戏,动作体育游戏,天梯对决 | 客户端拥有移动控制权并发送限频的位置更新。服务器进行反作弊验证。战斗(弹道、命中、伤害)完全由服务器权威控制。 |
| 服务器权威与插值 | IO 风格,持久世界 | 客户端发送输入命令。服务器在固定更新频率上模拟并发布权威快照。客户端在快照之间进行插值。 |
| 服务器权威(基础逻辑) | 回合制,事件驱动 | 服务器验证并应用离散动作(回合、阶段转换、投票)。客户端显示确认的状态。 |
requestAnimationFrame 或 Canvas/Three.js 循环进行模拟,而不是 React 状态。将 UI 框架状态保留给菜单、HUD 和表单使用。c.broadcast(...) 进行共享更新,使用 conn.send(...) 进行私有/每玩家数据。共享模拟逻辑在客户端和服务器上同时运行。例如,一个 applyInput(state, input, dt) 函数,用于积分速度并限制在世界边界内,可以在客户端上运行以进行预测,在服务器上运行以进行验证。
src/shared/ 中:将确定性辅助函数放在 src/shared/sim/* 中,确保无副作用。控制每个客户端接收的内容,以减少带宽并防止信息泄露。
worldId:chunkX:chunkY 为键的区块 Actor。rivetkit/db):更适合需要查询、索引或长期持久化的大型或类表格状态(图块、库存、匹配池)。由于多个操作可能同时访问同一个 Actor,因此通过队列序列化数据库工作。以下架构模式中使用的通用构建块。
| 原语 | 适用场景 | 典型所有权 |
|---|---|---|
matchmaker["main"] + match[matchId] | 基于会话的多人游戏(大逃杀、竞技场、天梯、派对、回合制) | 匹配器拥有发现/分配权。比赛拥有生命周期和游戏状态。 |
chunk[worldId,chunkX,chunkY] | 需要分片的大型连续世界 | 每个区块拥有本地玩家、区块状态和本地模拟。 |
world[playerId] | 每玩家进度循环(放置类/单人世界状态) | 每玩家资源、建筑、计时器和进度。 |
player[username] | 跨比赛复用的规范个人资料/评分 | 持久的玩家统计数据(例如评分和胜/负)。 |
leaderboard["main"] | 跨多个比赛/玩家的共享排名 | 全局有序的分数行和排行榜。 |
从这个基线开始,然后针对竞技或高风险环境进行加固。
c.conn.id 作为权威的传输身份。将参数中的 playerId/username 视为不受信任的输入,并通过服务器签发的分配/加入凭证进行绑定。对于任何具有客户端权威移动的模式(混合流程),客户端可能会发送位置/旋转更新以实现平滑性,但服务器必须:
下面的每种游戏类型都以一个快速摘要表开始,然后详细介绍 Actor 和生命周期。
| 主题 | 摘要 |
|---|---|
| 匹配 | 立即路由到最满的未开始大厅(最旧者优先);玩家在大厅等待直到满员,然后比赛开始。 |
| 网络代码 | 混合。客户端拥有移动、摄像机和本地预测。服务器拥有区域状态、弹道、命中判定、淘汰、战利品和最终排名。 |
| 更新频率 | 10 ticks/秒 (100ms),使用固定循环进行区域推进和生命周期检查。 |
| 物理 | 客户端拥有移动控制权,服务器进行反作弊验证;弹道、命中和伤害由服务器权威控制。3D 使用 @dimforge/rapier3d,俯视角 2D 使用 @dimforge/rapier2d。 |
Actor
键 : matchmaker["main"]
职责 : 查找或创建大厅,跟踪待处理的预约,并维护占用情况。
操作
findMatchpendingPlayerConnectedupdateMatchcloseMatch队列
findMatchpendingPlayerConnectedupdateMatchcloseMatch状态
matchespending_playersplayer_count 包括已连接和待处理的玩家键 : match[matchId]
职责 : 运行大厅/进行中/结束阶段,拥有玩家状态、区域推进和淘汰。
操作
connect队列
状态
phaseplayerszoneeliminationssnapshot data生命周期
sequenceDiagram
participant C as Client
participant MM as matchmaker
participant M as match
C->>MM: findMatch()
alt no open lobby
MM->>M: create(matchId)
end
MM-->>C: {matchId, playerId}
C->>M: connect(playerId)
M->>MM: pendingPlayerConnected(matchId, playerId)
MM-->>M: accepted
Note over M: lobby countdown -> live
M-->>C: snapshot + shoot events
M->>MM: closeMatch(matchId)
| 主题 | 摘要 |
|---|---|
| 匹配 | 基于模式的固定容量队列(duo, squad, ffa),仅构建满员比赛并预先分配队伍(FFA 除外)。 |
| 网络代码 | 混合。客户端拥有移动加上预测和平滑处理。服务器拥有队伍或 FFA 分配、弹道、命中判定、阶段转换和计分。 |
| 更新频率 | 20 ticks/秒 (50ms),使用更紧密的循环进行实时队伍和 FFA 快照。 |
| 物理 | 中到高强度;客户端移动加上服务器验证和服务器权威的战斗/实体。 |
Actor
键 : matchmaker["main"]
职责 : 运行模式队列,构建满员比赛,分配队伍,并发布分配。
操作
queueForMatchunqueueForMatchmatchCompleted队列
queueForMatchunqueueForMatchmatchCompleted状态
player_poolmatchesassignments 以连接和玩家为键键 : match[matchId]
职责 : 运行比赛阶段和比赛内的玩家/队伍状态,用于计分和获胜条件。
操作
connect队列
状态
phaseplayersteam assignmentsscore and win state生命周期
sequenceDiagram
participant C as Client
participant MM as matchmaker
participant M as match
C->>MM: queueForMatch(mode)
Note over MM: enqueue in player_pool
Note over MM: fill when capacity reached
MM->>M: create(matchId, assignments)
Note over MM: persist assignments
MM-->>C: assignmentReady
C->>M: connect(playerId)
Note over M: waiting -> live when all players connect
M->>MM: matchCompleted(matchId)
| 主题 | 摘要 |
|---|---|
| 匹配 | 开放大厅路由到容量以下最满的房间;房间数量通过心跳维护,并在需要时自动创建新大厅。 |
| 网络代码 | 服务器权威与插值。客户端发送输入意图并进行插值。服务器拥有移动、边界、房间成员资格和规范快照。 |
| 更新频率 | 10 ticks/秒 (100ms),带有轻量级的定期房间快照。 |
| 物理 | 低到中强度;服务器权威的运动学移动,仅在碰撞变得复杂时才升级到物理引擎。 |
Actor
键 : matchmaker["main"]
职责 : 将玩家路由到最满的开放大厅,并跟踪预约和占用情况。
操作
findLobbypendingPlayerConnectedupdateMatchcloseMatch队列
findLobbypendingPlayerConnectedupdateMatchcloseMatch状态
matchespending_players键 : match[matchId]
职责 : 运行每场比赛的移动模拟并广播快照。
操作
connectsetInput队列
状态
playersinputsmovement statesnapshot cache生命周期
sequenceDiagram
participant C as Client
participant MM as matchmaker
participant M as match
C->>MM: findLobby()
alt no open lobby
MM->>M: create(matchId)
end
MM-->>C: {matchId, playerId}
C->>M: connect(playerId)
M->>MM: pendingPlayerConnected(matchId, playerId)
MM-->>M: accepted
Note over M: fixed tick simulation
M-->>C: snapshot events
M->>MM: closeMatch(matchId)
| 主题 | 摘要 |
|---|---|
| 匹配 | 客户端驱动的基于世界坐标的区块路由,通过相邻区块连接预加载附近的区块窗口。 |
| 网络代码 | 沙盒模式为混合(客户端移动加验证)或 MMO 式流程为服务器权威。服务器拥有区块路由、持久化和规范世界状态。 |
| 更新频率 | 每个区块 Actor 10 ticks/秒 (100ms),因此负载随活跃区块数量扩展。 |
| 物理 | 规模上中到高强度;区块本地模拟可以是服务器权威(MMO 式)或客户端移动加服务器验证(沙盒式)。 |
Actor
chunk[worldId,chunkX,chunkY]connectenterChunkaddPlayersetInputleaveChunkremovePlayerconnectionsplayersblocks 限定在一个区块键内生命周期
sequenceDiagram
participant C as Client
participant CH as chunk
Note over C: resolve chunk keys from world position
loop each visible chunk
C->>CH: connect(worldId, chunkX, chunkY, playerId)
Note over CH: store connection metadata
end
C->>CH: enterChunk/addPlayer
loop movement updates
C->>CH: setInput(...)
CH-->>C: snapshot
end
C->>CH: leaveChunk/removePlayer or disconnect
Note over CH: remove membership and metadata
| 主题 | 摘要 |
|---|---|
| 匹配 | 使用派对代码和显式加入的房主创建私人派对流程。 |
| 网络代码 | 服务器权威(基础逻辑)。服务器拥有成员资格、房主权限和阶段转换。 |
| 更新频率 | 无连续 tick;更新是事件驱动的(join, start, finish)。 |
| 物理 | 大厅优先流程的强度低;通常没有专用的物理或索引,除非添加实时迷你游戏。 |
Actor
键 : matchmaker["main"]
职责 : 处理派对创建/加入流程,验证加入凭证,并跟踪派对规模。
操作
createPartyjoinPartyverifyJoinupdatePartySizecloseParty队列
createPartyjoinPartyverifyJoinupdatePartySizecloseParty状态
partiesjoin_tickets 用于派对查找和加入验证键 : match[matchId]
职责 : 拥有派对成员、房主角色、准备就绪标志和阶段转换。
操作
connectstartGamefinishGame队列
状态
membershostready statephaseparty events生命周期
sequenceDiagram
participant H as Host Client
participant MM as matchmaker
participant M as match
H->>MM: createParty()
MM-->>H: {matchId, partyCode, playerId, joinToken}
H->>M: connect(playerId, joinToken)
M->>MM: verifyJoin(...)
MM-->>M: allowed
M->>MM: updatePartySize(playerCount)
H->>M: startGame() / finishGame()
M->>MM: closeParty(matchId)
sequenceDiagram
participant J as Joiner Client
participant MM as matchmaker
participant M as match
J->>MM: joinParty(partyCode)
MM-->>J: {matchId, playerId, joinToken}
J->>M: connect(playerId, joinToken)
M->>MM: verifyJoin(...)
MM-->>M: allowed / denied
M->>MM: updatePartySize(playerCount)
| 主题 | 摘要 |
|---|---|
| 匹配 | 基于 ELO 的队列配对,随着等待时间增加,搜索窗口逐渐扩大。 |
| 网络代码 | 混合。客户端拥有移动加上本地预测和插值。服务器拥有弹道、命中判定、比赛结果和评分更新。 |
| 更新频率 | 20 ticks/秒 (50ms),使用固定的实时 tick 以实现确定性节奏和广播频率。 |
| 物理 | 中到高强度;客户端移动加上服务器验证和服务器权威的战斗/命中判定。 |
Actor
键 : matchmaker["main"]
职责 : 运行基于评分的排队、配对、分配持久化和完成扇出。
操作
queueForMatchunqueueForMatchmatchCompleted队列
queueForMatchunqueueForMatchmatchCompleted状态
player_poolmatchesassignments 带有评分窗口和连接范围限定键 : match[matchId]
职责 : 运行天梯比赛阶段、计分和获胜者报告。
操作
connect队列
状态
phaseplayersscorewinnercompletion payload键 : player[username]
职责 : 存储规范的玩家 MMR 和胜/负个人资料。
操作
initializegetRatingapplyMatchResult队列
状态
ratingwinslossesmatch counters键 : leaderboard["main"]
职责 : 存储并提供顶级玩家排名。
操作
updatePlayer队列
状态
生命周期
sequenceDiagram
participant C as Client
participant MM as matchmaker
participant P as player
participant M as match
participant LB as leaderboard
C->>MM: queueForMatch(username)
MM->>P: initialize/getRating
P-->>MM: rating
Note over MM: store queue row + retry pairing
MM->>M: create(matchId, assigned players)
MM-->>C: assignmentReady
C->>M: connect(username)
M->>MM: matchCompleted(...)
MM->>P: applyMatchResult(...)
MM->>LB: updatePlayer(...)
Note over MM: remove matches + assignments rows
| 主题 | 摘要 |
|---|---|
| 匹配 | 异步私人邀请和公共队列配对采用相同模式。 |
| 网络代码 | 服务器权威(基础逻辑)。客户端可以在提交前草拟移动。服务器拥有回合所有权、已提交的移动日志、回合顺序和完成状态。 |
| 更新频率 | 无连续 tick;移动提交和回合转换驱动更新。 |
| 物理 | 强度非常低;无实时物理循环,只有离散规则验证。索引是可选的,主要用于大规模时的棋盘或查询便利性。 |
Actor
键 : matchmaker["main"]
职责 : 处理异步比赛的私人邀请和公共队列配对。
操作
createGamejoinByCodequeueForMatchunqueueForMatchcloseMatch队列
createGamejoinByCodequeueForMatchunqueueForMatchcloseMatch状态
matchesplayer_poolassignments 用于邀请和队列映射键 : match[matchId]
职责 : 拥有棋盘状态、回合顺序、移动验证和最终结果。
操作
connectmakeMove队列
状态
boardturnsplayersconnection presenceresult生命周期
sequenceDiagram
participant A as Client A
participant B as Client B
participant MM as matchmaker
participant M as match
A->>MM: queueForMatch()
B->>MM: queueForMatch()
Note over MM: pair first two queued players
MM->>M: create(matchId) + seed X/O players
MM-->>A: assignment/match info
MM-->>B: assignment/match info
A->>M: connect(playerId)
B->>M: connect(playerId)
A->>M: makeMove()
B->>M: makeMove()
opt all players disconnected for timeout
Note over M: destroy after idle timeout
end
M->>MM: closeMatch(matchId)
sequenceDiagram
participant A as Client A
participant B as Client B
participant MM as matchmaker
participant M as match
A->>MM: createGame()
MM-->>A: {matchId, playerId, inviteCode}
B->>MM: joinByCode(inviteCode)
MM->>M: create(matchId) + seed X/O players
MM-->>A: assignment/match info
MM-->>B: assignment/match info
A->>M: connect(playerId)
B->>M: connect(playerId)
A->>M: makeMove()
B->>M: makeMove()
M->>MM: closeMatch(matchId)
| 主题 | 摘要 |
|---|---|
| 匹配 | 无匹配器;每个玩家使用直接的每玩家 Actor 和一个共享的排行榜 Actor。 |
| 网络代码 | 服务器权威(基础逻辑)。客户端拥有 UI 和建造意图。服务器拥有资源、生产率、建筑验证和排行榜总数。 |
| 更新频率 | 无连续 tick;使用 c.schedule.after(...) 设置粗略间隔,并根据经过的挂钟时间计算离线追赶。 |
| 物理 | 标准放置类循环无需物理;转换是离散的(build, collect, upgrade),不需要空间索引。 |
Actor
键 : world[playerId]
职责 : 拥有一个玩家的进度、建筑、生产调度和状态更新。
操作
initializebuildcollectProduction队列
状态
resourcestimersprogression state键 : leaderboard["main"]
职责 : 存储全局分数并提供排行榜更新。
操作
updateScore队列
updateScore状态
scores 表以玩家为键生命周期
sequenceDiagram
participant C as Client
participant W as world
participant LB as leaderboard
C->>W: getOrCreate(playerId) + initialize()
Note over W: seed state + schedule collection
W-->>C: stateUpdate
loop gameplay loop
C->>W: build() / collectProduction()
W->>LB: updateScore(...)
Note over LB: upsert scores
LB-->>C: leaderboardUpdate
W-->>C: stateUpdate
end
IMPORTANT: Before doing anything, you MUST readBASE_SKILL.md in this skill's directory. It contains essential guidance on debugging, error handling, state management, deployment, and project setup. Those rules and patterns apply to all RivetKit work. Everything below assumes you have already read and understood it.
Patterns for building multiplayer games with RivetKit, intended as a practical checklist you can adapt per genre.
Start with one of the working examples on GitHub and adapt it to your game. Do not start from scratch for matchmaking and lifecycle flows.
| Game Classification | Starter Code | Common Examples |
|---|---|---|
| Battle Royale | GitHub | Fortnite, Apex Legends, PUBG, Warzone |
| Arena | GitHub | Call of Duty TDM/FFA, Halo Slayer, Counter-Strike casual, VALORANT unrated, Overwatch Quick Play, Rocket League |
| IO Style | GitHub | Agar.io, Slither.io, surviv.io |
| Open World | GitHub | Minecraft survival servers, Rust-like worlds, MMO zone/chunk worlds |
| Party | GitHub | Fall Guys private lobbies, custom game rooms, social party sessions |
| Physics 2D | GitHub | Top-down physics brawlers, 2D arena games, platform fighters |
| Physics 3D | GitHub | Physics sandbox sessions, 3D arena games, movement playgrounds |
| Ranked | GitHub | Chess ladders, competitive card games, duel arena ranked queues |
| Turn-Based | GitHub | Chess correspondence, Words With Friends, async board games |
| Idle | GitHub | Cookie Clicker, Idle Miner Tycoon, Adventure Capitalist |
| Pattern | Use When | Implementation Guidance |
|---|---|---|
| Fixed realtime loop | Battle Royale, Arena, IO Style, Open World, Ranked | Run in run with sleep(tickMs) and exit on c.aborted. |
| Action-driven updates | Party, Turn-Based | Mutate and broadcast only on actions/events rather than scheduled ticks. |
| Coarse offline progression | Any mode with idle progression | Use c.schedule.after(...) with coarse windows (for example 5 to 15 minutes) and apply catch-up from elapsed wall clock time. |
Start with custom kinematic logic for simple games. Switch to a full physics engine when you need joints, stacked bodies, high collision density, or complex shapes (rotated polygons, capsules, convex hulls, triangle meshes).
Pick one engine per simulation. Keep frontend-only libs out of backend simulation paths and treat server state as authoritative.
| Dimension | Primary Engine | Fallback Engines | Example Code |
|---|---|---|---|
| 2D | @dimforge/rapier2d | planck-js, matter-js | GitHub |
| 3D | @dimforge/rapier3d | cannon-es, ammo.js | GitHub |
For non-physics spatial queries, use a dedicated index instead of naive O(n^2) checks:
| Index Type | Recommendation |
|---|---|
| AABB index | For AOI, visibility, and non-collider entities, use rbush for dynamic sets or flatbush for static-ish sets. |
| Point index | For nearest-neighbor or within-radius queries, use d3-quadtree. |
| Model | When To Use | Implementation |
|---|---|---|
| Hybrid (client movement, server combat) | Shooters, action sports, ranked duels | Client owns movement and sends capped-rate position updates. Server validates for anti-cheat. Combat (projectiles, hits, damage) is fully server-authoritative. |
| Server-authoritative with interpolation | IO Style, persistent worlds | Client sends input commands. Server simulates on fixed ticks and publishes authoritative snapshots. Client interpolates between snapshots. |
| Server-authoritative (basic logic) | Turn-based, event-driven | Server validates and applies discrete actions (turns, phase transitions, votes). Client displays confirmed state. |
requestAnimationFrame or a Canvas/Three.js loop for simulation, not React state. Reserve UI framework state for menus, HUD, and forms.c.broadcast(...) for shared updates and conn.send(...) for private/per-player data.Shared simulation logic runs on both the client and the server. For example, an applyInput(state, input, dt) function that integrates velocity and clamps to world bounds can run on the client for prediction and on the server for validation.
src/shared/: Keep deterministic helpers in src/shared/sim/* with no side effects.Control what each client receives to reduce bandwidth and prevent information leaks.
worldId:chunkX:chunkY.rivetkit/db): Better for large or table-like state that needs queries, indexes, or long-term persistence (tiles, inventory, matchmaking pools). Serialize DB work through a queue since multiple actions can hit the same actor concurrently.Common building blocks used across the architecture patterns below.
| Primitive | Use When | Typical Ownership |
|---|---|---|
matchmaker["main"] + match[matchId] | Session-based multiplayer (battle royale, arena, ranked, party, turn-based) | Matchmaker owns discovery/assignment. Match owns lifecycle and gameplay state. |
chunk[worldId,chunkX,chunkY] | Large continuous worlds that need sharding | Each chunk owns local players, chunk state, and local simulation. |
world[playerId] | Per-player progression loops (idle/solo world state) | Per-player resources, buildings, timers, and progression. |
player[username] | Canonical profile/rating reused across matches |
Start with this baseline, then harden further for competitive or high-risk environments.
c.conn.id as the authoritative transport identity. Treat playerId/username in params as untrusted input and bind through server-issued assignment/join tickets.For any mode with client-authoritative movement (hybrid flows), clients may send position/rotation updates for smoothness, but the server must:
Each game type below starts with a quick summary table, then details actors and lifecycle.
| Topic | Summary |
|---|---|
| Matchmaking | Immediate routing to the fullest non-started lobby (oldest tie-break); players wait in lobby until capacity, then the match starts. |
| Netcode | Hybrid. Client owns movement, camera, and local prediction. Server owns zone state, projectiles, hit resolution, eliminations, loot, and final placement. |
| Tick Rate | 10 ticks/sec (100ms) with a fixed loop for zone progression and lifecycle checks. |
| Physics | Client owns movement with server anti-cheat validation; projectiles, hits, and damage are server-authoritative. Use @dimforge/rapier3d for 3D or @dimforge/rapier2d for top-down 2D. |
Actors
Key : matchmaker["main"]
Responsibility : Finds or creates lobbies, tracks pending reservations, and maintains occupancy.
Actions
findMatchpendingPlayerConnectedupdateMatchcloseMatchQueues
findMatchpendingPlayerConnectedLifecycle
sequenceDiagram
participant C as Client
participant MM as matchmaker
participant M as match
C->>MM: findMatch()
alt no open lobby
MM->>M: create(matchId)
end
MM-->>C: {matchId, playerId}
C->>M: connect(playerId)
M->>MM: pendingPlayerConnected(matchId, playerId)
MM-->>M: accepted
Note over M: lobby countdown -> live
M-->>C: snapshot + shoot events
M->>MM: closeMatch(matchId)
| Topic | Summary |
|---|---|
| Matchmaking | Mode-based fixed-capacity queues (duo, squad, ffa) that build only full matches and pre-assign teams (except FFA). |
| Netcode | Hybrid. Client owns movement plus prediction and smoothing. Server owns team or FFA assignment, projectiles, hit resolution, phase transitions, and scoring. |
| Tick Rate | 20 ticks/sec (50ms) with a tighter loop for live team and FFA snapshots. |
| Physics | Medium to high intensity; client movement with server validation and server-authoritative combat/entities. |
Actors
Key : matchmaker["main"]
Responsibility : Runs mode queues, builds full matches, assigns teams, and publishes assignments.
Actions
queueForMatchunqueueForMatchmatchCompletedQueues
queueForMatchunqueueForMatchmatchCompletedLifecycle
sequenceDiagram
participant C as Client
participant MM as matchmaker
participant M as match
C->>MM: queueForMatch(mode)
Note over MM: enqueue in player_pool
Note over MM: fill when capacity reached
MM->>M: create(matchId, assignments)
Note over MM: persist assignments
MM-->>C: assignmentReady
C->>M: connect(playerId)
Note over M: waiting -> live when all players connect
M->>MM: matchCompleted(matchId)
| Topic | Summary |
|---|---|
| Matchmaking | Open-lobby routing to the fullest room below capacity; room counts are heartbeated and new lobbies are auto-created when needed. |
| Netcode | Server-authoritative with interpolation. Client sends input intents and interpolates. Server owns movement, bounds, room membership, and canonical snapshots. |
| Tick Rate | 10 ticks/sec (100ms) with lightweight periodic room snapshots. |
| Physics | Low to medium intensity; server-authoritative kinematic movement, escalating to a physics engine only when collisions get complex. |
Actors
Key : matchmaker["main"]
Responsibility : Routes players into the fullest open lobby and tracks reservations and occupancy.
Actions
findLobbypendingPlayerConnectedupdateMatchcloseMatchQueues
findLobbypendingPlayerConnectedLifecycle
sequenceDiagram
participant C as Client
participant MM as matchmaker
participant M as match
C->>MM: findLobby()
alt no open lobby
MM->>M: create(matchId)
end
MM-->>C: {matchId, playerId}
C->>M: connect(playerId)
M->>MM: pendingPlayerConnected(matchId, playerId)
MM-->>M: accepted
Note over M: fixed tick simulation
M-->>C: snapshot events
M->>MM: closeMatch(matchId)
| Topic | Summary |
|---|---|
| Matchmaking | Client-driven chunk routing from world coordinates, with nearby chunk windows preloaded via adjacent chunk connections. |
| Netcode | Hybrid for sandbox (client movement with validation) or server-authoritative for MMO-like flows. Server owns chunk routing, persistence, and canonical world state. |
| Tick Rate | 10 ticks/sec per chunk actor (100ms), so load scales with active chunks. |
| Physics | Medium to high at scale; chunk-local simulation can be server-authoritative (MMO-like) or client movement with server validation (sandbox-like). |
Actors
chunk[worldId,chunkX,chunkY]connectenterChunkaddPlayersetInputleaveChunkremovePlayerLifecycle
sequenceDiagram
participant C as Client
participant CH as chunk
Note over C: resolve chunk keys from world position
loop each visible chunk
C->>CH: connect(worldId, chunkX, chunkY, playerId)
Note over CH: store connection metadata
end
C->>CH: enterChunk/addPlayer
loop movement updates
C->>CH: setInput(...)
CH-->>C: snapshot
end
C->>CH: leaveChunk/removePlayer or disconnect
Note over CH: remove membership and metadata
| Topic | Summary |
|---|---|
| Matchmaking | Host-created private party flow using party codes and explicit joins. |
| Netcode | Server-authoritative (basic logic). Server owns membership, host permissions, and phase transitions. |
| Tick Rate | No continuous tick; updates are event-driven (join, start, finish). |
| Physics | Low intensity for lobby-first flows; usually no dedicated physics or indexing unless you add realtime mini-games. |
Actors
Key : matchmaker["main"]
Responsibility : Handles party create/join flow, validates join tickets, and tracks party size.
Actions
createPartyjoinPartyverifyJoinupdatePartySizeclosePartyQueues
createPartyjoinPartyLifecycle
sequenceDiagram
participant H as Host Client
participant MM as matchmaker
participant M as match
H->>MM: createParty()
MM-->>H: {matchId, partyCode, playerId, joinToken}
H->>M: connect(playerId, joinToken)
M->>MM: verifyJoin(...)
MM-->>M: allowed
M->>MM: updatePartySize(playerCount)
H->>M: startGame() / finishGame()
M->>MM: closeParty(matchId)
sequenceDiagram
participant J as Joiner Client
participant MM as matchmaker
participant M as match
J->>MM: joinParty(partyCode)
MM-->>J: {matchId, playerId, joinToken}
J->>M: connect(playerId, joinToken)
M->>MM: verifyJoin(...)
MM-->>M: allowed / denied
M->>MM: updatePartySize(playerCount)
| Topic | Summary |
|---|---|
| Matchmaking | ELO-based queue pairing with a widening search window as wait time increases. |
| Netcode | Hybrid. Client owns movement with local prediction and interpolation. Server owns projectiles, hit resolution, match results, and rating updates. |
| Tick Rate | 20 ticks/sec (50ms) with fixed live ticks for deterministic pacing and broadcast cadence. |
| Physics | Medium to high intensity; client movement with server validation and server-authoritative combat/hit resolution. |
Actors
Key : matchmaker["main"]
Responsibility : Runs rating-based queueing, pairing, assignment persistence, and completion fanout.
Actions
queueForMatchunqueueForMatchmatchCompletedQueues
queueForMatchunqueueForMatchmatchCompletedLifecycle
sequenceDiagram
participant C as Client
participant MM as matchmaker
participant P as player
participant M as match
participant LB as leaderboard
C->>MM: queueForMatch(username)
MM->>P: initialize/getRating
P-->>MM: rating
Note over MM: store queue row + retry pairing
MM->>M: create(matchId, assigned players)
MM-->>C: assignmentReady
C->>M: connect(username)
M->>MM: matchCompleted(...)
MM->>P: applyMatchResult(...)
MM->>LB: updatePlayer(...)
Note over MM: remove matches + assignments rows
| Topic | Summary |
|---|---|
| Matchmaking | Async private-invite and public-queue pairing in the same pattern. |
| Netcode | Server-authoritative (basic logic). Client can draft moves before submit. Server owns turn ownership, committed move log, turn order, and completion state. |
| Tick Rate | No continuous tick; move submission and turn transitions drive updates. |
| Physics | Very low intensity; no realtime physics loop, just discrete rules validation. Indexing is optional and mostly for board or query convenience at scale. |
Actors
Key : matchmaker["main"]
Responsibility : Handles private invite and public queue pairing for async matches.
Actions
createGamejoinByCodequeueForMatchunqueueForMatchcloseMatchQueues
createGamejoinByCodeLifecycle
sequenceDiagram
participant A as Client A
participant B as Client B
participant MM as matchmaker
participant M as match
A->>MM: queueForMatch()
B->>MM: queueForMatch()
Note over MM: pair first two queued players
MM->>M: create(matchId) + seed X/O players
MM-->>A: assignment/match info
MM-->>B: assignment/match info
A->>M: connect(playerId)
B->>M: connect(playerId)
A->>M: makeMove()
B->>M: makeMove()
opt all players disconnected for timeout
Note over M: destroy after idle timeout
end
M->>MM: closeMatch(matchId)
sequenceDiagram
participant A as Client A
participant B as Client B
participant MM as matchmaker
participant M as match
A->>MM: createGame()
MM-->>A: {matchId, playerId, inviteCode}
B->>MM: joinByCode(inviteCode)
MM->>M: create(matchId) + seed X/O players
MM-->>A: assignment/match info
MM-->>B: assignment/match info
A->>M: connect(playerId)
B->>M: connect(playerId)
A->>M: makeMove()
B->>M: makeMove()
M->>MM: closeMatch(matchId)
| Topic | Summary |
|---|---|
| Matchmaking | No matchmaker; each player uses a direct per-player actor and a shared leaderboard actor. |
| Netcode | Server-authoritative (basic logic). Client owns UI and build intent. Server owns resources, production rates, building validation, and leaderboard totals. |
| Tick Rate | No continuous tick; use c.schedule.after(...) for coarse intervals and compute offline catch-up from elapsed wall time. |
| Physics | None for standard idle loops; transitions are discrete (build, collect, upgrade) and do not need spatial indexing. |
Actors
Key : world[playerId]
Responsibility : Owns one player's progression, buildings, production scheduling, and state updates.
Actions
initializebuildcollectProductionQueues
State
resourcestimersLifecycle
sequenceDiagram
participant C as Client
participant W as world
participant LB as leaderboard
C->>W: getOrCreate(playerId) + initialize()
Note over W: seed state + schedule collection
W-->>C: stateUpdate
loop gameplay loop
C->>W: build() / collectProduction()
W->>LB: updateScore(...)
Note over LB: upsert scores
LB-->>C: leaderboardUpdate
W-->>C: stateUpdate
end
Weekly Installs
1.0K
Repository
GitHub Stars
8
First Seen
Feb 24, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex1.0K
cursor1.0K
amp1.0K
gemini-cli1.0K
kimi-cli1.0K
opencode1.0K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
| Durable player stats (for example rating and win/loss). |
leaderboard["main"] | Shared rankings across many matches/players | Global ordered score rows and top lists. |
updateMatchcloseMatchState
matchespending_playersplayer_count includes connected and pending playersKey : match[matchId]
Responsibility : Runs lobby/live/finished phases, owns player state, zone progression, and eliminations.
Actions
connectQueues
State
phaseplayerszoneeliminationssnapshot dataState
player_poolmatchesassignments keyed by connection and playerKey : match[matchId]
Responsibility : Runs match phases and in-match player/team state for score and win conditions.
Actions
connectQueues
State
phaseplayersteam assignmentsscore and win stateupdateMatchcloseMatchState
matchespending_playersKey : match[matchId]
Responsibility : Runs per-match movement simulation and broadcasts snapshots.
Actions
connectsetInputQueues
State
playersinputsmovement statesnapshot cacheconnectionsplayersblocks scoped to one chunk keyverifyJoinupdatePartySizeclosePartyState
partiesjoin_tickets for party lookup and join validationKey : match[matchId]
Responsibility : Owns party members, host role, ready flags, and phase transitions.
Actions
connectstartGamefinishGameQueues
State
membershostready statephaseparty eventsState
player_poolmatchesassignments with rating window and connection scopingKey : match[matchId]
Responsibility : Runs ranked match phase, score, and winner reporting.
Actions
connectQueues
State
phaseplayersscorewinnercompletion payloadKey : player[username]
Responsibility : Stores canonical player MMR and win/loss profile.
Actions
initializegetRatingapplyMatchResultQueues
State
ratingwinslossesmatch countersKey : leaderboard["main"]
Responsibility : Stores and serves top-ranked players.
Actions
updatePlayerQueues
State
queueForMatchunqueueForMatchcloseMatchState
matchesplayer_poolassignments for invite and queue mappingKey : match[matchId]
Responsibility : Owns board state, turn order, move validation, and final result.
Actions
connectmakeMoveQueues
State
boardturnsplayersconnection presenceresultprogression stateKey : leaderboard["main"]
Responsibility : Stores global scores and serves leaderboard updates.
Actions
updateScoreQueues
updateScoreState
scores table keyed by player