← ALL ARTICLES
游戏网络11 MIN READ

客户端预测

全库目录221
本文目录14

本地玩家等待往返确认才移动,会直接感到延迟。客户端预测先执行输入,服务器确认后恢复权威状态并重放剩余命令。逻辑修正立即生效,显示偏移再平滑,不能只线性插值权威碰撞位置。

以输入会话C的一次门口移动和射击为例:服务器确认16号时角色位于8米,前方10米处有墙;客户端生成17号向前5米并开火,18号向后1米。客户端先预测到墙前再退到9米;服务器在Tick600消费17号并接纳射击,历史命中查询仍在后台等待。全文沿这两个输入追踪移动重放、动作回执和断线恢复,输入ACK与射击终态各自推进。

输入步与状态边界

每条输入保存输入会话ID、序号、固定模拟步号、持续一个步长的控制值和一次性动作位。采样帧率可以与模拟频率不同,边界层把控制值保持或重采样到固定步;开火边沿只分配给一个输入步。服务端按会话与序号消费,每个输入最多执行一次,丢包可由后续包冗余携带尚未确认输入补齐,缺口不能直接当作已经执行。

权威快照包含快照Tick、已连续消费输入号,以及该边界的完整玩家模拟状态。位置之外,还包括速度、移动模式、地面/平台身份、能力冷却、会影响下一步的随机状态和动作确认结果。快照是在一个模拟边界上采集的,ACK与这些字段不能从不同Tick拼装。客户端恢复它之后,未确认输入重新调用同一Simulate,因此上一步的速度和接触条件也成为下一步的真实输入。

对已确认输入之后存在的本地输入按本地生成顺序重放;旧会话快照不与新会话队列比较。服务端没有输入时采用的保持控制或中性输入规则也是模拟协议的一部分,并通过权威Tick表现出来。客户端不能用“尚未确认输入数乘步长”代替服务器Tick,更不能重复补执行服务端已推进过的空输入步。需要按服务器Tick重建外部平台状态时,保存快照锚点(ack,tickAck),本次待重放的第i条输入预测放到tickAck+i,逐步查询该Tick的外部条件。服务端实际在下一可执行Tick消费队列,若它因缺包先推进了中性输入,后续权威快照会给出新的锚点;客户端重新排列尚未确认输入的预测Tick,并重建相关平台依赖。这里的Tick映射是可被校正的预测,不是客户端为服务端指定任意执行时间的权限。

完整序号身份

线上的16位序号只是压缩字段。客户端和服务器在输入会话内保存不回绕的完整序号;经认证的包头携带完整参考序号或高位轮次,低16位相对该参考展开,并在小于32768的活动窗口内接纳。仅根据当前ACK猜高位会把上一整轮延迟到达的0号误认成新的65536号,因此包的轮次也属于身份校验。无法唯一展开时拒绝并重同步。例如完整序号65536在线上为0,事件身份仍记录65536,不能与同会话最早的0号共用去重键。完整序号、输入摘要和回执一起持久保存,服务器恢复后不能只从低16位重新计数。达到完整计数器上限前执行输入会话关闭屏障,再换新会话。

待交付输入与重发

客户端先把17的完整身份、控制值、动作位和摘要加入待确认队列,再交给网络发送器。发送器持续从这份队列选择未确认项,断线或发送队列拒绝只结束本次投递,不移除17。传输层确认只能说明包已接收,服务器权威ACK才证明输入已消费。17已经登记却尚未发送就掉线时,重连仍以C/17交付同一内容;发送成功但ACK丢失时也重发同一身份,由服务器返回原消费记录。

预测重放只重新执行模拟,不向发送器添加新的输入身份。网络重试由待确认队列驱动,因此一次校正不会生成另一发射击。ACK17到达后停止重发17;动作仍Pending时只查询原动作回执,不重新发送一个新的开火命令。

客户端进程内断线保留这份队列。需要跨进程继续未确认输入的产品,把输入会话、完整序号和命令摘要作为一个本地恢复单元保存,重启先向服务器核对该会话是否开放及消费位置,再续交剩余命令。本地队列丢失时按后文会话关闭屏障重同步,不能猜测17的控制值或把旧开火重新编号为新操作。权威快照恢复与待交付队列因此分别承担模拟起点和命令交付,任一方都不能替代另一方。

权威边界

客户端可以预测表现,不能决定伤害、掉落、库存和最终位置。服务端验证输入频率、移动能力、碰撞、资源和技能前提。完全拒绝所有偏差会误伤网络抖动玩家,完全信任客户端则给作弊留下入口,规则需要带容差且可审计。

校正循环

伪代码使用完成身份展开的完整确认序号,要求确认号单调,Simulate与服务端使用相同固定步长及玩法规则。外部状态缺失时保留已完成的预测前缀与全部未确认输入,暂停后续步;取得新锚点后从新权威状态重新计算,不能把暂停当作输入已执行的确认。

