Slay the Spire 2 中的相关随机性
摘要
一篇博文揭示,《杀戮尖塔2》的随机数生成器由于 C# 的 System.Random 的线性特性而存在相关性,这使得玩家能够预测游戏内结果。文章详细说明了不同用种子加哈希初始化的 RNG 会产生可被利用的相关性,进而影响各种游戏事件。
<p><a href="https://lobste.rs/s/irws5p/correlated_randomness_slay_spire_2">评论</a></p>
查看缓存全文
缓存时间: 2026/06/16 11:33
# 杀戮尖塔2中的关联随机性
来源:https://tck.mn/blog/correlated-randomness-sts2/
关于《杀戮尖塔2》单人模式,有三个真实陈述:
1. 在码头区选择涅奥的骸骨时,随机诅咒有约54%的概率是负债\*。
2. 从垃圾堆事件中获得回手是**不可能的**。
3. 你的第一场战斗在码头区有76%概率掉落药水,在繁茂区只有4%概率掉落药水\*\*\*。
(\*假设涅奥的骸骨产生的遗物既不是新叶也不是万花镜) (\*\*假设你的涅奥遗物不给予卡牌或其他遗物) (\*\*\*均为当前beta补丁v0.107.0版本数据)
什么?!
为什么?罪魁祸首是不同随机数生成器之间出乎意料的关联性——知道游戏某个RNG的第一个输出,就能提供预测其他所有RNG第一个输出的信息。
## 杀戮尖塔2的随机数生成器
现在,我将对这个关联性做极其简化的解释。如果你想要更多细节,我会在这篇文章末尾深入探讨。如果不在乎,你可以跳过本节,直接看下面所有有趣的例子。
“关联RNG”(或“CRNG”)现象在杀戮尖塔社区中已为人知,因为《杀戮尖塔1》也有类似问题,详细描述见Forgotten Arbiter的博客文章 (https://forgottenarbiter.github.io/Correlated-Randomness/)。[\[1\]](https://tck.mn/blog/correlated-randomness-sts2/#footnote1)
简单来说,在尖塔1中,游戏使用了多个不同的伪随机数生成器,以防止例如战斗内的随机性影响未来的卡牌奖励。然而,它们都被初始化为相同的起始状态,这意味着它们会产生相同的数字序列。因此,狡猾的玩家可以通过关注过去随机事件的结果,来获取未来随机事件的信息。
为了避免同样的问题,尖塔2将其伪随机数生成器初始化为不同的状态。代码如下所示(为教学目的高度简化):
``
Rng UpFront = new Rng(seed + hash("up_front"));
Rng Shuffle = new Rng(seed + hash("shuffle"));
Rng UnknownMapPoint = new Rng(seed + hash("unknown_map_point"));
Rng CombatCardGeneration = new Rng(seed + hash("combat_card_generation"));
Rng CombatPotionGeneration = new Rng(seed + hash("combat_potion_generation"));
Rng CombatCardSelection = new Rng(seed + hash("combat_card_selection"));
Rng CombatEnergyCosts = new Rng(seed + hash("combat_energy_costs"));
Rng CombatTargets = new Rng(seed + hash("combat_targets"));
Rng MonsterAi = new Rng(seed + hash("monster_ai"));
Rng Niche = new Rng(seed + hash("niche"));
Rng CombatOrbGeneration = new Rng(seed + hash("combat_orbs"));
Rng TreasureRoomRelics = new Rng(seed + hash("treasure_room_relics"));
// ...
``
游戏中还有更多我没有列出的随机数生成器,值得一提的是,每个事件都有自己的RNG。
`hash`函数基本上从输入字符串产生一个“看似随机”的数字,但对于相同的输入,数字总是相同。所以这个想法是RNG状态被打乱了,但相同的种子仍然产生相同的运行结果。
问题在于这些种子被传递给C#中的标准`System.Random`类时。不幸的是,C#中使用的伪随机数生成算法在起始种子方面几乎是完全“线性”的。
这具体意味着什么有点复杂——我会在文章后面详细说明。但结果是,两个种子相差已知固定值的RNG,它们的输出也会相差一个模糊但仍可利用的量。
你能利用到什么程度呢?嗯……
这里有一大堆CRNG的后果,从有趣但不重要到真正影响游戏玩法都有(其中一些甚至会影响不知情的休闲玩家!)。
## 涅奥的骸骨
我先从引言中的第一个例子开始。如果你在码头区选择涅奥的骸骨,你获得的“随机”诅咒实际上遵循以下近似分布:
笨拙
负债
腐朽
疑虑
内疚
受伤
常态
悔恨
羞愧
扭动
然而,在繁茂区,你会从以下分布中获得诅咒:
笨拙
负债
腐朽
疑虑
内疚
受伤
常态
悔恨
羞愧
扭动
这个对我来说相当有趣——Reddit和Discord上的许多人都在哀叹他们糟糕的运气 (https://tck.mn/blog/correlated-randomness-sts2/bones_debt.png),抱怨自己总是从涅奥的骸骨中roll到负债。[\[2\]](https://tck.mn/blog/correlated-randomness-sts2/#footnote2)甚至在发现CRNG之前,我就看到一些帖子坚称这看起来比随机概率更频繁。很难形容我的大脑是如何*瞬间*自动将它们视为教科书式的确认偏误而忽略的。然而……
要理解这一点,我们需要关联三个随机性来源:
- **涅奥可用的“诅咒遗物”**来自对涅奥事件特定RNG的调用,该RNG的种子是`seed + 1 + hash("NEOW")`。涅奥选项总是恰好包含来自“诅咒池”的一个遗物,如wiki (https://slaythespire.wiki.gg/wiki/Slay_the_Spire_2:Neow) 所述。选择提供8个诅咒遗物中的哪一个,是对涅奥RNG的第一次调用。
- **涅奥的骸骨的随机诅咒**来自对`RunState.Rng.Niche`的调用,该RNG的种子是`seed + hash("niche")`。由于新叶和万花镜也会调用`Niche`进行随机,如果涅奥的骸骨roll到这两个遗物中的任何一个,关联性就会被破坏。但否则,这将是第一次调用`Niche`。
- **第一幕变体**(码头区或繁茂区)来自对`StartRunLobby\#BeginRunLocally`中创建的一个未命名RNG的调用,该RNG使用基础种子。
由于涅奥的骸骨来自涅奥的“诅咒池”,只有当对涅奥RNG的第一次调用落在特定范围内时,你才能看到它,这对`Niche`第一次调用的可能范围施加了很强的约束(结合你所在的第一幕变体后更强)。
显然,这种关联性对游戏玩法影响很大,即使是对此不知情的玩家也是如此。它使涅奥的骸骨成为一个更差的遗物,极少给出像笨拙、内疚和受伤这样危害较小的诅咒,而更常给出像负债这样更致命的诅咒。
在这一点上,你可能在想“等等,这不意味着我们可以预测*每一个*涅奥遗物的随机性吗?”确实可以!让我们再看看其他几个。
## 大胶囊
来自大胶囊的第一个遗物*从来不是*普通品质。
真是个大加强!
更具体地说,在繁茂区,大约有70%概率是不凡品质,30%概率是稀有品质。在码头区,大约有37%概率是不凡品质,63%概率是稀有品质——但有一个注意事项:
大胶囊在码头区第一幕出现的概率只有约1.65%,因为一切都相互关联。(似乎没人注意到这个;这里有人 (https://tck.mn/blog/correlated-randomness-sts2/large_capsule.png) 在我最初调查这一切时的非常有趣的回应。)
以下是码头区涅奥“诅咒池”选项的具体分布:
诅咒珍珠
沉重石碑
大胶囊
叶状膏药
涅奥的骸骨
危险剪刀
丝绒发丝
银制圣杯
在繁茂区:
诅咒珍珠
沉重石碑
大胶囊
叶状膏药
涅奥的骸骨
危险剪刀
丝绒发丝
银制圣杯
回到大胶囊,与涅奥的骸骨非常相似,关联性对游戏玩法有真正的影响。这个遗物平均比它“应该”的更强。
那小胶囊呢?
## 小胶囊
由于小胶囊不是诅咒池遗物,它不像涅奥的骸骨和大胶囊那样有内在偏差。
然而,这意味着我们可以利用另一个诅咒池遗物的存在来预测小胶囊遗物的稀有度:
诅咒珍珠\[U\]
沉重石碑\[U\]
叶状膏药\[U\]
涅奥的骸骨\[U\]
危险剪刀\[U\]
丝绒发丝\[U\]
银制圣杯\[U\]
诅咒珍珠\[O\]
沉重石碑\[O\]
叶状膏药\[O\]
涅奥的骸骨\[O\]
危险剪刀\[O\]
丝绒发丝\[O\]
银制圣杯\[O\]
(这里,\[U\]表示码头区,\[O\]表示繁茂区。大胶囊永远不会出现,因为硬编码限制两个胶囊不能同时出现。)
我保持了与上一节两个图表相同的柱状图缩放比例——每行的总宽度与该诅咒池遗物在该幕实际出现的频率成正比。这是为了展示一个简洁的启发式方法:**小胶囊在码头区*通常*给出普通遗物,在繁茂区*通常*给出不凡或稀有遗物**。
好了,还有更多带有随机性的涅奥,所以我将快速掠过几个,然后进入一些不同的东西。
## 叶状膏药和沉重石碑
(这些是“转化2”和“选择一张稀有卡”。)
由于两者都是诅咒池遗物,它们都有内在偏差。但它们都生成多张卡牌,所以我们只能预测第一张。
事实证明,**叶状膏药的第一次转化只有22种可能**(每个角色的80张卡牌池中),有些概率显著更高。
(这些图表相当大,所以我在这里隐藏了。你可以点击每个角色查看可用选项,并开心地决定哪一幕更好。)
叶状膏药码头区:
侵略
愤怒
武装
灰烬打击
壁垒
战斗专注
血墙
放血
重锤
铁壁
烙印
恶魔火
来打我
火焰屏障
遗忘仪式
havoc
头槌
地狱崛起
血之沸腾
超越之嚎
无惧
地狱之刃
传家宝锤
繁茂区:
熔岩拳
还未
献祭
一二拳
契约终结
完美打击
掠夺
剑柄打击
原始之力
火葬堆
愤怒
第二风
设置打击
耸肩无视
恶意
踩踏
添柴
践踏
石甲
回旋镖
嘲讽
撕裂
传家宝锤
类似地,**沉重石碑的第一个选项在繁茂区只有11种可能,在码头区只有3种可能**!这是因为如上所示,沉重石碑在码头区本来就只出现在约1.3%的情况下,所以看到它本身就是非常强的信息。
码头区:
主宰
撕裂
鞭打
传家宝锤
繁茂区:
主宰
蹂躏
还未
献祭
一二拳
契约终结
原始之力
火葬堆
添柴
撕裂
鞭打
传家宝锤
## 新叶与奥术卷轴
(这些是“转化1”和“随机稀有卡”。)
与小胶囊一样,第一幕和诅咒池选项都会影响这些遗物。
列出所有14种第一幕与诅咒池遗物组合的完整卡牌列表会占用太多空间,所以我就只说:你可以将新叶可能的转化范围缩小到**4到39个选项**(80张卡中),将奥术卷轴可能的卡牌范围缩小到**3到12个选项**(25张稀有卡中),具体取决于你的第一幕和涅奥。
不过有一个有趣的小细节:如果你在繁茂区看到危险剪刀(这相当罕见),那么新叶约有70%概率给出你角色字母顺序的第一张卡牌,奥术卷轴约有65%概率给出你角色字母顺序的第一张稀有卡牌。
好吧,但说实话,到目前为止,这些大部分并不会真正改变你的玩法。让我们看看其他能做到这一点的东西吧。
## 闪电球与随机目标
码头区的简单怪物池中有两个多敌人战斗:尸虫群 (https://slaythespire.wiki.gg/wiki/Slay_the_Spire_2:Corpse_Slug) 和蝌蚪群 (https://slaythespire.wiki.gg/wiki/Slay_the_Spire_2:Toadpole)。如果你是故障机器人,你可能想知道第一个闪电球会击中哪里,特别是如果你在第一回合抽到了双重释放。
具体到码头区的第一场战斗,**你的第一个闪电球有75%概率击中左侧的敌人**。(如果你使用双重释放,这适用于激发效果;如果不使用,则适用被动效果。)如果你记得你看到了哪个诅咒池遗物,你可以做得更好:
诅咒珍珠
沉重石碑
大胶囊
叶状膏药
涅奥的骸骨
危险剪刀
丝绒发丝
银制圣杯
你可以在尸虫群战斗中做得*更好*,因为它的起始攻击模式是随机的。我不会在这里列出完整的表格,但举个例子:如果你看到了危险剪刀*并且*右边的尸虫正在施加debuff,那么你的闪电球实际上有>95%概率击中右边的那个。
(顺便说一下,第二层的尸虫群在回合1同时攻击的概率不到3%。它们真是太好了!)
这适用于整个运行的第一个随机战斗目标——例如,你可能会预测你的第一个倒计时触发在亡灵法师身上,或者你的第一面招架盾触发在任何角色身上。
说到早期第一幕,让我们终于来看看引言中的另外两个例子。
## 垃圾堆
由于垃圾堆是码头区专属事件,它本身就有内在偏差。以下是垃圾堆RNG在条件于第一幕RNGroll到码头区时的输出:
铁蒺藜
冲突
干扰
双持
巩固
你好世界
迂回
回手
撕裂
叠加
正如你所见,在单人游戏中**完全不可能**获得回手这张卡牌。[\[3\]](https://tck.mn/blog/correlated-randomness-sts2/#footnote3)
如果你关心预测遗物,连续两张卡牌分别对应黑石护符、捕梦网、手钻、钱庄和靴子(例如,如果卡牌是巩固或你好世界,那么遗物是手钻)。
如果你想要更精确地预测垃圾堆,以下是在你看到的诅咒池遗物条件下的输出(柱状图可悬停):[\[4\]](https://tck.mn/blog/correlated-randomness-sts2/#footnote4)
诅咒珍珠
你好世界 (66.63%)
迂回 (32.26%)
撕裂 (0.43%)
叠加 (0.69%)
沉重石碑
迂回 (98.82%)
撕裂 (1.18%)
大胶囊
你好世界 (0.04%)
迂回 (0.26%)
撕裂 (99.70%)
叶状膏药
巩固 (0.08%)
你好世界 (0.26%)
撕裂 (35.29%)
叠加 (64.36%)
涅奥的骸骨
铁蒺藜 (76.61%)
冲突 (8.24%)
双持 (0.14%)
巩固 (0.18%)
叠加 (14.82%)
危险剪刀
冲突 (57.61%)
干扰 (42.16%)
双持 (0.23%)
丝绒发丝
冲突 (0.56%)
干扰 (42.90%)
双持 (56.54%)
银制圣杯
铁蒺藜 (0.83%)
冲突 (0.03%)
双持 (4.40%)
巩固 (79.10%)
你好世界 (15.46%)
叠加 (0.18%)
顺便说一下,发现这个问题后,我在网上搜索了相关讨论,确实有人注意到他们似乎无法完成图鉴。但我也发现用户@hoge大约一个月前在Discord上发布了对该问题的准确描述 (https://tck.mn/blog/correlated-randomness-sts2/hoge_discord.png)。向他们致敬!
## 药水掉落与问号战斗
最后,这是引言中提到的第三点——你的第一场战斗掉落药水的概率有多高?你现在已经熟悉流程了:
诅咒珍珠\[U\]
沉重石碑\[U\]
大胶囊\[U\]
叶状膏药\[U\]
涅奥的骸骨\[U\]
危险剪刀\[U\]
丝绒发丝\[U\]
银制圣杯\[U\]
诅咒珍珠\[O\]
沉重石碑\[O\]
大胶囊\[O\]
叶状膏药\[O\]
涅奥的骸骨\[O\]
危险剪刀\[O\]
丝绒发丝\[O\]
银制圣杯\[O\]
再次提醒,石碑和大胶囊在码头区极其罕见,剪刀和发丝在繁茂区极其罕见。综合考虑,**总体而言,你的第一场战斗在码头区掉落药水的概率是76%,在繁茂区仅为4%**!
然而,请注意,选择任何会生成卡牌奖励或随机遗物的涅奥会打破这种关联,因为它会抢走对奖励RNG的第一次调用。所以在糟糕的繁茂区地图上,失落之箱可能看起来比平均水平更有吸引力。
作为奖励,机会
相似文章
How hackers reverse Math.random()
本文演示如何逆向常见的伪随机数生成器(如线性同余生成器、XOR shift、Flash的RNG),并通过预测扫雷游戏中的地雷位置展示实际攻击,强调不要将普通随机数用于安全场景。
什么是随机生成?
本文探讨了计算机中的伪随机数生成,重点聚焦于线性同余生成器(LCG)及其质量可视化。文章还提及了 Cloudflare 的熔岩灯等熵源,并作为基于属性的测试的前导内容。
有时需要随机性来实现协调
本文介绍了 Diamond Attention,这是一种用于多智能体强化学习的方法,通过引入结构化随机性来打破对称性,从而实现同质智能体之间的角色区分,在 XOR 游戏等对称任务中实现了完美的协调。
具备发现 Bug 概率保证的随机调度器
Microsoft Research 的这篇论文介绍了一种随机调度技术,旨在为发现软件系统中的 Bug 提供概率性保证。该成果已发表于 ASPLOS 会议,核心在于利用算法随机性来实现系统化的故障检测。
熵
一篇技术博文,探讨随机性、Linux熵以及构建一个名为morerandom的工具,该工具使用WASM插件来为系统熵池提供熵。