scenario-godot-multiplayer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Godot multiplayer (network programmer)

Godot 多人联机(网络程序员)

Target: Godot 4.7.2, SceneMultiplayer over ENet and WebSocket.
Expert level here means the networked game is proven with several processes, under injected latency, before anyone plays it: authority is a written decision per object, every client message is a request the server checks, and replication cost is measured in KiB/s. This skill builds the replication in code, runs a server and 2 to 3 clients headless on localhost (plus a browser client), and passes only on numbers from those runs. If a sibling skill named here is missing from your available skills, ask the user to install it (
npx skills add scenario-labs/skills --skill <name>
); unattended, proceed from tool schemas and flag the gap.
REQUIRED BACKGROUND: scenario-godot-expert (channels,
gd_run
, review loop, 4.7 traps).
Status (2026-10-02): 16 live tests (up to 3 clients, ENet, WebSocket, exported server, headless Chrome) and 18 offline tests pass. Evidence:
tests/live_evidence/godot-multiplayer/
.
目标:Godot 4.7.2,基于 ENet 和 WebSocket 的 SceneMultiplayer。
这里的专家级标准意味着:在任何人开始游玩之前,必须通过多个进程和注入延迟来验证联机游戏:每个对象的权限归属都形成书面决定,每条客户端消息都是服务器会检查的请求,并且复制开销以 KiB/s 衡量。该 skill 会用代码构建复制系统,在 localhost 上以 headless 方式运行一个服务器和 2 至 3 个客户端(另加一个浏览器客户端),并且只有当这些运行所得的数值达标时才通过。如果可用 skills 中缺少此处命名的同级 skill,请要求用户安装(
npx skills add scenario-labs/skills --skill <name>
);在无人值守情况下,则依据工具 schema 继续,并指出缺口。
必需背景: scenario-godot-expert(channels、
gd_run
、review loop、4.7 traps)。
状态(2026-10-02): 16 项实时测试(最多 3 个客户端、ENet、WebSocket、导出的服务器、headless Chrome)和 18 项离线测试均已通过。证据:
tests/live_evidence/godot-multiplayer/
。

Stance (the expert delta)