text
predicted = authoritativeAtAck
erase inputs through ack
for i, input after ack in local generation order:
    predictedTick = snapshotTick + i + 1
    external = QueryExternalState(predictedTick)
    if external is unavailable: stop prediction and request a new anchor
    predicted = Simulate(predicted,input,external)
visualOffset = previousRenderedPosition - predicted.position

重放中的碰撞与序号

权威状态必须与确认序号成对使用。本例服务器确认16号时角色位于8米,客户端还保存17号向前5米和18号向后1米的命令,前方10米处有墙。重放17号先在墙前停到10米,再重放18号得到9米。把未确认位移直接求和会得到12米,因此重放必须执行同一移动与碰撞规则。

把同一组输入换到序号边界时,完整65535之后是65536和65537,线上低16位分别为0和1,碰撞重放顺序仍不改变。验证包头轮次并展开完整身份后,同一受限活动窗口内的低位字段也可用模差比较;下列函数只负责这个局部比较:

python
def newer(a, b):
    return 0 < ((a - b) & 65535) < 32768

输入确认序号与权威快照Tick分别比较。相同输入确认号只表示没有新输入被消费,服务器仍可能因外力或平台运动产生较新的权威状态;应接受较新快照,再重放尚未确认输入。快照Tick过旧或确认号倒退的包才拒绝,完全重复的状态不再处理。两序号恰好相差32768时无法判断方向,协议必须保证不会积累到这个跨度,或重新同步会话。待重放队列按本地生成顺序保存,不能对回绕后的整数直接排序。

预测还需要对应时间的外部条件。角色站在移动平台上时,仅有自己的坐标不足以复现碰撞;保存或重建对应Tick的平台变换,才能让重放使用相同接触条件。具体做法是给平台也保留按Tick索引的状态,角色输入的模拟Tick查询相应平台快照;只有权威插值样本时,按同一连续运动段插值得到该Tick的碰撞变换。对未来尚未收到的平台步采用明确的限时预测,并标记为预测依赖,平台权威更新到达后重新校正受影响角色。缺少必要历史或超过预测窗口时停止这一段预测,请求新的角色与外部条件锚点,不能无限复用平台当前位置。

校正后的逻辑位置立即供下一 Tick 使用。显示层保存“旧显示位置减新预测位置”的偏移,并按固定半衰期衰减;指数形式 offset *= exp(-ln(2)*renderDt/halfLife) 使不同显示帧率具有一致衰减时间。传送等大幅不连续变化直接重置偏移。

输入缺口与拒绝结果

连续确认要求服务器区分“尚未收到”和“已经处理但拒绝”。把本例开火改为使用冷却中的技能时,服务器仍为17保存拒绝结果并推进已消费序号,后续18才能执行;重复17返回同一拒绝,不能等冷却结束后改成成功。结构损坏到无法确认身份的包在接入层丢弃,不把包内序号当成已消费输入。ACK之前的每条输入都有持久消费记录:移动步已执行,动作已接纳并登记Pending,或者已被拒绝。客户端据此移除输入重放命令;动作的Pending与最终结算另由回执表保持,ACK不表示所有异步动作已经结束。

服务器一直收不到17而持续收到18、19时,按有界等待策略请求补发17。等待超过协议期限则关闭该输入会话的新操作入口;关闭屏障先结清已接纳输入,Pending射击继续按原截止等待并记录终态,完成后再建立新业务会话锚点;不能直接把ACK跳到19却让客户端以为17已经执行。缺包期间服务器采用的中性输入推进世界Tick,但不凭空消费17。客户端按新的ACK/Tick锚点重放,显式拒绝动作的预测效果按事件ID取消,移动状态则以权威快照为准。

表现事件与预测恢复

输入消费确认与异步动作终态分别保存。开火输入17已扣弹并进入历史查询Pending时,ACK可以推进17,客户端移除它的移动重放命令;动作回执表仍保留17的事件ID和Pending状态,按权威回执更新,不能把输入出队当作命中确认。恢复快照携带未终结动作及终态回执水位,客户端按身份查询缺失回执,服务端重发已有结果。具体接纳、查询包和截止规则见 延迟补偿命中。这样重连不会因丢掉输入队列而丢掉已经扣弹但尚未结算的射击。

移动快照与动作回执使用独立的单调边界。每个动作身份保存服务端状态修订号,例如接纳Pending为1、最终命中或超时为2;终态不可回退,重发返回同一修订号。客户端在同一动作表中合并实时回执与恢复快照,只采用更高修订;相同修订的不同内容报告协议错误。604的终态先到、600的Pending快照后到时,保留终态;600的输入ACK先到但没有动作结果时,只移除移动命令,动作表继续等待。移动快照本身仍按Tick与ACK规则接纳,不能因一个动作已终结而丢掉较新移动状态。

