预测、补偿与纠正
Category
server / sync 子树。节点定位:在「等服务端迟钝」与「信客户端失控」之间搭桥——预测、补偿、纠正的边界:哪些状态能抢时间,纠正不只有瞬移回去。
Definition
如果完全等服务端结果再表现,实时游戏会显得迟钝;如果完全相信客户端,本地体验是顺了,裁决和反作弊又会崩。预测、补偿与纠正,就是在这两端之间搭桥的一组工程机制。它的难点不在概念,而在边界:哪些状态能抢时间,哪些状态必须等权威,服务端回来以后又如何把本地前馈重新收拢。
Problem
难点不在概念在边界:哪些状态能抢时间、哪些必须等权威、服务端回来后本地前馈怎么收拢。边界不清,实时手感与裁决反作弊会同时塌——帧同步和状态同步里的预测还不是一回事。
为什么必须抢时间
人在操作时,对延迟的感知并不会等服务端给你面子。只要角色位移、射击、释放技能、换弹、近战命中和镜头反馈这些东西都要等一轮网络往返,手感一定会塌。
所以同步体系必须同时回答三件事:
- 客户端先演什么。
- 服务端怎么算真结果。
- 真结果回来后怎么纠偏。
这三件事缺一不可。
预测、补偿、纠正分别在做什么
- 预测:客户端根据本地输入和已知状态,先推进一部分未来表现或逻辑。
- 补偿:系统通过输入窗口、插值、服务端回溯或其他机制,减小网络延迟对体验和判定的影响。
- 纠正:服务端权威结果回来后,把客户端和服务端的状态偏差重新收拢。
很多讨论把三者混成一句“客户端预测”,这是不够的。预测解决的是先反馈,补偿解决的是延迟本身,纠正解决的是前两者必然带来的偏差。
不是所有状态都应该预测
比较稳妥的实践通常会把状态分层:
- 适合大胆预测:本地移动、朝向、起手动作、部分非关键动画反馈。
- 适合有限预测:技能起手、投射物出手、近战挥砍开始、局部命中特效预播。
- 更适合权威确认:资源扣除、死亡、击飞击退最终落点、关键控制、结算相关状态。
越靠近胜负裁决、越容易被作弊利用、越依赖复杂碰撞和多人交互的状态,越不适合完全本地决定。
常见的预测与补偿手法
本地输入前馈
玩家按下移动键,本地角色立即开始动,服务端稍后给出权威位置。最常见于位移和手感敏感的动作反馈。
动作和技能前摇预播
客户端在发送施法请求后,先播放起手动作和起手特效。服务端若判定失败,再做打断或回退。
远端对象插值与外推
别人的角色通常不是预测“他要干嘛”,而是对最近两个权威状态做插值,必要时做短时间外推。这不是本地操作预测,但同样是在抢时间。
服务端回溯判定
射击和某些命中判定里,服务端可能按客户端上报的射击时间回看历史位置,以补偿链路延迟。这属于补偿,不是客户端预测,但常和预测一起出现。
纠正不是只有“瞬移回去”
服务端结果回来以后,纠正有很多层级:
- 硬纠正:直接把客户端状态改成服务端状态,简单粗暴,但体验容易炸。
- 平滑纠正:用插值在短时间内收敛到权威状态,视觉更自然,但实现更复杂。
- 分类纠正:不同状态采用不同方式,例如位置平滑回收、死亡立即权威、生效中的技能按规则中断。
- 回滚重演:在更重的方案里,客户端回滚到过去某个权威点,再重放后续输入。
没有哪种方案总是更好,关键是别把所有偏差都当成一种偏差处理。
帧同步和状态同步里的预测并不一样
在服务端权威状态同步里,预测通常是为了弥补客户端到服务端这段 RTT,焦点是本地手感和服务端纠正。
在帧同步体系里,系统更常依赖领先帧、输入缓冲和统一帧号来吞掉一部分延迟。此时预测空间通常更小,但也不是完全没有。有些项目仍会对动作起手、镜头反馈和部分局部表现做前馈。
所以“做帧同步就不用预测”并不准确;更准确的说法是,帧同步把更多延迟处理放进了输入窗口和共享帧推进,预测边界通常比服务端状态同步更保守。
服务端补偿最容易引发什么争议
补偿如果设计不清楚,会直接变成玩家口中的“明明躲开了还中”。最典型的是:
- 服务端为了照顾开枪者体验,回溯受击者过去位置。
- 客户端本地已经看到自己走开了。
- 两边都觉得自己没错。
因此,补偿策略必须和玩法匹配。竞技性越强,越要提前定义清楚:
- 最多允许回溯多久。
- 哪类技能允许回溯判定,哪类不允许。
- 位移、闪现和无敌帧如何参与判定。
- 日志里能否复原当时的时序证据。
补偿不是“越多越好”,而是要让体验和可追责都说得通。
一套更现实的状态分层
比较稳妥的项目里,常见会把状态分成三层:
- 本地体验层:优先快,允许短时间偏差,例如动作起手、镜头和部分位移前馈。
- 权威裁决层:必须准,宁可慢一点也不能错,例如伤害、死亡和关键控制。
- 表现收敛层:负责把前两层之间的差异做平滑处理,例如位置插值、效果取消和补播命中。
一旦这三层没有拆开,系统就很容易把“表现先播”和“裁决先算”混成一件事。
预测链路必须配日志
预测相关问题最怕没有证据。至少应该记录:
- 客户端发起输入的本地时间和逻辑帧。
- 服务端收到输入的时间和处理帧。
- 客户端本地预测结果。
- 服务端最终权威结果。
- 纠正触发时机与纠正幅度。
否则玩家说“我明明已经滚过去了”,工程师只能靠录像肉眼猜。
常见误区
- 把预测范围无限扩张,最后连关键裁决都在本地先演一遍,服务端变成形式上的确认器。
- 服务端结果一回来就强行回写,不做平滑和分类,结果体验像系统在和玩家抢控制权。
- 只盯本地手感,不看补偿策略给公平性和仲裁带来的副作用。
- 没有时序日志,预测链路出了问题以后只能靠猜。
预测、补偿与纠正真正要解决的,不是“延迟怎么藏”,而是怎样在不丢掉裁决权和可追责性的前提下,把延迟从玩家手里偷走一部分。
实战案例:一次「明明躲开了还中」的回溯判定争议排查
案例为教学示例(非摘自真实项目),数字经过取整简化。它把本页「服务端补偿最容易引发什么争议」「纠正不是只有瞬移回去」「一套更现实的状态分层」「预测链路必须配日志」用到深水区:补偿窗口本身不是 bug,窗口与客户端可见状态不在同一口径才是——这是实时对战的服务端裁决排查,也是「补偿不是越多越好,要让体验和可追责都说得通」的完整展开。
背景与现象:一款 PVP 射击 + 位移闪现的手游,日活 20 万、日对局 60 万场(场均射击 12 次)。服务端命中判定按客户端上报的开火时间做回溯判定,回溯窗口 200 毫秒;位移技能带 120 毫秒无敌帧。上线首周各项都平,直到新增两个带位移的射手技能,客服「明明躲开了还中」的工单开始爬坡,稳定在日 1800 单,而同期命中率口径一点没变——这正是最难查的那种信号:业务指标全绿,玩家体感在崩。
第一步:现象与口径——先把「明明躲开了」变成可统计的工单标签
争议口径:客服工单中,判定为「客户端画面已表现为躲开、服务端仍记命中」的单量。日 1800 单,占全日命中的 0.1%——比例看着不高,但它同时是仲裁风险:这类单子一旦坐实,裁决权就成了玩家眼里的玄学。
命中率口径:服务端回溯命中数 / 射击请求数 = 25%(全日射击 720 万次、命中 180 万次)。先把两个口径并排摆出来,才能说明问题不在命中判定「准不准」,而在它「凭什么这么判」。
第二步:分层归因——按三层状态把争议拆开
按本页的状态分层逐层核对争议单归属:
争议工单 1800 单/日 归属
回溯口径(无敌帧未排除) 88% → 1584 单
硬纠正瞬移(观感争议) 9% → 162 单
其他(表现/状态不同步) 3% → 54 单- 本地体验层:无异常。起手动作、镜头和位移前播都在按设计走,玩家确实本地就躲开了——这坐实了争议单的口径没问题。
- 权威裁决层:88% 的争议集中在这里,全部指向同一条规则:回溯取历史位置时,无敌帧不在排除集合里。玩家在客户端按下闪现的同一时刻,服务端正在回看 200 毫秒前的受击方位置。
- 表现收敛层:9% 是硬纠正造成的观感争议——服务端结果一回来直接回写位置,玩家看到的是瞬移。这不是错判,但会把本来可接受的判定变成投诉。
时序对照(同一场射击,开火方 P99 RTT 80 毫秒):
客户端 t=0ms 按下射击,同时闪现进入无敌帧(120ms)
服务端 t=80ms 收到请求,回看 200ms → 取 t=-120ms 的受击方位置
判定 t=80ms 命中:t=-120ms 受击方在弹道上,无敌帧未进入排除集合第三步:根因——窗口比玩家能看见的躲闪提前量还长
根因只有一条,但它有两个咬合在一起的参数:回溯窗口 200 毫秒 > 无敌帧 120 毫秒 + P99 RTT 80 毫秒。也就是说,服务端为了照顾开枪者体验,把判定窗口开到了玩家根本来不及看见的那一段;而恰恰在这一段里,客户端已经用无敌帧作出了「我躲开了」的可见承诺。两边都没写错代码,是判定口径与客户端可见状态没有对齐。
配套的次生问题是无据可查:争议单要复盘时,团队既没有客户端上报的开火时间与逻辑帧,也没有服务端的处理帧与纠正幅度,仲裁准确率只有 61%,剩下 39% 只能靠录像肉眼猜,最后统计成一句「玩家感觉有点问题」。
第四步:处置与回填——三处改动,窗口按 P99 对齐
- 回溯窗口 200 毫秒收到 80 毫秒,与 P99 RTT 对齐:判定只回看链路已消耗掉的那段,不再回看玩家看不见的未来。
- 无敌帧纳入回溯的排除集合,位移、闪现、无敌帧参与判定的方式逐技能写进判定规则表,不再由回溯实现默认继承。
- 硬纠正改平滑纠正:位移平滑收敛、死亡与关键控制仍立即权威,分层照本页的三层来。
复核账:争议工单日 1800 单 → 日 180 单,争议率 0.1% → 0.01%;命中率 25% → 24%(窗口收窄后回溯命中略降,这部分下降是主动的);仲裁工单复核准确率 61% → 97%,依据是补齐五项时序日志后,争议单可逐条复原时序证据。
回填清单:
| 回填项 | 动作 | 回填处 |
|---|---|---|
| 状态三层拆分 | 技术评审模板把权威裁决层与表现收敛层分栏评审,杜绝「表现先播 + 裁决先算」混成一件事 | 一套更现实的状态分层 |
| 补偿口径与玩法匹配 | 逐技能判定规则表:回溯上限、是否允许回溯、位移/闪现/无敌帧如何参与判定 | 服务端补偿最容易引发什么争议 |
| 纠正层级规范 | 位移平滑收敛、死亡与关键控制立即权威,写成纠正规范而非各处临时判断 | 纠正不是只有「瞬移回去」 |
| 五项时序日志 | 战斗日志全量留存,争议单 100% 可复原时序:客户端发起输入的本地时间和逻辑帧、服务端收到输入的时间和处理帧、客户端本地预测结果、服务端最终权威结果、纠正触发时机与纠正幅度 | 预测链路必须配日志 |
案例的三个教训
- 回溯窗口不能超过玩家可见的躲闪提前量。窗口按 P99 RTT 定档,不按「手感更好」拍脑袋;本页说「最多允许回溯多久」要提前定义,定义时就该照链路指标定,而不是照愿望定。
- 权威判定必须与客户端可见状态同口径。客户端敢用无敌帧对玩家作出承诺,服务端就必须把它算进排除集合;承诺与判定不同口径,裁决权就会变成玄学。
- 争议率和命中率必须一起看。命中率 25% 纹丝不动时,公平性已经在塌——「手感和公平」的指标缺一个,监控就只剩半只眼睛。
Used By
预测补偿方案设计;状态分层评审;服务端补偿争议的裁决依据。
- Colyseus(commit
e620123):补偿与纠正的轻量实现——patch 节奏下用Rewind.record记录快照供客户端插值复现(packages/core/src/Room.ts:1416-1420),不可靠通道字段走数据报且排在可靠 patch 之后(Room.ts:1396-1403)
Related
关系链:抢时间 → 三件事分工 → 状态可预测边界 → 常见手法 → 纠正形态 → 帧/状态差异 → 状态分层。上游:sync/02。下游:client/01(客户端预测面)、networking 树。
Reference
本页为工程经验归纳;页内实战案例为教学示例(数字取整简化)。