解释《天际》中的魔光经验漏洞

《上古卷轴5:天际》中一个长期存在的漏洞让某个法术能提供远超应有数量的经验值。Alex Beals在游戏中验证了这一点,随后逆向分析了经验值实际发放的时机,并找到了背后的竞态条件。

中文
复制
作者在文中用的一张表情包图,配文是「Fire and Forget:我现在最想对这个游戏做的事」

2026年10月6日

目录⤴︎

Magelight 如何给予经验值

在游戏中验证

逆向工程经验值给予的触发时机

碰撞是如何工作的

当它撞到东西时会发生什么

让投射物停下

Bug 拆解

如何最大化收益

稳定复现的配置指南

在《天际》里,首都 Solitude 城外有个地方,如果你在山顶施放 Magelight,就能获得海量的改变系经验值——一次施法就足以让你从 15 级升到 25 级。但这是为什么?

关于它为何有效,流传着很多错误的解释,其中主流的一种认为与目标距离有关:

“Magelight 根据飞行时间长短,在抵达时给予经验值。你发现的是一个你正好处于渲染距离边缘的位置,但还有办法让它更高。”——@Jewbacca1991

但其他人很快指出并非如此,在其他远距离下这招并不管用:这座山似乎有什么特别之处。UESP 的《天际》wiki声称原因在于你能看到的云层:

“该地点获得的大量经验值,似乎与穿过多个天气效果平面有关。”

但有很多多云的山,在那里这招都不管用。让我们逆向工程 Magelight 的经验值公式,把这件事彻底了结。

Magelight 如何给予经验值

Magelight 是学徒级法术。用 TES5Edit 加载它,可以看到它的 Magnitude 为 5,Duration 为 60,基础效果为 LightFFAimed,即 0001EA6D。加载该效果后可以看到它的 Base Cost 为 2,Skill Usage Multiplier 为 0.15。

Fire and Forget——我现在最想对这个电子游戏做的事。 No Death Dispel 很有意思。如果光源不是我,那它从哪来?

法术 ID,00043323 法术效果,0001EA6D

这些都会被用来计算 Skill Usage 值。由于该法术没有 Area,公式很快就能简化:

SkillUsage=BaseEffectCost×Area×(Magnitude×Duration10)1.1=2×1×(5×6010)1.1≈84.307

Alteration XP 拿到这个值后,乘以技能使用倍率(Magelight 为 0.15),再乘以技能使用乘数(Alteration 为 3),得到每次施法的原始 XP 为 37.938。

在游戏中验证

理想情况下,我们对着墙施放 Magelight,看看它是否给出预期的 XP 量。可惜游戏内(即使使用控制台命令)没有办法获得精确的 XP。幸运的是,我们可以用 Cheat Engine 通过 Lua 直接读取底层内存。1 你可能还记得 Lua(我最喜欢的、源自巴西石油公司的下游编程语言)——在我之前那篇速通 GBA 版哈利波特 1 的文章里提到过。