恢复接口使用每个输入会话有序的动作回执流,服务端在接纳或终结动作的同一耐久Tick事务内分配流序号,并把动作修订与回执发件箱共同提交。客户端保存已经连续收到的流游标,乱序项暂存,缺口按区间补取;终态修订的流序号不能代替输入ACK。服务端在一个已提交边界返回当前Pending集合、动作修订以及覆盖流游标,再发送该游标之后的回执;客户端先合并基线,再补齐后续,重复项按身份及修订去重。流记录只有在客户端确认连续游标且超过恢复保留窗口后才可回收;游标太旧则返回新的完整动作基线。基线包含相关已终结动作或其终态墓碑,覆盖客户端仍待确认的身份;采用基线后,游标及以前的迟到流记录直接丢弃,避免已回收墓碑又被旧Pending重新创建。旧会话的待终结动作仍按原身份查询,不能换新会话ID再生成一次射击。

恢复基线与游标提交

动作基线按一个完整响应接纳,请求携带当时的连续游标,响应固定业务会话、基线ID、覆盖游标及相关动作修订;覆盖游标小于请求游标的响应作为过旧基线拒绝。客户端先校验并暂存全份基线,再在同一个本地状态提交中合并动作表、把连续游标推进到当前已消费位置与基线覆盖位置的较大者、删除已覆盖的暂存流项,最后向服务器确认连续游标。分块响应未收齐时继续保留原动作表和游标,不用半份基线删除等待项。

同一恢复过程中到达的实时流项先暂存,安装基线后再应用覆盖游标之后的项;此前已经采用的更高动作修订继续保留。这样基线传输期间到达的604终态不会被600的Pending覆盖。若客户端需要跨进程保存动作状态,动作表与游标也共同持久提交;提交前退出重取同一基线,提交后退出从保存游标续取。只保存游标而未保存动作表,会使第二次恢复跳过尚未安装的终态。

本地声音和粒子属于显示状态。进程内按事件ID去重;跨进程恢复时按动作状态重建仍有意义的持续表现,历史一次性枪口效果作为已发生事实处理。业务回执的耐久交付保证不会把历史声音变成可原子提交的外部效果,命中伤害仍只取服务器已提交状态。

例如600的ACK17丢失,但604命中回执先到:客户端把C/17标为终态并播放一次命中,仍保存17号移动重放输入。随后600的权威快照到达,移除17并重放18;它携带的Pending修订1不能覆盖已经收到的终态修订2。若此时重连,恢复接口沿流游标补齐缺项,枪口与命中事件都保留原身份。服务器如何固定604采用的异步结果、记录606超时并重放已提交决策,见射击页的“权威决策与Tick日志”;客户端只消费已提交回执,不根据查询线程的完成通知自行改伤害。

位置可以反复重算,声音和粒子不能每次校正都创建一次。为可预测事件保存(输入会话,完整输入序号,事件槽)作为身份;首次模拟输入17的开火槽0时播放枪口效果,重放同一输入再次产生该ID,只更新其状态。服务器确认接纳时,只把枪口这类开火表现标为已接纳;命中提示等依赖结算的表现仍等待最终回执。接纳前拒绝动作时取消可撤销开火表现;已接纳射击随后未命中或查询超时,保留开火事实,只结束等待中的命中表现。弹药按接纳回执确认,伤害按结算回执确认。

重连与历史上限

输入队列还必须有上限。断线后积累超过可重放历史窗口时,旧外部碰撞条件已不存在,客户端停止按旧输入追赶,申请完整权威状态并建立新的预测纪元。预测纪元切换清除本地重放与可重算的显示历史,已消费事件身份、动作修订和回执游标继续保留到对应业务会话关闭及回执保留期结束;输入业务会话仍按 世界分区与AOI 的关闭屏障完成已入队操作后再切换,不能通过清队列撤销已提交开火或消耗。继续重放任意长队列会把重连耗时和错误碰撞一起放大。此时显示层可淡入或直接跳转,但逻辑状态必须从新锚点开始。

验证方法

沿C/17与C/18分别列移动队列、预测位置、动作修订和回执流游标:ACK16时重放得到9米;ACK17时只重放18;终态先到时移动仍可重放而命中只显示一次。再调换Pending快照与终态的到达顺序、制造回执流缺口、在等待外部平台状态时重连,检查移动锚点恢复和动作补取能否分别完成。

记录校正距离与重放Tick,模拟延迟、重复确认和序号回绕。碰撞、平台、传送与技能打断分别测试,事件重放去重并不等于复制旧显示状态。

本机CPU验证:确认输入重放由 completion_reference.py 检查,运行 python 知识库/examples/mechanisms/completion_reference.py。这些检查验证模型,不包含GPU执行或实际网络故障注入。

实现与证据

2026-10-22,以本机 Python 3.11 运行 python 知识库/examples/revision_16_21/test_reference.py,17项检查通过。参考实现与测试输入可复现本页注明的机制。 本页验证序号回绕及包含墙面钳制的输入重放;completion_reference.py 只验证加法分段关系。补充的 快照接纳模型通过4项检查,覆盖同ACK新Tick、过旧/重复Tick、ACK回绕和ACK倒退;运行 python 知识库/examples/revision_16_21/test_review_reference.py 可复现。真实移动平台与碰撞引擎的重放尚未运行。

相关主题

参考资料