立场(专家级差异)

  1. Decide authority per object before writing code. Someone must own each object's data (AndrooDev, jff9oxO8v1s [00:04:01]). Client authority over the body is fine for casual co-op; competitive or cheat-prone games keep the body on the server and give clients their input only (Tamas Galffy, iP_xdJ0peFo [00:03:46, 00:06:47]; BatteryAcidDev, GqHTNmRspjU [00:00:33]). Deciding condition: cost of cheating against cost of input lag. Measured at 250 ms RTT: server authority shows your own move after 294 to 310 ms, client authority after 11 to 22 ms, and a client-authority cheater's 50 m jump stuck.
  2. Set authority from the node name in
    _enter_tree
    .
    Name each player node with its peer id;
    _enter_tree
    calls
    set_multiplayer_authority(name.to_int())
    so it holds before
    _ready
    (Travis Hunter, tK2ACXUGcrY [00:46:16]). Do not read a spawn-synced property there: on every client copy measured,
    peer_id
    was still 0 in
    _enter_tree
    .
  3. Every
    any_peer
    RPC is a request.
    Read
    multiplayer.get_remote_sender_id()
    first (it is 0 after an
    await
    , measured), check that the sender owns the target and that the values are possible, then act. A forged teleport for another player and a 30 m jump were both rejected in the live test.
  4. The RPC checksum compares method names only, whatever the docs say. The high-level docs say it covers every
    @rpc
    declaration and skips arguments; two live runs in 4.7.2 (M2, round 1 and round 2) disagree. A name missing on one side logs "The rpc node checksum failed" on both sides and RPCs are matched by sorted index: the client called
    b
    and the server ran
    a
    . A different mode only fails per call ("not allowed"), a different argument count fails per call ("expected 2 argument(s)"), different
    call_local
    or transfer mode passes silently. Keep server and client on one script per node path (one codebase: Davide Di Staso, nyKBuM9Y_-Q).
  5. Test with latency and three players. Localhost hides every latency bug; two players hide what a bystander sees (Galffy, iP_xdJ0peFo [00:09:58, 00:42:31]).
    gd_net.UdpLagProxy
    adds delay, jitter and loss on localhost without root.
  6. Replication cost is a design input. ALWAYS sends every network frame whether or not the value changed; ON_CHANGE is reliable and costs more than ALWAYS while values move (measured: 72.6 against 100 KiB/s for 50 moving props). A headless server is uncapped: 145 fps here, 2.2 to 2.4 times the bandwidth of 60 fps. Cap it (
    --max-fps
    ,
    Engine.max_fps
    ) or set
    replication_interval
    . Cut by visibility (Di Staso [00:14:29]).
  7. Know which connection event fires where. On a joining client,
    peer_connected
    fires once per existing peer, server id 1 included;
    connected_to_server
    fires on clients only, so send client info there, not from
    peer_connected
    (docs delta 7; FinePointCGI e0JLO_5UgQo [00:22:50]). After the server leaves,
    get_unique_id()
    reads 0: skip id 0 in registries and spawn loops (pA9RGn87Uag [00:47:31]). All measured (M14).
  8. The server stops speed hacks, not wall hacks. Server authority blocks impossible moves and infinite health; it does not stop wall hacks or aimbots, because every client still receives everyone's position. Interest management (synchronizer visibility, server line-of-sight checks) limits the leak (MfjEB1cowsE [00:18:55]; iP_xdJ0peFo [00:53:28]).
  9. The dedicated server is the same project exported in "dedicated server" mode. That mode adds the
    dedicated_server
    feature, which forces headless display and dummy audio even without
    --headless
    (measured on the exported macOS binary), and Strip Visuals removed the 3.1 MB texture from the server pack (BatteryAcidDev, jgJuX04cq7k [00:04:47]).
  1. 编写代码前逐对象决定权限归属。 每个对象的数据都必须有明确的拥有者(AndrooDev、jff9oxO8v1s [00:04:01])。在休闲合作游戏中,允许客户端拥有角色身体(body)的权限;竞技或容易出现作弊的游戏则应让身体由服务器掌控,客户端只提交输入(Tamas Galffy、iP_xdJ0peFo [00:03:46, 00:06:47];BatteryAcidDev、GqHTNmRspjU [00:00:33])。决策依据是作弊收益与输入延迟之间的成本权衡。在 250 ms RTT 下实测:服务器权限下,自己的移动在 294 至 310 ms 后可见;客户端权限下为 11 至 22 ms;而采用客户端权限的作弊者进行的 50 m 跳跃会生效。
  2. 在
    _enter_tree
    中根据节点名称设置权限归属。
    将每个玩家节点命名为其 peer id;
    _enter_tree
    调用
    set_multiplayer_authority(name.to_int())
    ,从而在
    _ready
    之前完成归属设置(Travis Hunter、tK2ACXUGcrY [00:46:16])。不要在该处读取通过 spawn 同步的属性:在每个客户端副本中实测,
    peer_id
    在
    _enter_tree
    时仍为 0。
  3. 每个
    any_peer
    RPC 都是请求。
    先读取
    multiplayer.get_remote_sender_id()
    (实测在
    await
    之后它会变为 0),检查发送者是否拥有目标对象以及参数值是否合理,然后再执行。实测中,伪造其他玩家传送以及 30 m 跳跃均被拒绝。
  4. 无论文档如何表述,RPC checksum 只比较方法名。 高层文档称其涵盖每个
    @rpc
    声明并忽略参数;但 4.7.2 中两次实时运行(M2,第 1 轮和第 2 轮)结果不一致。如果某一端缺少某个名称,两端都会记录“The rpc node checksum failed”,且 RPC 按排序后的索引进行匹配:客户端调用了
    b
    ,服务器却执行了
    a
    。另一种 mode 只会导致单次调用失败(“not allowed”),参数数量不同会导致单次调用失败(“expected 2 argument(s)”),而不同的
    call_local
    或 transfer mode 会静默通过。让服务器与客户端对同一节点路径使用同一份脚本(单一代码库:Davide Di Staso、nyKBuM9Y_-Q)。
  5. 在有延迟且有三名玩家时测试。 localhost 会掩盖所有延迟问题;两名玩家会掩盖旁观者看到的问题(Galffy、iP_xdJ0peFo [00:09:58, 00:42:31])。
    gd_net.UdpLagProxy
    可在 localhost 上增加延迟、抖动和丢包,且无需 root。
  6. 复制成本是设计输入。 ALWAYS 无论值是否变化都会在每个网络帧发送;ON_CHANGE 是 reliable,且在值持续变化时成本高于 ALWAYS(实测:50 个移动道具分别为 72.6 对 100 KiB/s)。headless 服务器不限制帧率:此处达到 145 fps,带宽是 60 fps 的 2.2 至 2.4 倍。应限制帧率(
    --max-fps
    、
    Engine.max_fps
    )或设置
    replication_interval
    。通过可见性裁剪复制内容(Di Staso [00:14:29])。
  7. 清楚连接事件在哪里触发。 在加入的客户端上,
    peer_connected
    会针对每个已存在 peer 触发一次,包括服务器 id 1;
    connected_to_server
    只在客户端触发,因此应在那里发送客户端信息,而不是在
    peer_connected
    中发送(文档差异 7;FinePointCGI、e0JLO_5UgQo [00:22:50])。服务器离开后,
    get_unique_id()
    读取为 0:在注册表和生成循环中跳过 id 0(pA9RGn87Uag [00:47:31])。以上均为实测结果(M14)。
  8. 服务器能阻止速度作弊,但阻止不了 wall hacks。 服务器权限可阻止不可能的移动和无限生命值;但无法阻止 wall hacks 或 aimbots,因为每个客户端仍会收到所有人的位置。兴趣管理(synchronizer 可见性、服务器视线检查)可限制信息泄露(MfjEB1cowsE [00:18:55];iP_xdJ0peFo [00:53:28])。
  9. 专用服务器就是同一项目以“dedicated server”模式导出的结果。 该模式添加
    dedicated_server
    feature,强制使用 headless 显示和 dummy audio,即使未传入
    --headless
    也是如此(在导出的 macOS binary 上实测);Strip Visuals 会从服务器包中移除 3.1 MB texture(BatteryAcidDev、jgJuX04cq7k [00:04:47])。

