确定性逻辑为什么必须与表现层分离

联机游戏里有一类问题很难靠增加条件判断解决:相同输入在两台设备上执行,结果逐渐不同;回放能开始,却无法复现原对局;画面卡顿之后,逻辑也跟着改变。它们看起来分别属于网络、回放和渲染,根源却常常相同:权威逻辑和画面表现共享了不该共享的状态。
分离这两层,不是为了让目录更整齐,而是为了建立两种完全不同的运行规则。逻辑层追求同一输入得到同一结果,表现层追求画面连续、反馈及时。前者不能随意丢数据,后者必须有能力插值、降级甚至丢弃过期效果。
两种时间
逻辑世界按离散帧前进。每一帧接收已经量化的输入,按固定顺序运行系统,更新实体,在帧末统一处理创建和销毁,再产生这一帧的权威状态。它不应该读取渲染帧率,也不应该根据 deltaTime 临时改变移动距离。
表现层面对的是另一种时间。例如屏幕以每秒六十帧刷新,而对局协商出的逻辑频率为三十帧;设备偶尔掉帧时,一次渲染更新又可能跨过多个逻辑帧。若表现层直接推动逻辑对象,显示频率就会进入模拟结果。同一组输入在不同设备上运行,便可能得到不同位置、碰撞顺序或技能结算。
可靠的边界是:逻辑帧决定“现在是什么”,渲染帧决定“怎样把它显示出来”。后者可以读取前者,不能反向改写前者。
权威状态
确定性不是“不使用随机数”,而是所有影响结果的状态都必须可控制、可序列化、可比较。一套实际运行的逻辑内核,会把当前帧号、下一个实体编号、随机数生成器状态以及实体的位置、速度、生命值等字段写入稳定顺序的数据流,再计算状态哈希。
这个哈希不是安全签名,它是一把便宜而有效的尺子。两端在同一逻辑帧得到不同哈希,说明权威状态已经分叉。回放结束后的哈希与原对局不同,也说明输入、版本或状态演进存在不一致。
关键是只对权威状态计算哈希。粒子是否已经播放、镜头震动还剩多久、伤害数字是否消失,都不应该影响对局结果。把这些表现状态混入哈希,只会让设备性能差异制造假失步。
单向交接
逻辑层向表现层交付两类数据:状态快照和逻辑事件。
状态快照回答“实体在哪里、朝向哪里、当前是什么状态”。表现层在每个逻辑帧结束后采集快照,把最近两帧放入一个很小的环形缓冲。渲染时根据当前显示进度,在旧快照和新快照之间插值。逻辑位置仍然是离散的,玩家看到的运动却可以保持平滑。
逻辑事件回答“刚才发生了什么”,例如受伤、死亡、技能释放和弹丸命中。表现层消费这些事件,播放特效、声音和镜头反馈。事件只携带表现所需的事实,不把播放结果写回逻辑世界。
这条交接是单向的。表现层可以因为对象尚未加载而使用占位模型,可以因为设备性能不足而减少粒子,也可以在追帧期间暂时隐藏画面,但不能改变实体生命值、随机数状态或下一帧输入。
丢弃策略
“事件都是队列”还不够,两个队列需要不同的失败策略。
逻辑层产生的帧内事件会先进入暂存队列,帧末再转入表现缓冲。暂存队列容量不足时,静默覆盖旧事件会隐藏逻辑错误,因此更合理的做法是立即失败,让测试和监控尽早暴露容量假设。表现事件缓冲则不同:一次短暂卡顿后,补播数百个已经过期的命中特效没有价值,还可能造成第二次卡顿。它可以丢弃最旧事件,同时记录丢弃次数供诊断。
这种差异体现了边界的真正意义:逻辑数据要求完整、稳定和可复现;表现数据要求及时、可降级和不阻塞主流程。两者若共用同一套容错策略,要么逻辑会悄悄丢失事实,要么画面会被历史反馈拖垮。
插值不是预测
只保留最近两个逻辑快照,就能完成最基础的平滑显示。假设旧帧位置为零,新帧位置为十,渲染进度位于两帧中间,显示位置就是五。角度需要沿最短方向插值,避免从三百五十度转到十度时绕完整一圈。
这里没有改变逻辑结果,也没有猜测未来输入。插值只是让画面稍晚于最新逻辑帧,以换取连续运动。真正的本地预测则会提前显示尚未确认的结果,还需要回滚或校正机制。两者解决的问题不同,不应因为都让画面更顺滑而混在一个组件里。
当只收到一帧快照时,表现层直接使用该姿态;收到旧帧时拒绝倒退;同一帧重复到达时替换最新值。这些规则很小,却能避免重连、补帧或重复消息把画面拉回过去。
验证边界
架构图只能说明意图,测试才证明边界成立。至少需要四类检查。
第一,同一随机种子和输入脚本运行多次,每一帧的状态哈希都相同。第二,改变生命值或随机数状态后,哈希必须变化。第三,添加不会进入权威状态哈希的事件时,状态哈希不能变化。第四,快照缓冲要验证双帧插值、角度短弧、拒绝旧帧和同帧替换。
本文取证的实际项目当前运行了完整的核心与表现测试:核心测试 263 项全部通过,表现测试 56 项全部通过。其中包含一万逻辑帧双运行逐帧哈希一致,以及双快照位置和角度插值检查。这些数字不是性能结论,只说明本文描述的边界在当前代码中有对应实现和回归保护。
最终判断
确定性逻辑和表现层的分离,不应停留在两个命名空间。真正有效的边界必须同时满足四点:逻辑只依赖确定性输入;权威状态可以序列化和计算哈希;状态快照与事件只从逻辑流向表现;表现层能够独立插值、降级和丢弃过期反馈。
做到这些以后,帧同步、重连和回放仍然需要各自的协议,但它们终于建立在同一个可复现世界上。画面可以继续追求流畅,逻辑则保持准确。两层各自接受不同的约束,才是这套架构真正带来的价值。