三种理论模型
三种说法 · 九项对比 · 返回总条目
关于拉弓包为什么会出现,目前流传三种说法。它们不是三种解释角度,而是在关键问题上互相矛盾的三个主张——尤其是“拉弓包是否独立存在”。
C08 复合载荷说(主流)
拉弓包不是一个单独设计的数据包,而是 C08(开始拉弓)与 C07(松手)在同一个 tick 内先后到达、服务端按顺序逐个处理的结果。两个包各自独立执行,效果只有放在一起才成立,因此称为复合载荷。按这一说,只有 C2S 一侧成立。
它的强项在于能解释两件别的说法解释不了的事:为什么字段比同类包多(因为这一串里有好几个包的内容),以及为什么这次松手不射箭(重发的 C08 把服务端侧计时归零,releaseUsing() 算出的力度低于 0.1)。
它的弱项是不承认 S2C 一侧,解释不了老资料里反复提到的“首包”和 1 tick 窗口。
握手说
拉弓包不是一个注册的类,但是一个真实存在的独立流程(有首包、有窗口),需要客户端在同一个 tick 内与服务端完成一次固定顺序的握手才会出现。S32 给出序号,C0F 异步回带,再发 C08 与半个 C07,服务端接受后下发首包并打开 1 tick 窗口。
它流传较早,完整展开见握手序列。
它解释的是流程而不是载荷:首包与窗口从哪来,它答得比 C08 说清楚。为什么这一刻没有生成箭矢,它不涉及,这一层由 C08 说补上。
融合说
两者描述的是同一件事的不同层面:握手说是传输层(包怎么来、怎么发),C08 复合载荷说是语义层(包到了之后发生什么)。
它调和的正是表面上最冲突的一点:握手说要求 S32 的往返在同一个 tick 内完成,C08 说却要求把 S08 推迟 15 – 20 tick 再应用。融合说的解释是两者针对不同的东西——推迟的是应用拉回位置,而 C0F 回应的是 S32,不经过主线程队列,立即发出。
它避免了“独立流程 vs 先后处理”的二选一:从流程看它双向、带 Sequence,但落到服务端仍是几个包按顺序逐个处理,所以它是一个流程,不是协议分配的独立类型。代价是它要同时维护两层描述,任何一处改动都要在两层里对上。
九项对比
| 对比项 | C08 复合载荷说 | 握手说 | 融合说 |
|---|---|---|---|
| 接受度 | 最广 | 次之 | 较少 |
| 拉弓包是否独立存在 | 否,是 C08 与 C07 先后处理的结果 | 是独立流程(但不是独立的类) | 线路上是一个独立流程,内容是 C08 → C07 的先后处理,等价于一轮级联 |
| 方向 | 仅 C2S | S2C 与 C2S | S2C 与 C2S |
| 关键数据包 | C08、C07、C04、C03、S08 | S32、C0F、C08、C07、C06 | 全部 |
| “半个”指什么 | 只带动作字段、其余沿用前一个包的 C07 | 握手中的半个 C07,以及窗口内回发的半个拉弓包 | 同握手说 |
| 能否解释不射箭 | 能(服务端侧 getTicksUsingItem() 被重发的 C08 归零) | 不能 | 沿用 C08 说 |
如何处理 S08 | 推迟 15 – 20 tick 再应用 | 作为位置校正同 tick 收下 | 即时异步回应 S32,推迟应用位置 |
| 概率性的来源 | C08 与半个 C07 能否赶上同一个 tick | 1 tick 窗口内能否发出 | 两者叠加 |
怎么自己判断哪一种成立
三种说法里,能与源码对照的部分都能逐条查清;不能对照的只有一件事——服务端会不会在那一刻下发首包、打开窗口。所以检验的思路应该反过来:先把能查的查清,再看剩下那个不确定项站在哪一边。
- 查映射。搜
PullBow,确认没有对应的类,再逐个搜组成包,确认它们都在。这一步排除的是“拉弓包是一个注册类型”这种误读——三种说法都不这么主张。 - 查释放逻辑。读
BowItem.releaseUsing(),确认力度低于 0.1 不射箭;再确认重发C08会重置服务端计时。这两条成立,C08 说对“不射箭”的解释就成立。 - 查 Pong 语义。读
ServerboundPongPacket,确认它只带一个 id,且这个 id 来自S32。这一步确认握手说里序号往返是真的,但它本身不产生拉弓效果。 - 抓包。在客户端与本地服务端之间抓一次,看同一 tick 内是否真的出现你发出的那几个包、它们是否按顺序被处理,以及服务端是否回了首包。这是唯一能把三种说法分开的实验。
前三步不需要抓包,任何人都能做。第四步要看到的是:这一串是否落在同一 tick、顺序是否与你的发送顺序一致、服务端是否回了首包。抓一次就能把三件事同时对齐,方法见抓包。
三者各自解释什么
三种说法回答的是不同的问题,把它们放回各自的位置就不冲突:构成列出七项原版包,回答“内容从哪来”;C08 说回答“这一次为什么不生成箭矢”;握手说回答“首包与 1 tick 窗口从哪来”;融合说把前两者分别放在语义层与传输层。要复现一次完整的序列,走握手序列;要判断手上这一份抓包里发生了什么,走抓包。