Establish first

首先确定

Genre and player count; who may cheat (competitive, co-op, friends only); platforms (browser means WebSocket or WebRTC, no ENet); topology (dedicated server, listen server, relay or P2P); a backend already chosen (Steam, Nakama, Colyseus, own server); hosting budget (cloud costs money: ask before any deployment); tick and target RTT (default 60 Hz server, play tested at 100 and 250 ms). Defaults: one project, server authority with input-only clients for anything competitive, ENet for desktop and mobile, WebSocket behind TLS (
wss://
) for the web.
游戏类型和玩家数量;谁可能作弊(竞技、合作、仅好友);平台(浏览器意味着使用 WebSocket 或 WebRTC,不能用 ENet);拓扑(dedicated server、listen server、relay 或 P2P);是否已选定 backend(Steam、Nakama、Colyseus、自建服务器);托管预算(云服务需要花钱:部署前先询问);tick 和目标 RTT(默认服务器 60 Hz,并在 100 和 250 ms 下进行游戏测试)。默认方案:单一项目;竞技类游戏一律采用服务器权限和仅输入客户端;桌面和移动端使用 ENet;Web 使用 TLS(
wss://
)保护的 WebSocket。

Workflow

工作流

  1. Network design note. Per object: owner, what replicates (synchronizer property and mode), what is an event (RPC with mode, transfer and channel), what a client may request. Streams of unreliable_ordered updates get their own transfer channel, channel last in
    @rpc
    (docs deltas 4, 5); idle players send nothing below a velocity threshold (pA9RGn87Uag [00:42:16]). GATE: no object without an owner; no client-owned health, score or inventory unless the brief accepts cheating.
  2. Build the replication in code.
    gd_net.install_netkit(P)
    , then
    net_build.gd
    (
    make_sync
    ,
    make_spawner
    ) writes scenes with
    SceneReplicationConfig
    set in code; the Replication panel is for humans (
    references/gui-paths.md
    ). Players: name = peer id,
    add_child(node, true)
    , authority in
    _enter_tree
    . GATE:
    net_audit.gd:scene
    has no flags (root paths resolve, no Object or RID property, no
    @Node@2
    names, intervals match modes).
  3. Session test on localhost.
    gd_net.session(P, roles)
    runs a server and 2 to 3 clients, each in its own APFS clone, all machine slots held first. Cover the lobby gate, a late joiner, a leaver, cheat RPCs, and a password through
    auth_callback
    plus
    complete_auth
    on both sides (P5). GATE: every client sees every player, the late joiner sees all, the leaver's node is freed everywhere, authority report correct, forged RPCs rejected, logs clean.
  4. Latency pass. Rerun through
    UdpLagProxy
    at 100 and 250 ms, then 250 ms with 30 ms jitter and 5 % loss;
    replication_delay()
    measures how far behind the server truth a remote client shows a player. GATE: the numbers fit the genre, or prediction is planned (netfox or own).
  5. Bandwidth pass. Server KiB/s per client from ENet counters (
    bandwidth
    in the result), per mode, interval and visibility. GATE: under the budget at max players, fps capped.
  6. Web and transports. Same session with
    transport="websocket"
    ; for a browser build, export Web and run the client in headless Chrome (procedure P9). WebSocket is TCP: "unreliable" RPCs arrive reliable and ordered, with head-of-line blocking under loss; WebRTC gives unreliable browser channels but needs signalling, STUN/TURN and webrtc-native on desktop (WebRTC doc; wgIqB6JNcro [00:19:35]). An HTTPS page needs
    wss://
    . A background tab stops
    _process
    and the socket drops: build reconnect (web doc). GATE: the browser client joins, sees all players, RTT measured.
  7. Dedicated server.
    gd_net.server_preset()
    , export, compare client and server packs (
    pack_report
    ), run the exported binary without
    --headless
    and connect clients. Set
    application/run/flush_stdout_on_print=true
    for systemd or journald logs (docs delta 12; default false, measured). GATE:
    dedicated_server
    feature true, display
    headless
    , no client textures in the pack, exit 0.
  8. Look. A windowed observer client captures the replicated world (
    capture_at
    ); open it. GATE: players where the server says, colors per peer, nothing missing.
  1. 网络设计说明。 逐对象记录:所有者、复制内容(synchronizer 属性和 mode)、哪些内容属于事件(带 mode、transfer 和 channel 的 RPC)、客户端可以提出哪些请求。unreliable_ordered 更新流应使用独立 transfer channel,且 channel 放在
    @rpc
    参数列表末尾(文档差异 4、5);速度低于阈值的闲置玩家不发送任何数据(pA9RGn87Uag [00:42:16])。GATE:任何对象都不能没有所有者;除非任务说明接受作弊,否则客户端不得拥有生命值、分数或背包。
  2. 用代码构建复制系统。
    gd_net.install_netkit(P)
    ,然后
    net_build.gd
    (
    make_sync
    、
    make_spawner
    )通过代码写入场景并设置
    SceneReplicationConfig
    ;Replication 面板供人工使用(
    references/gui-paths.md
    )。玩家:名称 = peer id,
    add_child(node, true)
    ,在
    _enter_tree
    中设置 authority。GATE:
    net_audit.gd:scene
    无任何 flags(根路径可解析、不含 Object 或 RID 属性、没有
    @Node@2
    名称、间隔与 modes 匹配)。
  3. 在 localhost 上进行 session test。
    gd_net.session(P, roles)
    启动一个服务器和 2 至 3 个客户端,每个客户端使用独立的 APFS clone,并先占用所有机器槽位。覆盖大厅门槛、迟加入者、离开者、作弊 RPC,以及通过
    auth_callback
    和双方
    complete_auth
    实现的密码流程(P5)。GATE:每个客户端都能看到所有玩家;迟加入者能看到所有玩家;离开者的节点在所有位置都被释放;authority 报告正确;伪造 RPC 被拒绝;日志干净。
  4. 延迟测试。 通过
    UdpLagProxy
    在 100 和 250 ms 下重新运行,然后在 250 ms 下加入 30 ms 抖动和 5% 丢包;
    replication_delay()
    衡量远端客户端显示的玩家状态落后服务器真实状态多少。GATE:数值应符合游戏类型,否则应规划 prediction(netfox 或自行实现)。
  5. 带宽测试。 通过 ENet counters 获取每客户端的服务器 KiB/s(结果中的
    bandwidth
    ),按 mode、interval 和 visibility 分别统计。GATE:在最大玩家数和 fps 受限条件下低于预算。
  6. Web 与传输协议。 使用
    transport="websocket"
    运行相同 session;对于浏览器构建,导出 Web 并在 headless Chrome 中运行客户端(流程 P9)。WebSocket 是 TCP:“unreliable” RPC 实际上会可靠、有序到达,并在丢包时产生队头阻塞;WebRTC 可为浏览器提供不可靠 channel,但需要 signalling、STUN/TURN,且桌面端需要 webrtc-native(WebRTC doc;wgIqB6JNcro [00:19:35])。HTTPS 页面需要
    wss://
    。后台标签页会停止
    _process
    并断开 socket:应构建重连机制(web doc)。GATE:浏览器客户端能够加入、看到所有玩家且完成 RTT 测量。
  7. 专用服务器。
    gd_net.server_preset()
    ,导出,对比客户端和服务器包(
    pack_report
    ),在不带
    --headless
    的情况下运行导出的 binary 并连接客户端。为 systemd 或 journald 日志设置
    application/run/flush_stdout_on_print=true
    (文档差异 12;默认 false,实测)。GATE:
    dedicated_server
    feature 为 true,显示为
    headless
    ,包内无客户端 textures,并以状态码 0 退出。
  8. 观察。 窗口模式的 observer client 捕获复制后的世界(
    capture_at
    );打开它。GATE:玩家位置与服务器一致、颜色按 peer 区分且没有缺失。