-- (Set the player's Alteration level to 15 with a console command)
-- player.setav alteration 15

-- Set the overflow XP to 0
local ALTERATION = 18
local player = readPointer(0x01B2E8E4)
local skills = readPointer(readPointer(player + 0x614))
local entry  = skills + 12 * ALTERATION - 0x40
writeFloat(entry + 4, 0)
print(string.format('Alteration XP: %.3f', readFloat(entry + 4)))

-- <Cast the spell>

-- Read out the current XP
print(string.format('Alteration XP: %.3f', readFloat(entry + 4)))

Lua

复制

如果我们运行上面的脚本2 我换成了使用 VEH 调试器,通过 Edit > Settings > Debugger method 设置,不过我不确定这是否必要。 将 XP 重置为 0,施放 Magelight,然后运行最后一步读取新的 XP,我们看到它正好按预测增长。好耶!

但这一切都没有考虑距离。为什么我们得到的 XP 远多于 38?

逆向工程 XP 何时发放

Magelight 与大多数其他法术不同,因为它不需要击中一个人——击中人可以生效,但对着墙、桌子或石头施放也同样有效。它在所有这些情况下都会给予 XP,那么它如何知道何时击中了什么东西?

碰撞是如何工作的

施放该法术会创建一个 LightSpellProjectile,它沿着基于你最初瞄准位置的路径飞行。每个 tick 它都会检查是否与某物发生了碰撞,大致遵循以下三个步骤:

绘制物体在下一个 tick 将移动到的路径(0x007A0090)
它取投射物的速度(512 单位/秒)除以你的帧率(60 fps3 是的,这意味着如果你在老 Xbox 主机上玩,碰撞检查频率会更低;如果你把帧率调到正常值以上,频率会更高。如果你使用 Slow Time 龙吼或使用 Archery/Block 技能点来减慢时间,它也会更高,不过每秒碰撞次数保持不变。 )得到 \frac{512}{60} = 8.5\bar{3} 单位/tick,方向为你施放的方向。该法术的射程为 10,000,4 同样由 ESM 文件决定,不过它不在上面的截图裁剪范围内。哎呀。 所以它会沿路径继续飞行约 ≈19.5 秒,然后消失。

将这条路径与每个拥有碰撞体、且通过碰撞层过滤的对象求交。(0x00DE0040)

游戏中的每个对象都有一个碰撞层,用来定义它会与什么发生碰撞(比如 L_TRANSPARENT 不会阻挡投射物或法术,而 L_TERRAIN 会)。如果两者不匹配,碰撞就不会触发。

L_TRANSPARENT L_TERRAIN

检查路径是否命中了该对象。(0x00EC41C0)

首先,它找到对象表面上离投射物最近的点,然后求出该最近点处背离表面的法向量。

这里用长方体演示,不过像岩石或城堡这类东西会有复杂得多的法线贴图

接着它检查投射物的运动方向是否与法线方向一致。5 做法是看点积是正还是负。数学万岁!如果为正,说明投射物正在远离表面,于是判定没有碰撞。如果两者方向相反,那么只要它已经与该对象相交或位于对象内部,或者会在下一个 tick 步中撞上,就标记为碰撞。

注意,即使投射物已经位于对象内部,法线检查也仍然先执行:如果它已经穿过了对象的一半以上,使得最近点落在远侧、法线与运动方向一致,那么它就不再触发碰撞。

碰撞发生时会发生什么

一旦 LightSpellProjectile 与某个东西(或多个东西)发生碰撞,它就会针对每次碰撞运行一个 "OnHit" 函数(0x007A30FD),触发本该发生的事。这个函数处理了很多与 Magelight6 无关的事情,比如 wards 在 0x006ECE30 处阻挡法术,或者目标在 0x006E99D0 处被 Destruction 法术击退。但最终它要么把效果施加到人7 上——严格说是 magic target,其中也包括激活触发器等东西(0x00664740)——要么施加到碰撞表面上。大多数单次施放法术在 person 分支里处理 XP 奖励,但 0x00660353 处有一个特殊检查,用来给那些能作用于替代目标的法术发放 XP,比如 Magelight。8 其实不是比如 Magelight,就是 Magelight。严格来说所有 Reanimate 和 Light 法术都算,但所有 Reanimate 法术都会覆盖这个位置,改成在你把尸体复活时直接给 XP,而另一个 Light 法术 Candlelight 不是投射物。

最后,"OnHit" 在 0x007A2560 处施加命中效果。它确保自身只运行一次,并检查被击中的材质,以决定后续行为(例如附着到人身上,或者对墙壁而言只是悬浮在墙边)。如果没有材质,或者材质不受支持,就不会触发命中事件。

让投射物停下来

到目前为止描述的一切看起来都能正常工作,实际上大部分时候也确实如此,但问题就出在这里。与某物碰撞并运行 "OnHit" 之后,代码会调用 "HandleHit" 函数来判断投射物是否应该继续飞行。通常情况下它会令投射物停下,但有两种例外:1)撞到非实体物件时它会继续飞行⁹。更具体地说,是 Havok 碰撞代码中的 BROAD_PHASE_PHANTOM,用于触发器(比如"走到这里就激活陷阱/过场动画/对话"这类东西)。这个检查发生在 0x0079f022 处。2)撞到层类型 26 或 28 的物件时,它们分别对应 L_TRANSPARENT_SMALL 和 L_TRANSPARENT_SMALL_ANIM¹⁰。如果你好奇是未来的我想找证据,这个例外判断在 0x0079f063 处。

