# Helix Empire —— 一万名玩家共处同一个实时世界

一款浏览器端的实时太空策略游戏，核心是遗传学：玩家通过编辑 DNA 来培育各类生物种群。难点在引擎——让一万名玩家在同一台服务器上共享一个世界，并实时更新。我把架构设计在真正的瓶颈上（出站流量，而非 CPU），并用一次实测的压力测试证明了它：一万玩家时 237.6 Mbit/s，占预算的 24%。单人独立完成，目前处于内测阶段。

- **Author:** Fedor Chishchin
- **Role:** 独立架构师兼工程师
- **Period:** 2026 — 至今
- **Stack:** Rust · WebAssembly · WebTransport / QUIC · Event sourcing · Area of Interest · Hetzner bare-metal
- **Source:** https://fedorthinks.com/zh/work/helix-empire

---

## 问题

**Helix Empire** 是一款实时的浏览器端太空策略游戏。它的核心不是建造——
而是**遗传学**：玩家通过编辑 DNA 来培育各类生物种群（这正是「Helix」的由来——
双螺旋）。基因决定性状，性状决定生物在不同环境下的表现，这驱动经济和军队，进而驱动
科技，而科技又解锁对基因组的新编辑。一个闭环：**基因 → 性状 → 经济 → 科技 → 实验室
→ 再次回到基因。** 帝国实力 = 基因 × 行星 × 科技。

从技术上讲，这意味着**数千名玩家共处同一个世界**，并实时存活——经济在跳动、生物在
繁殖、舰队在移动、资源每秒都在变化——而**这一切都必须毫无延迟地送达每位玩家的浏览器。**

大多数在线游戏都在掩盖自己的规模：把玩家拆成 20–50 人的小房间，或者拆成分片。我故意
设了一个更难的目标——**一万名玩家同时在线，处于同一台服务器上的同一个世界**——因为
有趣的架构恰恰就藏在这个约束里。

## 四堵墙

把「一万人在同一个世界」说出口，就会冒出四堵墙，naive 的设计会一头撞上去。

1. **「人人对人人」的广播是 O(N²)。** 如果每个玩家都必须知道其他每个玩家，一次 tick
   就是 `10,000 × 10,000 = 100,000,000` 对——而且每秒要算好几次。物理上不可能。玩家
   翻倍，工作量翻四倍。
2. **主要开销是流量，不是 CPU。** 一个反直觉的托管事实：**出站流量（egress）才是最贵
   的部分**——在超大规模云厂商上比裸金属贵几十倍。如果每个 tick 都向每个玩家发送大量
   字节，流量账单会在 CPU 顶不住之前就先把项目拖垮。所以架构必须围绕最小化流量来设计。
3. **一个世界想跑在一台服务器上。** 把一个世界铺在多台机器上，就会被迫引入分布式共识
   （哪台服务器持有真正的状态？）——又慢又复杂。我选了 **single-writer**：恰好只有一个
   进程可以修改一个世界。没有竞态，没有共识。（它有一个讨厌的陷阱——下文细说。）
4. **瘦客户端跟不上。** 如果服务器算一切、浏览器只负责画，服务器就成了瓶颈。客户端必须
   是**厚的**——能从最少的数据里重建出整幅画面。

## 这套技术栈——针对墙而选，不追潮流

架构的意义在于，**技术服务于约束，而非时髦。** 这里的每一个选择都回应了某一堵具体的墙。

## 我一步步建了什么——每一步都在测量

我不是一次建好的。我是分层推进，**在每一层都做测量**——因为你没法优化你没测量过的东西。

**第 0 步——基线。** 在动手修任何东西之前，我先测了它有多糟：状态读取会飙到
**4–10 秒**，向客户端推送大约**每 20 秒一帧**。这给了我一个要打败的数字。

**第 1–3 步——消除 single-writer 争用。** 陷阱在这里。既然一个进程在修改世界，那么在它
应用一次 tick（写入存储——很慢）期间，**所有读者都在等同一把锁**。打开防御界面就卡 10 秒，
因为服务器正卡在存盘中。修法：**RAM 里的无锁读模型缓存**，增量更新，读取时不需要写锁。

架构上的动作：**把读和写分开。** 一个写者仍然掌管一致性；读者读 RAM 投影，不再等待。
争用消失了，每个端点都稳定地降到一秒以内。

**第 4–5 步——发送差异，而不是整份状态。** 即便读取已经很快，每个 tick 仍然发送*完整*
状态——把成堆没变过的东西重新发一遍。修法：**增量（deltas）**。一个经过数学校验的
`WireSessionStateDelta`，其中 `between(prev, next)` 构造差异，`apply(prev, delta)` 把它
回放。一个收敛测试证明：客户端折叠的一连串增量*精确*重现了服务器快照——而且既然客户端和
服务器共享同一份 Rust 代码，它们就不会漂移。丢了一帧？客户端会察觉（`ResyncRequired`）并
请求一份完整快照。

**第 6 步——Area of Interest 把 O(N²) 砍到线性。** 一个玩家并不需要全部一万人——只需要他
在空间上和外交上的邻居。`AreaOfInterest` + `filter_frame` 把每个增量裁剪到订阅者真正在意
的内容，同时保留全殖民地范围的上下文（聊天、交易）。二次方广播变成线性：流量按
N ×（兴趣区大小）增长，而不是 N²。