Harness in one call

一次调用完成测试框架

python
import sys; sys.path[:0] = ["<skills>/scenario-godot-expert/scripts", "<skills>/scenario-godot-multiplayer/scripts"]
import gd_net
gd_net.install_netkit(P); gd_net.build_scenes(P)          # res://net/world.tscn, player.tscn, props
roles = [gd_net.role("server", role="server", port=24010, lobby_size=2),
         gd_net.role("c1", role="client", port=24011, index=0, delay_s=0.3, cheat_at=2.0),
         gd_net.role("c2", role="client", port=24011, index=1, delay_s=0.3)]
with gd_net.UdpLagProxy(24011, 24010, rtt_ms=100, jitter_ms=0, loss=0.0):
    s = gd_net.session(P, roles)                            # one APFS clone and one Godot per role
s["roles"]["c1"]["result"]["rtt_median_ms"]                 # every NetLab number is in AGENT_RESULT
Your own game uses the same pattern: a job script per role (
role(name, script=...)
), user args after
--
,
AGENT_RESULT
printed by each process. Keep ports unique per project; two roles never share a project folder.
python
import sys; sys.path[:0] = ["<skills>/scenario-godot-expert/scripts", "<skills>/scenario-godot-multiplayer/scripts"]
import gd_net
gd_net.install_netkit(P); gd_net.build_scenes(P)          # res://net/world.tscn, player.tscn, props
roles = [gd_net.role("server", role="server", port=24010, lobby_size=2),
         gd_net.role("c1", role="client", port=24011, index=0, delay_s=0.3, cheat_at=2.0),
         gd_net.role("c2", role="client", port=24011, index=1, delay_s=0.3)]
