跳到主要内容

世界与 Tick

一个永久世界

  • 所有玩家共享同一个永久运行的二维方格世界。
  • 没有赛季,没有对局重置,也没有 NPC、野怪或服务器舰队。
  • 每个账号同一时间最多拥有一个存活的 Core。
  • Unit 和每一代 Core 使用不可枚举的 UUID。对象活着的时候 ID 一直不变,死亡之后 这个 ID 永不复用。
  • 每个 Core 都带公开的 owner_username,客户端显示为 @username。内部账号 ID、邮箱和 Unit 所属玩家不会进入状态。
  • v0.1 没有公开排行榜。

如果一个账号正好在世界结算期间激活,它不会被塞进一份没做完的快照里。服务端会给它 记一个持久的 activation_tick,玩家在那个 Tick 走确定性重生流程进入世界,和其他人 一样。

确定性区块生成

地形以 32×32 区块为单位,由一个永久保密的 world seed 加上版本化的 HMAC-SHA256 协议 生成,客户端永远拿不到 seed。

这给了你几条可以放心依赖的保证:

  • 世界、生成器版本、平衡参数和坐标都相同,地形就一定相同。
  • 相邻区块之间的边界通道是确定性的。
  • 任何可通行区域都连到区块主干上;但允许出现一格宽的瓶颈,而且这种地形往往很关键。
  • [0, 0] 以及它通往主干的那条路永久是 EMPTY,所以 Champion Beacon 不会被围死。
  • 生成器协议对不上,服务直接拒绝启动。也就是说改动生成语义就得换一个世界数据库。

障碍和主干通道属于永久地形。资源点则是另一层可消耗地图数据,每个区块都有固定配额, 补充位置同样按确定性规则生成。配额和放置规则见 地图与视野

Tick 生命周期

每个逻辑 Tick 都是固定长度的命令阶段,加上一段长短不固定的结算阶段。

窗口先开,之后服务端才逐个发布玩家状态。所以你拿到手的,是那 15 秒里还剩下的 那部分;收到 state 并不会给你单独起一个 15 秒计时。协议也故意不公开 opened_atdeadline_at

两条消息别搞混:tick 只是宣布有这么一个逻辑 Tick,此时命令还没开放;state 才是 唯一的行动信号。

结算顺序

下面这个顺序属于协议的一部分,不是实现细节:

  1. 锁定最终有效的 Agent 与 Manual 计划。
  2. 结算所有 SELF_DESTRUCT,立即移除这些 Unit,并把 Worker Cargo 掉在最后所在格。
  3. 结算 Unit 移动,以及走到第 4 个 Tick 的 Core 迁移。
  4. 检查新提交的 Core START_MOVE
  5. 结算 Champion Beacon 的拾取与放下。
  6. 结算 Worker 的采集与交付。
  7. 冻结不可变战斗快照,累计所有合法攻击。
  8. 同时应用伤害、移除死亡对象,并转移符合条件的被摧毁 Core 库存。
  9. 结算所有战斗后仍存活 Core 的 SELF_DESTRUCT;销毁库存和全军,并让 Worker Cargo 与 Beacon 掉在各自实际位置。
  10. 按 Unit UUID 顺序结算仍存活 Unit 的 HEAL
  11. 记录每名玩家此时的存活人口,再结算其余存活 Core 的 HEALREPAIR_SHIELD 或 动态定价的 SPAWN 动作。
  12. 立即尝试让新摧毁的 Core 重生,只有找不到合法出生点时才顺延到下一 Tick 重试。
  13. 每结算满 4 个 Tick,按区块只补回已经消耗的资源槽位,使可用资源总数回到该区块的 固定配额。
  14. 原子提交世界、资源层、事件、统计、journal 和新时钟。
  15. 宣布下一个 Tick,并准备新的私有状态。

服务端不会为了追上墙上时间而跳 Tick,停服期间世界就是暂停。两个 Tick 也绝不会同时 结算。

资源补充属于本 Tick 的结算末尾。在补充 Tick 里被采掉的点会先消失,随后补充步骤再填满 区块缺少的槽位。没被采的点不会移动,没用掉的配额也不会累计到上限以上。

原子性与重放

一个 Tick 的结果在同一个 PostgreSQL 事务里提交,所以客户端不存在看到「结算了一半的 世界」的时机。规则引擎同样不允许用 map 遍历顺序、墙上时间、进程随机数或者无序的查询 结果去决定胜负。

世界状态和锁定计划都相同,规则算出来的结果就逐字节相同。

崩溃恢复

故障点恢复方式
状态准备失败不开启命令窗口。
状态发布失败中止 gate;恢复后重新宣布同一 Tick 并重开完整 15 秒。
OPEN 阶段崩溃保留已持久化计划;重启后发送同一 tick、完整 state 和最新回执,并重开完整窗口。
锁定后崩溃不重新开放窗口,确定性重放已锁定计划。
服务离线世界和所有逻辑计时暂停。

现有世界切换

可消耗资源版本会替换现有世界的旧资源布局,但不会重置世界时钟、玩家、Core、Unit、 库存、重生状态或 Champion Beacon 状态。资源位置作为地图层迁移到新的区块配额与补充 协议。

规则 v0.5 保留 Worker 死亡后的 Cargo 资源堆,并增加随人口变化的 Core 容量。规则 v0.6 将容量改为 max(10, population × 5)。规则 v0.7 让 Ranger 射击穿过 Unit 和 Core,只有障碍物阻挡。规则 v0.8 加入射程 1-3 的 45° 斜线射击,并且只检查射线实际 经过的中间格障碍物。规则 v0.9 把战斗中被摧毁 Core 的容量内库存交给对它伤害最高、 且 Core 在同 Tick 战斗后仍存活的玩家。规则 v0.10 把 Core 的资源动作移到战斗后, 并加入 Unit 与 Core 的 HP 恢复。规则 v0.11 曾把维护费欠款从 Core 伤害改为超额 Unit 伤害。规则 v0.12 加入战斗后无条件、无冷却的 Core 自毁。规则 v0.13 加入无需目标的 Ranger 按格射击,以及按最低 HP、UUID 确定目标的规则。规则 v0.14 删除维护费,改用 精确的随人口变化的 Unit 价格。现有 v0.1 到 v0.13 世界可以在 OPENCOMMITTED 边界升级, 不会重置游戏状态。当前规则同时移除了复活冷却:Core 被摧毁后会在同一个 Tick 的后续 阶段立即尝试重生。如果旧服务停在 LOCKEDRESOLVING,必须先用旧规则完成该 Tick, 才能升级。