ワンクリックで
grim-elytra-movement-simulation
Grim 中 Elytra 预测、烟花容错、offset 判定与 setback 回滚的结果逻辑。用于直接回答滑翔检测、fireworksBox、1.7 来源与拉回后高速原因。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Grim 中 Elytra 预测、烟花容错、offset 判定与 setback 回滚的结果逻辑。用于直接回答滑翔检测、fireworksBox、1.7 来源与拉回后高速原因。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Analyze server-side block interaction flow around ServerPlayerInteractionManager.interactBlock, BlockState.onUseWithItem/onUse, ItemStack.useOnBlock, and BlockItem.useOnBlock in this merged Yarn Minecraft tree. Use when tracing whether right-clicking a block can fall through into item use or block placement, when classifying PASS vs PASS_TO_DEFAULT_BLOCK_ACTION behavior, or when summarizing special interactable block families.
理解并维护 SlimefunHelper 中 `ElytraExtra.applyAxisLimit2` 的 Elytra 烟花容错盒、两帧 look 叠加、目标逼近与爬升策略选择。用于分析 `currentMotion = currentRotation * 1.7` 一类上推方案,判断斜向追点、竖直拉升、前后折返等 rotation 序列的收益与约束。
在 SlimefunHelper 中兼容多个 Baritone 发行形态,尤其是“全混淆”版本。用于区分无混淆、仅方法混淆、类与方法全混淆三种形态,并决定 MixinPlugin、目标类定位、成员定位、反射桥接和 hooks 访问层该如何拆分。
在 SlimefunHelper 中新增或改造模块配置屏幕时,统一复用 `WidgetUtils` 的配置屏幕组装链,而不是在模块里重复手写 `DynamicListWidget + RefKeyValueInputWidget`。用于把 `BaseModule` 的 `editableConfig`、显示条件、模块标题、主题色和自定义 widget 接到同一条打开链路上。
理解并维护 SlimefunHelper 中 `NBTType`、`NBTParsable`、`NBTRef`、`AttrKeyValue` 的自动配置链。用于分析或新增 NBT 配置类型时,追踪它如何在字符串、原始对象、NBT、Ref、可编辑 GUI 之间自动转换,并据此实现新的 `NBTParsable` / 组合型配置结构。
为 SlimefunHelper 新模块补全 BaseModule 基础注册骨架。用于创建或改造模块时,统一完成模块名、ModulePath、Flag 绑定、不同类别 Ref、监听器、常用监听器、热键和命令注册。
| name | grim-elytra-movement-simulation |
| description | Grim 中 Elytra 预测、烟花容错、offset 判定与 setback 回滚的结果逻辑。用于直接回答滑翔检测、fireworksBox、1.7 来源与拉回后高速原因。 |
| disable-model-invocation | true |
1.7 解释成“全局鞘翅速度上限”。fireworksBox 解释成“直接修改某个固定 movementThreshold 常量”。Grim 对 Elytra 的处理不是“看到滑翔就单独硬判”,而是把玩家放进 Elytra 预测分支,生成一组允许的运动结果,再把客户端实际位移和允许结果之间的最小距离变成 offset,最后再由 offset / advantage / setback 链决定是否 flag 或拉回。
PacketEntityActionMovementCheckRunnerMovementTickerPredictionEngineElytraPredictionEngine + UncertaintyHandler + PointThreeEstimatorCompensatedFireworksOffsetHandlerSetbackTeleportUtilSTART_FLYING_WITH_ELYTRA。PacketEntityAction 先做起飞合法性门槛:
player.isGliding 成立,后续移动包不会走普通地面预测,而是进入 Elytra 预测分支。MovementCheckRunner 接收位置更新,整理实际位移与当前状态。MovementTicker 在状态分支里识别 player.isGliding,交给 PredictionEngineElytra。OffsetHandler / setback 链。Elytra 分支的核心不是 WASD 推进,而是“当前速度 + 视角 + 重力”的连续演化。
它的主因果链是:
look vector。pitch 计算俯仰带来的垂直/前向耦合。0.99 / 0.98 / 0.99 阻力。直接结论:
PredictionEngineElytra 里输入被视为零输入。Grim 明确把两层速度拆开处理:
clientVelocity = 碰撞前速度,会被带入下一 tick。predictedVelocity = 碰撞后速度,只用于当前 tick 预测结果,不直接作为下一 tick 延续速度。这意味着 Elytra 的速度承接不是“每 tick 从头算一个全新速度”,而是:
clientVelocity 出发;所以只要玩家还处于合法滑翔链路里,Elytra 动量就是连续承接的,不是每 tick 清零重建。
START_FLYING_WITH_ELYTRA 这条包只负责切换到 gliding 状态,不负责把速度清零。
实际结果是:
PredictionEngineElytra 的公式继续演化。所以 Elytra 的起步更接近“把当前空中动量接管进滑翔模型”,不是“切状态后从 0 速重新开始”。
CompensatedFireworks 只做一件事:记录“当前最多可能还有多少枚烟花在作用”。
然后 UncertaintyHandler.tickFireworksBox() 在存在烟花作用时构造一个 fireworksBox:
look vector 和上一 tick 的 look vector。antiTickSkipping 缓冲。1.7。[-1.7, 1.7],形成一个三维 AABB。这说明 Grim 这里建模的不是“这一枚烟花精确给了你多少推力”,而是:
这也是为什么烟花存在时,玩家会表现出更强的动量可控性:
fireworksBox 如何影响移动阈值这里必须分成两层看,不能混成一个概念。
Grim 里有固定的基础移动阈值:
GrimPlayer.getMovementThreshold()
1.18.2 以下的 point-three 客户端:0.030.0002这层阈值的用途是:
0.03 / point-three 粒度问题。getOffsetHorizontal() / getVerticalOffset() 里的基础容错。另外还有处罚层阈值:
OffsetHandler.threshold 默认 0.001OffsetHandler.immediateSetbackThreshold 默认 0.1这两个阈值控制的是“offset 多大开始 flag / 立即 setback”。
结论:
fireworksBox 不直接修改 getMovementThreshold() 返回值。fireworksBox 也不直接改 OffsetHandler.threshold 或 immediateSetbackThreshold。fireworksBox 真正影响的是预测空间,也就是“实际移动会被拿去和多大一块允许区域比较”。
链路是这样的:
PredictionEngine.handleStartingVelocityUncertainty() 先构造基础不确定性盒
这里已经会把普通的垂直误差、流体、bubble、sneak hidden velocity、point-three、碰撞等因素加进去。
如果 fireworksBox != null
Grim 不会把它当成最终答案,而是先计算:
fireworksBox 相对原始起始速度 originalVec 的最小/最大差值min/max盒子被扩张后,Grim 用 cutBoxToVector()
从这块更大的允许空间里,裁出一个“最接近实际移动”的允许向量。
后续预测、碰撞和误差比较
都是在这个被扩大的候选空间基础上继续做。
最终结果不是“阈值常量变了”,而是:
OffsetHandler 的 flag / setback 阈值。可以把这两层理解成:
fireworksBox = 把允许落点区域画大尺子没变,但因为允许区域变大了,实际点到允许区域边界的距离更短,所以“看起来像阈值变宽了”。
fireworksBox 不是一个“给全部方向统一 +N 容错”的球形半径,而是一个按朝向生成的三维盒。
这意味着它的放宽有三个特征:
X/Y/Z 的放宽量可以不同。所以它影响的不是一个简单的标量“速度阈值”,而是一个“朝向相关、三轴不对称、跨两帧的允许移动空间”。
fireworksBox 不是单独生效,它会和下面这些层一起叠加:
PointThreeEstimator 的 0.03 / skip tick 处理controlsVerticalMovement() 相关的垂直可控性判断cutBoxToVector() 把目标向量压回允许盒reduceOffset() 对最终偏移再做一次保守削减所以更准确的说法不是:
而是:
fireworksBox 扩大了预测可接受的起始速度/位移空间,进而缩小了最终 offset,让同样的实际移动更不容易跨过后置的 flag/setback 阈值”。当前代码会读取 getMaxFireworksAppliedPossible(),但 tickFireworksBox() 这段实现里,盒体尺寸本身并没有继续按烟花数量线性放大。
当前效果更接近:
这也是为什么这里更像“是否存在烟花推进的不确定性容错”,而不是“精确累计每枚烟花的推进量”。
1.7 的真正含义1.7 出现在 UncertaintyHandler.tickFireworksBox(),不在 Elytra 本体物理公式里。
它的语义是:
fireworksBox 的轴向边界;更准确地说:
1.7;[-1.7, 1.7];所以 1.7 限制的是:
Elytra 分支生成候选结果后,Grim 会把“实际位移”和“最佳允许结果”的差距当成 offset 基础量,再进入统一处罚链。
v1 / v2 / v3 / box 写成完整公式先统一变量:
v1 = 当前 tick 开始时,Grim 手里延续下来的客户端动量基线v2 = 玩家这个 tick 实际发送的移动量,也就是实际位移v3 = 对 v1 套完 Elytra 本体递推后得到的主模拟结果box = 烟花容错盒 fireworksBox注意两点:
v3 不是最终比较值,它只是 Elytra 本体递推产物。v2 比较的是“裁进允许空间、再过碰撞修正之后”的最终候选结果。设当前视角单位向量为 L = (lx, ly, lz),其水平长度为:
[ h = \sqrt{lx^2 + lz^2} ]
设 v1 的水平长度为:
[ s = \sqrt{v1_x^2 + v1_z^2} ]
设俯仰角为 pitch,重力项为 g',其中:
g' = gravityg' 会被压到更小值再定义:
[ c = \cos^2(pitch) \cdot \min(1, |L| / 0.4) ]
那么 Elytra 本体递推可以写成下面 4 段。
第一段,先显式加入重力:
[ u_1 = v1 + (0,\ g'(-1 + 0.75c),\ 0) ]
第二段,如果正在下落,就把一部分下落量转成沿朝向的推进:
[ \text{若 } u_{1y} < 0 \text{ 且 } h > 0, \quad d_f = -0.1 \cdot u_{1y} \cdot c ]
[ u_2 = u_1 + \left(\frac{lx}{h}d_f,\ d_f,\ \frac{lz}{h}d_f\right) ]
否则 u2 = u1。
第三段,如果抬头角度允许俯冲推进,就再加一次朝向耦合:
[ \text{若 } pitch < 0 \text{ 且 } h > 0, \quad d_p = s \cdot (-\sin(pitch)) \cdot 0.04 ]
[ u_3 = u_2 + \left(-\frac{lx}{h}d_p,\ 3.2d_p,\ -\frac{lz}{h}d_p\right) ]
否则 u3 = u2。
第四段,把水平速度往当前朝向对齐:
[ \text{若 } h > 0, \quad u_4 = u_3 + \left(\left(\frac{lx}{h}s - u_{3x}\right)0.1,\ 0,\ \left(\frac{lz}{h}s - u_{3z}\right)0.1\right) ]
否则 u4 = u3。
最后乘 Elytra 阻力:
[ v3 = (0.99u_{4x},\ 0.98u_{4y},\ 0.99u_{4z}) ]
这一步已经说明:
v3 不是无重力模型;box 的计算设当前 tick 视角为 L_now,上一 tick 视角为 L_prev。
设:
a = 0a = 0.05对每个轴 i ∈ {x, y, z},烟花盒边界是:
[ box_{min,i} = \max\left(-1.7,\ 1.7\bigl(\min(-a, L_{now,i}) + \min(-a, L_{prev,i})\bigr)\right) ]
[ box_{max,i} = \min\left(1.7,\ 1.7\bigl(\max(a, L_{now,i}) + \max(a, L_{prev,i})\bigr)\right) ]
所以 box 本质是一个三维 AABB,不是球,不是欧氏半径阈值。
box 怎么并到 v3Grim 不是直接拿 box 和 v2 比较,而是先围绕 v3 造一个基础允许盒,再用 box 去扩张这块允许盒。
先把普通误差层记成:
Δ- = 基础负向误差Δ+ = 基础正向误差这里面已经包含 point-three、流体、bubble、活塞、碰撞、潜行隐藏速度等普通不确定性。
于是围绕 v3 的基础允许盒是:
[ U_0 = [v3 + \Delta^-,\ v3 + \Delta^+] ]
然后烟花盒不是直接加在 v3 上,而是先相对 v1 取差值。
对每个轴 i:
[ e^-i = \min(0,\ box{min,i} - v1_i) ]
[ e^+i = \max(0,\ box{max,i} - v1_i) ]
于是扩张后的允许盒是:
[ U = [U_{0,min} + e^-,\ U_{0,max} + e^+] ]
这就是为什么更准确的理解不是“把阈值直接改大”,而是:
v3;v3 生成允许盒;box 把允许盒按轴扩张。v2 比较的不是 v3,而是裁剪后的候选结果Grim 接下来会把 v2 按轴裁回允许盒 U 内,得到最接近 v2 的合法候选:
[ c = clamp_{box}(v2, U) ]
这里的 clamp_box 就是三轴分别裁剪,不是球形距离。
然后这份候选还要经过碰撞修正,得到最终可比较结果:
[ p = collide(c) ]
所以真正和 v2 比较的是 p,不是裸 v3,也不是裸 box。
实际运行时 Grim 不只试一条链,而是会试很多候选分支。
对每一条候选分支 k,都会得到:
[ p_k = collide(clamp_{box}(v2, U_k)) ]
然后按平方距离选最接近 v2 的那一条:
[ k^* = \arg\min_k |p_k - v2|^2 ]
最终留下:
[ p^* = p_{k^*} ]
这就是本 tick 被 Grim 采纳的“最佳解释”。
offset 怎么生成进入处罚链之前,先把最终距离变成 offset:
[ offset_{raw} = |p^* - v2| ]
然后再过一次保守削减层:
[ offset = reduceOffset(offset_{raw}) ]
可以把 reduceOffset 理解成:
[ offset = \max(0,\ offset_{raw} - \lambda_{boat} - \lambda_{glitch} - \lambda_{stuck} - \lambda_{bounce} - \lambda_{boost}) ]
其中这些 \lambda 只在对应异常场景成立时才扣减,否则就是 0。
所以最终传给 PredictionComplete.offset 的,不是平方距离,而是:
reduceOffsetoffset进入 OffsetHandler 后,判定链可以写成:
如果:
[ offset \ge threshold \quad \text{或} \quad offset \ge immediateSetbackThreshold ]
则:
[ advantage := advantage + offset ]
并进入 flag 链。
之后如果满足:
[ (advantage \ge maxAdvantage \ \text{或} \ offset \ge immediateSetbackThreshold) ]
并且:
[ violations \ge setbackViolationThreshold ]
并且玩家没有豁免权限,那么触发 setback。
如果本 tick 没过阈值,则:
[ advantage := advantage \cdot setbackDecayMultiplier ]
所以把整条链压成一句话,就是:
[ v1 \xrightarrow{Elytra递推} v3 \xrightarrow{box扩张允许空间} U \xrightarrow{按轴裁剪} c \xrightarrow{碰撞修正} p^* \xrightarrow{与v2比较} offset \xrightarrow{OffsetHandler} flag/setback ]
v2当前 tick 结束后,Grim 会同时保留两层结果:
用式子写就是:
[ v1_{next} = c^* ]
而不是:
[ v1_{next} = v2 ]
也不是:
[ v1_{next} = p^* ]
所以“这一 tick 没被立刻拉回”不等于“下一 tick 会直接继承玩家真实发送的 v2”。
处罚链的主逻辑:
OffsetHandler 读取 offset。threshold 或 immediateSetbackThreshold 时,开始累计 advantageGained。maxAdvantage,或者单次 offset 超过 immediateSetbackThreshold,并且 violation 次数达到要求时,会触发 executeViolationSetback()。setbackDecayMultiplier 衰减。结论:
OffsetHandler 负责“解释不了的程度是否足够处罚”。根因不在 OffsetHandler,而在 SetbackTeleportUtil 的安全点速度重建链。
当前实现的因果链是:
lastKnownGoodPosition 会保存一个安全位置,以及这个安全点对应的 vector。executeViolationSetback() 后,blockMovementsUntilResync() 会从这个安全点拿回 clientVel。simulateNextTickPosition=true,Grim 会先做一次碰撞,再调用 simulateFriction(clientVel)。simulateFriction() 不走普通地面摩擦,而是再次调用 Elytra 运动更新:
PredictionEngineElytra.getElytraMovement(...)0.99 / 0.98 / 0.99Y - 0.05这就意味着:
体感结果就是:
所以这个现象的本质不是“拉回包本身太猛”,而是:
lastKnownGoodPosition.vectorsimulateNextTickPositionsimulateFriction()这四段拼在一起,让安全点速度被再次推进。
答:
OffsetHandler / setback 链决定是否处罚。答:
fireworksBox 扩大了与视角相关的允许速度空间。fireworksBox 是怎么影响移动阈值的?答:
movementThreshold 常量,也不直接改 OffsetHandler 的处罚阈值。1.7 是什么?答:
答:
simulateFriction() 当成 Elytra 下一 tick 起始速度,再跑了一次 Elytra 更新和摩擦。1.7、movementThreshold、OffsetHandler.threshold 说成同一层东西。fireworksBox 时,默认使用“基础阈值没变,但有效容错空间变宽”这套表述。