with gd_net.UdpLagProxy(24011, 24010, rtt_ms=100, jitter_ms=0, loss=0.0):
    s = gd_net.session(P, roles)                            # one APFS clone and one Godot per role
s["roles"]["c1"]["result"]["rtt_median_ms"]                 # every NetLab number is in AGENT_RESULT
你自己的游戏也使用相同模式:每个 role 使用一个 job script(
role(name, script=...)
),用户参数放在
--
之后,每个进程都会输出
AGENT_RESULT
。每个项目应使用唯一端口;两个 role 不得共用项目文件夹。

Prediction, rollback and backends (not run here)

预测、回滚与后端(此处未运行)

netfox gives server authority with client prediction and rollback: input gathered on the network tick, logic in
_rollback_tick
,
NetworkTime.physics_factor
around
move_and_slide
,
TickInterpolator
for display (GqHTNmRspjU [00:06:58, 00:18:55, 00:21:24]; iP_xdJ0peFo [00:28:00]). Prove it on a small prototype first: it is opinionated (GqHTNmRspjU [00:34:53]). Deterministic input-only rollback fits fighting and RTS-like games with 2 to 8 players (David Snopek, zvqQPbT8rAE). Steam (GodotSteam, SteamMultiplayerPeer), Nakama, Colyseus and WebRTC P2P swap the peer and lobby calls; authority, spawner and synchronizer code stays the same (Gwizz, fUBdnocrc3Y; AndrooDev, wgIqB6JNcro [00:07:18]). None of these ran here: they need a third-party addon, an account or a signalling server. Verify against the installed release before quoting an API.
netfox 提供服务器权限下的客户端 prediction 和 rollback:在网络 tick 上收集输入,在
_rollback_tick
中执行逻辑,在
move_and_slide
周围使用
NetworkTime.physics_factor
,并使用
TickInterpolator
进行显示(GqHTNmRspjU [00:06:58, 00:18:55, 00:21:24];iP_xdJ0peFo [00:28:00])。应先在小型 prototype 上验证:它的设计取向非常强势(GqHTNmRspjU [00:34:53])。确定性的 input-only rollback 适合 2 至 8 人的格斗和类 RTS 游戏(David Snopek、zvqQPbT8rAE)。Steam(GodotSteam、SteamMultiplayerPeer)、Nakama、Colyseus 和 WebRTC P2P 只需替换 peer 和 lobby 调用;authority、spawner 和 synchronizer 代码保持不变(Gwizz、fUBdnocrc3Y;AndrooDev、wgIqB6JNcro [00:07:18])。此处未运行上述方案:它们需要第三方 addon、账户或 signalling server。引用 API 前,应针对已安装的版本进行验证。