Thinspo! 没有调试颜色,所以用 `bShowMarkers=1` .ini 调整也显示不出来。

L_TRANSPARENT,不与 L_SPELL 碰撞 L_TRANSPARENT_SMALL,确实与 L_PROJECTILE 和 L_SPELL 碰撞

由于 L_TRANSPARENT_SMALL 在其碰撞层中会与投射物和法术"碰撞",这个表面会不断登记碰撞却不令投射物停下,于是 bug 就出现了。

Bug 剖析

你不断施放 Magelight,直到它进入 L_TRANSPARENT_SMALL 的包围盒。那个碰撞层会与 L_SPELL 碰撞,所以它被算作一次碰撞。它既不是人也不是法术目标,于是走 fallback 路径,奖励 38 点经验。但 L_TRANSPARENT_SMALL 不受 "HandleHit" 约束,所以投射物继续飞行。下一个 tick,同样的事情再次发生。它在包围盒里,算作一次碰撞,奖励 38 点经验。它会一直这样下去,直到最近点的法线方向与飞行方向一致,大致就是它穿到一半的时候。

但那些 L_TRANSPARENT_SMALL 物体到底在哪儿?你猜对了:就在 Solitude 旁边那座山上。11 Ï 我用 Skyrim Creation Kit 快速做了个 mod 把墙体显示出来,不过你也可以直接改 .esm,给碰撞层加个调试颜色,效果一样。

别的地方也有,比如 High Hrothgar 两侧:

或者 Thieves' Guild 任务线结尾处 Irkngthand 雕像周围:

再比如 Thalmor Embassy 外围那些极薄的平面:

但最大的几块都在 Solitude 旁边,所以你必须对着那座山施法,而不是随便找座远处的云雾缭绕的山就行。

如何最大化

大多数攻略都说瞄准山顶,但你真正该瞄的,是能让法术穿过 L_TRANSPARENT_SMALL 方块的时间最长的那条线(而且要穿过正确的区域)。山顶不错,但还能更好。12 山顶大约能触发 ~280 次,而最优位置能到 ~850 次。切,才 280。

Ï 很快13 我说的是真的很快,Claude Opus 5.5 只用了大约 10 分钟、只靠一张截图指出问题出在哪,就两把写完了这个 mod。 用 SKSE 写了个 mod,把隐形墙渲染出来,并根据你瞄准的位置实时计算预期的 XP 触发次数,以及最大化 XP 收益的最优角度。只要在附近走一圈就能很快找到最优位置。14 大量数学计算也找到了那个“最优”位置,但它在底下,进不去。完美。

字体选得几乎没法看

从 Alteration 15 级升到 100 级,需要 ∑L=15992⋅L1.95 = 528,804.0234 XP。每次触发 37.938 XP,也就是将近 14,000 次触发,所以一条线触发 250 次还是 850 次,差别就是 55 次施法和 17 次施法。不过关键在于能稳定复现——光说“瞄准天上这个点”没用,别人根本照做不了。所以下面是一份能照着做的指南!

稳定卡位指南

先传送到 Solitude,然后从正门出去,这样你就会正好站在门外。接下来,我们要爬上左边那块岩石。那里有一堵隐形墙,贴着它站好,同时靠住最远角落的城墙。

我们要瞄准的是紧贴的那面墙左上角砖块的砂浆缝。

为了找到正确的角度,我们要蹲下,然后把准星中心对准那条砂浆缝(最佳位置是右边那块砖的左上角

来源: Alex Beals← 返回首页