确定性与数值一致性
Category
server / sync 子树。节点定位:确定性与数值一致性是一等公民——分叉来源、浮点代价、随机如资源、顺序纪律、校验是日常设施。
Definition
很多团队把同步问题理解成“网络问题”,直到他们发现包都到了、顺序也没乱,客户端和服务器却还是越跑越不一样。真正的原因往往不在网络,而在确定性和数值一致性没有被当成一等公民处理。只要系统依赖帧同步、锁步、输入回放或跨端共享部分逻辑,这个问题就会迟早找上门。
Problem
包到齐、顺序没乱,两台机器还是算出两个结果——同步问题不在网络在确定性。依赖帧同步、锁步、回放、跨端共享时,确定性迟早找上门;校验工具当日常设施才能在分叉出现时第一时间截住。
什么叫确定性
在这个语境里,确定性可以粗暴地理解成一句话:相同初始状态、相同输入序列、相同推进顺序,应该得到相同结果。
只要这条不成立,下面这些能力都会被动摇:
- 帧同步的一致性。
- 输入回放的可复现性。
- 断线后按输入追赶的正确性。
- 争议对局的事后复盘。
- 服务端与客户端共享逻辑时的可信度。
因此,确定性不是数学洁癖,而是同步体系能不能落地的工程前提。
最常见的分叉来源是什么
真实项目里,分叉往往来自很小的细节:
- 浮点误差在不同 CPU、平台、编译器上的差异。
- 集合遍历顺序不稳定。
- 随机数源没统一,或不同模块共用了同一随机流。
- 多线程更新顺序不固定。
- 物理引擎或碰撞库在不同平台表现不同。
- 脚本虚拟机和宿主交互顺序不稳定。
- 时间 API、系统时钟、帧耗时直接参与了逻辑判断。
短局里这些问题可能不明显,长局、多对象和高频技能交互里就会快速累计。
哪些系统最容易把确定性搞坏
比较容易出问题的通常不是纯数值公式,而是以下几类系统:
- 位移、碰撞、寻路。
- 技能命中判定与目标选择。
- Buff 周期 Tick 与触发链。
- 投射物推进与命中。
- 依赖“最近目标”“第一个命中的单位”这类顺序语义的逻辑。
- 使用脚本热更或跨端共享逻辑的地方。
这些系统的共同点是:顺序、边界和数值都很敏感。
浮点不是原罪,但必须知道代价
很多工程争论最后都会落到“到底要不要全改定点数”。更现实一点看:
- 浮点更方便,开发效率更高。
- 浮点跨平台完全一致很难保证。
- 不是所有系统都值得为完全一致性付出极高成本。
因此更常见的实践是分层:
- 核心裁决层尽量收紧数值行为。
- 表现层允许一定误差。
- 真正依赖强确定性的局部系统再考虑更强约束,例如定点或受控数学库。
关键不是宗教式地否定浮点,而是别在依赖确定性的架构里又假装浮点差异不存在。
随机数必须像资源一样被管理
随机数是最典型的隐性分叉源。比较稳妥的做法通常包括:
- 为战斗实例分配明确的随机种子。
- 不同子系统用不同随机流,不要共用一个全局 RNG。
- 同一事件的随机消费顺序固定。
- 回放和重连时能够恢复 RNG 状态。
如果一个 Buff 多触发了一次、一个投射物多随机了一次角度,后面整局都可能开始偏。
顺序比很多人想的更重要
很多逻辑看起来和数值没关系,实际上高度依赖顺序。例如:
- 先更新 Buff 再更新移动,还是反过来。
- 目标列表按创建顺序、距离顺序还是 ID 顺序遍历。
- 多个命中目标时,伤害吸血和击杀回蓝谁先结算。
- 同时到期的两个控制效果哪个先移除。
只要顺序不稳定,哪怕数学公式完全一样,结果也会分叉。稳定顺序往往比“更高级的数据结构”更重要。
多线程和异步消息最容易偷偷破坏一致性
只要逻辑依赖执行顺序,多线程就会天然带来风险。常见问题包括:
- 不同线程回写共享状态,顺序不固定。
- 异步消息到达逻辑线程的时点不稳定。
- 某些平台调度差异导致并发竞争结果不同。
因此,战斗裁决层常见的保守做法是:
- 核心逻辑仍按单线程或严格顺序推进。
- 异步结果进入逻辑层时统一封装成下一帧消费。
- 多线程更多用于可并行但不影响裁决顺序的外围工作。
这不是拒绝多线程,而是承认同步体系对顺序非常敏感。
校验工具必须是日常设施
确定性问题最怕“只有出事故才排查”。比较稳妥的团队通常会准备:
- 定期状态哈希。
- 关键对象快照对比。
- 双端或多端自动回放比对。
- 最早分叉帧定位工具。
- 关键系统随机流和事件序列日志。
没有这些设施,确定性问题一旦进入线上,就会从工程问题退化成玄学。
不是所有系统都要追求完全确定
一个更现实的原则是分层确定性:
- 必须强确定:核心裁决、技能命中、关键资源变更和回放证据。
- 尽量一致:角色移动、投射物、Buff 触发和重要时序状态。
- 可以放宽:纯表现特效、镜头微调、局部插值和音效。
这种分层不是妥协,而是避免把整个项目都拖进高昂的确定性成本。
常见误区
- 在架构上依赖确定性,在工程上却继续使用不受控的浮点、随机和顺序。
- 没有日常校验,只在玩家发现不同步时才开始排查。
- 想让所有系统都达到同一强度的确定性,最后成本失控。
- 把表现层误差和裁决层误差混为一谈,导致该严的地方不严,该松的地方也不敢松。
确定性与数值一致性真正要处理的,不是“数学正不正确”,而是同步体系赖以成立的那组工程前提能不能被持续守住。
实战案例:一次「同一份回放,两台机器算出两个结果」的确定性分叉排查
案例为教学示例(非摘自真实项目),数字经过取整简化。它把本页「什么叫确定性」「最常见的分叉来源是什么」「顺序比很多人想的更重要」「随机数必须像资源一样被管理」「校验工具必须是日常设施」用到深水区:回放不一致不是网络问题,而是确定性前提在工程上没有被守住——这是帧同步与输入回放体系的排查,也是「确定性是同步体系的工程前提,不是数学洁癖」的完整展开。
背景与现象:一款帧同步 + 输入回放的竞技手游,日对局 75 万场,用回放做争议复盘与裁决。测试机与线上机回放同一份输入序列,结果却对不上;玩家投诉「回放和当局打的不是一局」。回放比对抽 200 局/日,13 局不一致——不一致率 6.5%,而回放走本地文件、输入序列逐帧一致,网络因素一开始就被排除。
第一步:现象与口径——先把「对不上」变成可统计的分叉帧
不一致口径:回放比对中,两台机器从同一初始状态、同一输入序列推进,状态哈希首次不同的局。200 局抽测 13 局不一致,不一致率 6.5%。
分叉定位:逐帧状态哈希比对,不一致局的最早分叉帧中位数是第 47 帧——分叉不是随机散布,而是集中在开局两秒内的技能交互段。
工单口径:客服「回放和当局对不上」的日 45 单,占日对局的 0.006%——比例极低,但每一单都是仲裁风险。
第二步:分层归因——按本页分叉来源逐项排查
回放不一致 13 局 归属
集合遍历顺序不稳定(目标选择 map 遍历) 62% → 8 局
随机数流共用(暴击与特效共用同一随机流) 23% → 3 局
浮点差异(不同 CPU 三角函数末位) 15% → 2 局- 输入层:两台机器输入序列逐帧一致、初始状态一致 → 排除输入与初始状态。
- 顺序层:62% 指向目标选择——技能命中判定遍历候选目标 map 取「最近目标」,Go 的 map 遍历顺序随机,同一输入下目标顺序不同,伤害结算顺序随之不同。
- 随机层:23% 指向随机流共用——技能暴击判定与特效模块共用一个全局 RNG,暴击多消费一次随机数,后续整局的随机序列全部偏移。
- 数值层:15% 指向浮点——不同 CPU 的三角函数末位差异,只在投射物边界累积,是长尾。
第三步:根因——顺序不稳定是主根因,随机流共用是次根因
主根因:目标选择依赖 map 遍历顺序。本页说「集合遍历顺序不稳定」是分叉来源之一,这里正是活例:62% 的不一致局都能追到同一段代码——遍历候选目标 map 取最近目标,遍历顺序每局不同,命中判定与结算顺序随之分叉。数学公式完全一样,顺序不一样,结果就不一样。
次根因:随机流共用。本页说「随机数源没统一,或不同模块共用了同一随机流」——暴击判定与特效模块共用一个全局 RNG,暴击多抽一次,后面整局都偏。
长尾:浮点差异。不同 CPU 的三角函数末位差异,15% 的不一致局由它贡献,单独修不划算,靠分层确定性把它归到核心裁决层收紧。
第四步:处置与回填——三处改动,顺序与随机都收口
- 目标选择改稳定排序:候选目标按(距离, ID)排序后再取最近,map 遍历改有序结构,结算顺序写进代码评审检查项。
- 随机流拆分:战斗实例分配独立随机种子,暴击与特效各用独立随机流,随机消费顺序固定并登记进架构规范。
- 状态哈希日常校验:每局结束算状态哈希,双端自动回放比对 + 最早分叉帧定位工具上线,从事后排查变成日常设施。
复核账:回放不一致 13 局 → 0 局(200 局全一致);仲裁工单日 45 单 → 5 单;同类问题排查耗时 3 天 → 状态哈希自动报警后 0 天发现(分叉在第 47 帧被自动定位)。
回填清单:
| 回填项 | 动作 | 回填处 |
|---|---|---|
| 稳定顺序规范 | 目标选择与结算顺序按(距离, ID)等稳定键排序,禁止依赖 map/哈希遍历顺序 | 顺序比很多人想的更重要 |
| 随机流登记 | 战斗实例独立种子、子系统独立随机流,消费顺序固定并登记 | 随机数必须像资源一样被管理 |
| 状态哈希日常校验 | 每局状态哈希 + 双端自动回放比对 + 最早分叉帧定位,作为日常设施 | 校验工具必须是日常设施 |
| 分层确定性 | 核心裁决强确定、表现层放宽,评审按三层核对 | 不是所有系统都要追求完全确定 |
案例的三个教训
- 确定性是工程前提,不是数学洁癖。回放、裁决、断线追赶都建立在「相同初始状态、相同输入序列、相同推进顺序,得到相同结果」上;这条不成立,同步体系就是沙上城堡。
- 顺序稳定比数据结构高级更重要。map 遍历顺序随机这种「看起来没问题」的细节,是确定性分叉的头号来源;稳定键排序是底线,不是优化。
- 校验必须是日常设施。没有状态哈希和自动回放比对,确定性问题线上排查就是玄学;有设施,分叉在第 47 帧就能被自动定位,而不是靠玩家投诉才发现。
Used By
确定性设计评审;回放分叉排查;随机数与时序纪律制定。
Related
关系链:定义 → 分叉来源 → 易坏系统 → 浮点代价 → 随机管理 → 顺序 → 多线程破坏 → 校验日常化。上游:sync/03(帧同步的确定性前提)。下游:sync/07(回放证据链)、runtime/01(执行模型与顺序)。
Reference
本页为工程经验归纳;页内实战案例为教学示例(数字取整简化)。