Numbers

数值

ValueMeasured here (2026-10-02, M-series Mac, localhost)Relative to
RPC round trip, no proxy16 to 19 ms60 fps both ends; one frame each way dominates
ENet RTT statistic15 to 23 mssame
Own input to visible motion, server authority47 to 52 msabout 3 frames
Remote view behind server truth20 to 30 ms, 0.07 m error at 3 m/sno proxy
Same at 100 / 250 ms RTT70 to 80 / 150 to 155 ms; 0.22 / 0.44 m
UdpLagProxy
, adds about 6 ms per direction
50 props, ALWAYS, 60 fps72.6 KiB/s, moving or idle1 client
ON_CHANGE moving / idle98 to 101 / 2.3 KiB/ssame
ALWAYS,
replication_interval
0.05
21 to 22 KiB/ssame
ALWAYS, uncapped headless server (145 fps)160 to 175 KiB/ssame
auth_timeout
default; kick of a silent peer
3.0 s; 1.50 s after setting 1.5server
Web export, single-thread40 MB, wasm 39.5 MB (10.1 MB gzip)handoff to scenario-godot-performance-export
Dedicated server pack / client pack0.11 / 3.27 MB (3.1 MB texture stripped)same project
spawn_limit
0 = unlimited (default); limit 2: third
spawn()
logs "Spawn limit reached!", returns null, clients get 2
M14; raise it to max players (jgJuX04cq7k [00:18:34])
ValueMeasured here (2026-10-02, M-series Mac, localhost)Relative to
RPC 往返,无代理16 至 19 ms两端均为 60 fps;每个方向一帧的延迟占主导
ENet RTT 统计值15 至 23 ms相同
从自身输入到可见移动,服务器权限47 至 52 ms约 3 帧
远端视图落后服务器真实状态20 至 30 ms,3 m/s 时误差为 0.07 m无代理
100 / 250 ms RTT 下的相同数值70 至 80 / 150 至 155 ms;0.22 / 0.44 m
UdpLagProxy
,每个方向额外增加约 6 ms
50 个道具,ALWAYS,60 fps72.6 KiB/s,无论移动或静止1 个客户端
ON_CHANGE,移动 / 静止98 至 101 / 2.3 KiB/s相同
ALWAYS,
replication_interval
0.05
21 至 22 KiB/s相同
ALWAYS,未限帧的 headless 服务器(145 fps)160 至 175 KiB/s相同
默认
auth_timeout
;踢出静默 peer
3.0 s;设置为 1.5 后为 1.50 s服务器
Web 导出,单线程40 MB,wasm 39.5 MB(gzip 后 10.1 MB)移交给 scenario-godot-performance-export
专用服务器包 / 客户端包0.11 / 3.27 MB(已剥离 3.1 MB texture)同一项目
spawn_limit
0 = 无限制(默认);限制为 2 时:第三次
spawn()
记录“Spawn limit reached!”,返回 null,客户端收到 2 个
M14;提高到最大玩家数(jgJuX04cq7k [00:18:34])

Quality gates