**第 7 步——诚实的最后一公里。** 这是整个故事里最重要的部分。一次压力测试揭示，那个漂亮的
「320 Mbit/s」数字建立在一个**硬编码的假设**之上：每个玩家增量 32 字节。我接上真实的二进制
编解码器并测量：一个真实增量是 **104 字节**，因为我即便只有一个字段变了，也要发送一个玩家
的*整份*档案（11 个资源字段）。104 对 32 是**超出 3.25 倍**——在一万玩家时投影到约
1.04 Gbit/s。那一刻诚实的回答是：**「不行，在真实流量下我扛不住一万。」**

修法是一个**按字段的增量**，`WirePlayerDelta`：

```
player_id (4 字节) + 变更字段位掩码 (2 字节) + 仅变更的值
```

如果人口、食物和科技变了，线上就恰好携带这三个，再加掩码——
`4 + 2 + 4 + 8 + 8 = 26 字节`，而不是 104。这个 16 位的掩码说明后面跟着哪些字段；一个没变的
字段不花任何成本。

## 它是如何被证明的——以及为什么这个数字值得信

没有证明的架构只是一个承诺。我用数字证明了它：往返与收敛单元测试、每个端口上的契约测试，
以及 **42 个跑在真实 Chromium 上、走 WebTransport 的端到端测试**（包括「在实时套接字上接收
服务器 tick，无需轮询」和「同一个世界里的两名玩家实时同步」）。

最核心的证明是一次一万机器人的蜂群测试，它用**真实的二进制编解码器**测量 egress——没有任何
硬编码常量。它在一个世界里构建一万个机器人客户端，跑一次真实的权威 tick，让每个机器人的可见
增量都经过真实的 AoI 过滤，再把测出来的字节加总。

这个数字由一道 `check:load` 关卡守护：每次运行都会校验报告，而投影是**从实测值推导出来的，
不是来自假设**。如果有人不小心把增量格式撑大了，这道关卡就会失败。

## 诚实的边界

架构上的成熟，不只是拿到一个好数字——还包括对它的局限保持诚实。

**已证明：** 就算力和延迟而言，向一万机器人的扇出稳稳保持在 200 ms 以内，余量很大；就 egress
而言，237.6 Mbit/s（1 Gbit/s 预算的 24%），用真实编解码器测量得出，不是假设出来的。

**尚未证明：** 这是从一万玩家时的一次实测 tick（机器人在同一个进程里）做出的投影，而不是一个
有一万个真实 QUIC 套接字、连续数小时承压的实时集群。在持续数分钟的数据流下 CPU/RAM 的表现，
以及面向厚 WASM 客户端的逐订阅者 AoI 通道，是下一层要做的工作。但**最大的风险——egress，也就是
我自己主动标记为「在真实格式下超预算」的那一项——已经关闭并被测量。**

## 这里属于架构师、而不只是开发者的部分

1. **点中了真正的瓶颈：** egress，而不是 CPU。整套技术栈都服务于这一个结论。判断错了，你就会
   去优化错误的东西。
2. **把读和写分开：** 用 single-writer 保证一致性 + 无锁 RAM 投影服务读取。在不牺牲正确性的
   前提下消除了争用。
3. **把 O(N²) 降到线性**，靠的是 Area of Interest——没有它，一万人原则上就不可能。
4. **用增量代替快照**，服务器和客户端共用一份代码，于是收敛由架构保证，而非靠运气。
5. **没测量之前不轻信那个漂亮数字。** 发现「32 字节」是个假设，测出真实的 104，设计了按字段
   的增量，把它压到 26，并**用测量证明了它。**
6. **用一道关卡锁住结果**，让回归没法悄悄溜过去。

一个能**经过测量**、在 24% 流量预算下于同一个世界服务一万名玩家的引擎，不是运气，也不是单个
小把戏。它是一连串架构决策，每一个都回应一堵具体的墙，每一个都有数字背书。

## 技术栈速览

| 层       | 选择                                | 回应的墙                                   |
| -------- | ----------------------------------- | ------------------------------------------ |
| 仿真内核 | Rust，确定性，编译为原生 + WASM     | 厚客户端，每台服务器更多玩家（#2、#4）     |
| 传输     | WebTransport / QUIC，WebSocket 回退 | 低延迟二进制流（#2）                       |
| 世界模型 | Single-writer + event sourcing      | 无共识，可恢复（#3）                       |
| 读取     | 无锁 RAM 读模型投影                 | 无争用、一秒以内的读取                     |
| 更新     | 按字段二进制增量 + Area of Interest | O(N²) → 线性，egress 最小化（#1、#2）      |
| 托管     | Hetzner bare-metal                  | egress 比超大规模云厂商便宜 20–40 倍（#2） |

Helix Empire 处于内测，单人独立完成——产品设计、Rust 仿真内核、WASM 客户端、传输、读写分离、
增量 + AoI 流水线、压力测试框架，以及那份经过测量的证明。很快就能在 **helixempire.com** 上玩到。
