半个包

参数减半,不是字节减半 · 返回总条目

“半个”不是把一个包截掉一半发出去,而是只带一半有效参数、另一半由服务端沿用上一个包。拉弓包里有两种半个:握手中的半个 C07,以及窗口内回发时用 C04 代替 C06 省掉朝向。

协议里每一帧都带长度前缀、按帧解码,少一个字节服务端就会把后面的流读错。所以“半个”只能是参数层面的省略:发送方知道接收方手里已经有这份状态,就不再重复写一遍。

两种半个

半个 C07半个拉弓包
发生在首包之前,握手的第 4 步首包之后,窗口内的回发
省掉的是什么坐标与朝向填占位值,服务端不读;只有 action 有效Yaw 与 Pitch 整段不发,用 C04 代替 C06
沿用的是此前正在使用的物品与玩家位置上一个移动包报上去的朝向
省掉的字节不改帧结构,只改字段取值8 字节(两个 float)

半个 C07

C07 是 ServerboundPlayerActionPacket,动作枚举里有一项 RELEASE_USE_ITEM。这一项只用得上动作字段:方块坐标固定填 BlockPos.ZERO,朝向固定填 DOWN,服务端处理释放时根本不看这两个值。

所以这个包照常带长度前缀、照常完整发出——只是坐标与朝向是占位值。字节数没变,变的只是有效信息的比例。“半个”在协议语境里一直指这个意思。

半个拉弓包

回发时的移动部分有两种写法,两者都是合法的独立包,只是携带的字段不同:

写法移动部分线上字段字节数没带的部分
一个 C06(PosRot) X, Y, Z, Yaw, Pitch, On Ground 8 × 3 + 4 × 2 + 1 = 33 无,全部由客户端给出
半个 C04(Pos) X, Y, Z, On Ground 8 × 3 + 1 = 25 Yaw、Pitch 沿用上一个移动包的值

差的 8 字节正是两个 float。Charge Ticks 与 Sequence 不在移动包里:前者是客户端本地计时,后者由 C0F 带回,两种写法里都一样。

为什么可以省

窗口只有 1 tick。在这一 tick 里,客户端手里的朝向通常和上一次移动包报上去的完全一样——重复发一遍不增加信息,只增加占用,也增加对齐失败的概率。用 C04 省掉朝向,服务端沿用上一个包的值,结果等价。

前提是服务端那份状态必须是新的。Sequence 保证的正是这件事:配对号对得上,说明窗口就是刚开的那个,服务端手里的朝向也刚更新过。配对号对不上,整个包被丢弃,省掉的朝向也就无从谈起。

为什么容易混

两处都叫“半个”,但一处省的是字段取值(C07 填占位值),另一处省的是字段本身(C06 换成 C04)。前者不改变帧长度,后者少 8 字节。抓包时把两者当成一回事,就会在字节数上对不上。

在抓包里看到这是哪一种
C07 后面跟着全零坐标与 DOWN半个 C07,握手第 4 步
C04 只有 25 字节移动字段半个拉弓包,窗口内回发
C06 带满 33 字节完整的回发写法

校验顺序与丢弃规则见Serverbound 一侧;在序列里的位置见握手序列。