质量门槛

  • Measurable:
    net_audit
    flags empty; every role
    ok
    with no unexpected engine error;
    players_seen_max
    equals the player count everywhere; authority body 1, input = peer; teleport counts as expected; RTT and
    replication_delay
    at 100 and 250 ms; KiB/s per mode;
    rpc_audit
    reviewed,
    rpc_pair_check
    ok; exported server boot dict correct.
  • Visual: observer capture shows every player; under 250 ms no streak after a teleport; the browser screenshot shows the replicated players.
  • 可量化:
    net_audit
    无任何 flags;每个 role 均为
    ok
    ,且没有意外引擎错误;所有位置的
    players_seen_max
    都等于玩家数量;authority 报告为 body 1、input = peer;teleport 次数符合预期;记录 100 和 250 ms 下的 RTT 与
    replication_delay
    ;记录各 mode 的 KiB/s;审核
    rpc_audit
    ,且
    rpc_pair_check
    为 ok;导出的服务器启动字典正确。
  • 视觉:observer capture 显示所有玩家;在 250 ms 下,teleport 后没有拖影;浏览器截图显示已复制的玩家。

Common mistakes

常见错误

MistakeWhat it looks likeFix
Players spawned only on the server without a spawner, or spawner
spawn_path
empty
client sees nobody
MultiplayerSpawner
with
spawn_path
and the scene in its list; server calls
add_child
Authority read from a synced property in
_enter_tree
authority 1 everywhere on clientsauthority from
name.to_int()
Runtime node without
add_child(node, true)
@Node@2
names, "node not found" on RPC
readable names
await
before
get_remote_sender_id()
sender 0, validation passes or fails wronglyread the sender first
RPC added on the server script onlychecksum error, the wrong method runsone script, or
rpc_pair_check
Object or Resource as RPC argumentno error; receiver gets
EncodedObjectAsID
send ids, paths or plain data
Client owns its body in a competitive gamespeed and teleport hacks stickinput-only authority plus server simulation
Headless server not capped2.4 times the bandwidth
--max-fps 60
or intervals
server_relay
turned off for a P2P-style feature
"Attempt to call RPC with unknown peer ID"keep relay on, or route through the server
Freeing a networked branch in the wrong order"isn't currently active", "Node not found"offline peer, free contents, drop custom API, free branch
Server export in normal modefull-size pack, window opensdedicated server export mode, Strip Visuals
Server start inside
assert()
works from the editor; the release export never listens
var err := peer.create_server(port)
, then check
err
(M15: assert body stripped in release)
Client info sent from
peer_connected
, or id 0 registered
duplicate nodes on late joiners, a phantom player
connected_to_server
; skip id 0
Client and server on different Godot versionsconnection or protocol failuressame 4.7.2 build (protocol changed in 4.3)
MistakeWhat it looks likeFix
仅在服务器上生成玩家但未使用 spawner,或 spawner 的
spawn_path
为空
客户端看不到任何人使用带
spawn_path
的
MultiplayerSpawner
,并将场景加入其列表;由服务器调用
add_child
在
_enter_tree
中从已同步属性读取 authority
客户端上的 authority 全为 1根据
name.to_int()
设置 authority
运行时节点未使用
add_child(node, true)
出现
@Node@2
名称,RPC 报“node not found”
使用可读名称
在
get_remote_sender_id()
前执行
await
sender 为 0,验证错误地通过或失败先读取 sender
RPC 仅添加到服务器脚本中checksum 错误,执行了错误的方法使用单一脚本,或运行
rpc_pair_check
将 Object 或 Resource 作为 RPC 参数不报错;接收方收到
EncodedObjectAsID
发送 ids、paths 或普通数据
竞技游戏中由客户端拥有身体权限速度与传送作弊会生效使用仅输入权限和服务器模拟
未限制 headless 服务器帧率带宽达到 2.4 倍使用
--max-fps 60
或 replication intervals
为 P2P 式功能关闭
server_relay
出现“Attempt to call RPC with unknown peer ID”保持 relay 开启,或通过服务器进行路由
以错误顺序释放 networked branch出现“isn't currently active”或“Node not found”将 peer 设为 offline,释放 contents,丢弃自定义 API,再释放 branch
以普通模式导出服务器包体积完整,窗口被打开使用 dedicated server 导出模式和 Strip Visuals
在
assert()
中启动服务器
编辑器中正常,release 导出版本从不监听端口使用
var err := peer.create_server(port)
,然后检查
err
(M15:release 会剥离 assert 内容)
从
peer_connected
发送客户端信息,或注册 id 0
迟加入者出现重复节点或幽灵玩家使用
connected_to_server
;跳过 id 0
客户端与服务器使用不同的 Godot 版本连接或协议失败使用相同的 4.7.2 构建(4.3 时协议发生过变化)

Handoffs

交接

  • Receives: the game scene and player controller from scenario-godot-gameplay and scenario-godot-3d-world or scenario-godot-2d; architecture rules from scenario-godot-architecture.
  • Delivers: network design note, kit scenes or scripts, session evidence JSON, server preset; the web build size and headers go to scenario-godot-performance-export, CI jobs to scenario-godot-pipeline-automation, lobby screens to scenario-godot-ui.
  • 接收:来自 scenario-godot-gameplay 以及 scenario-godot-3d-world 或 scenario-godot-2d 的游戏场景和玩家控制器;来自 scenario-godot-architecture 的架构规则。
  • 交付:网络设计说明、kit 场景或脚本、session 证据 JSON、服务器 preset;Web 构建体积和 headers 移交给 scenario-godot-performance-export,CI jobs 移交给 scenario-godot-pipeline-automation,大厅界面移交给 scenario-godot-ui。

Godot 4.7 notes

Godot 4.7 注意事项

  • get_rpc_config
    became
    get_node_rpc_config
    (4.5);
    @rpc
    tables live on
    Script.get_rpc_config()
    .
  • auth_timeout
    is the property; the docs page spells
    auth_timout
    .
  • 4.7.2 fixed peers that stopped replicating after deleting a spawner-spawned node.
  • Godot 3 names (
    NetworkedMultiplayerENet
    ,
    remote
    ,
    rset
    ,
    get_rpc_sender_id
    ) do not exist;
    gd_net.rpc_audit
    flags them.
  • WebRTC on desktop needs the webrtc-native extension:
    initialize()
    returns OK and only logs an error without it (measured).
  • Open: under heavy machine load, 4 of 20 ALWAYS runs sent about 10 packets/s instead of 68 (ENet throttle at 0). Log
    NetUtil.enet_throttle()
    in long sessions (P8).
  • Two peers leaving in one server frame log "Unable to send packet on channel 0" (ENet) or
    ready_state != STATE_OPEN
    (WebSocket); harmless, whitelisted by exact text.
  • get_rpc_config
    已更名为
    get_node_rpc_config
    (4.5);
    @rpc
    表位于
    Script.get_rpc_config()
    。
  • auth_timeout
    是正确属性名;文档页面将其拼写为
    auth_timout
    。
  • 4.7.2 修复了删除 spawner 生成节点后停止复制的问题。
  • Godot 3 的名称(
    NetworkedMultiplayerENet
    、
    remote
    、
    rset
    、
    get_rpc_sender_id
    )不存在;
    gd_net.rpc_audit
    会标记这些名称。
  • 桌面端 WebRTC 需要 webrtc-native extension:缺少该 extension 时,
    initialize()
    会返回 OK,但只记录错误(实测)。
  • 尚未解决:在机器负载较高时,20 次 ALWAYS 运行中有 4 次只发送约 10 packets/s,而非 68 packets/s(ENet throttle 为 0)。在长时间 session 中记录
    NetUtil.enet_throttle()
    (P8)。
  • 两个 peer 在同一服务器帧内离开时,会记录“Unable to send packet on channel 0”(ENet)或
    ready_state != STATE_OPEN
    (WebSocket);无害,已按确切文本加入白名单。

References

参考资料

  • references/procedures.md
    : P1 to P14 with code, test ids and results.
  • references/expert-notes.md
    : claims by expert with timestamps and verified corrections.
  • references/critique.md
    ,
    references/gui-paths.md
    (Replication panel, Run Instances, Network Profiler),
    references/sources.md
    .
  • scripts/gd_net.py
    ,
    scripts/agentkit/multiplayer/
    : the harness and kit.
  • references/procedures.md
    :P1 至 P14,包含代码、测试 ID 和结果。
  • references/expert-notes.md
    :专家观点、时间戳和已验证的修正。
  • references/critique.md
    、
    references/gui-paths.md
    (Replication 面板、Run Instances、Network Profiler)、
    references/sources.md
    。
  • scripts/gd_net.py
    、
    scripts/agentkit/multiplayer/
    :测试框架和 kit。