Slay the Spire 2 相関ランダム性バグ
Slay the Spire 2(ベータパッチ v0.107.0)には、異なる疑似乱数生成器(PRNG)が相関しているという重大なバグが含まれています。これは、Neow が提供する遺物のようなランダムイベントの結果が、カード変換、ポーションドロップ、敵のターゲティングなど、将来のランダムイベントの結果を予測できることを意味します。
原因: C# の System.Random における線形シード
Slay the Spire 2 は、戦闘などのある領域でのランダム性が別の領域(例: カード報酬)に影響しないように、複数の異なる RNG インスタンスを使用しようとしています。各インスタンスは、特定の文字列のハッシュで修正されたシード(例: seed + hash("shuffle"))で初期化されます。
しかし、ゲームは標準の C# System.Random クラスを使用しています。このクラスで使用されるアルゴリズムは、開始シードに対して線形です。その結果、既知の固定量だけシードが異なる二つの RNG は、予測可能で悪用可能な量だけ出力が異なります。ゲームの RNG オフセットが固定されているため、任意の RNG の最初の出力は、ゲーム内のすべての他の RNG の最初の出力に関する情報を提供します。
相関ランダム性がゲームプレイに与える影響
この相関は、プレイヤーにすぐには分からない形でゲームプレイに体系的なバイアスをもたらします。
Neow の遺物と呪い
予測可能性は Neow が最初に提供する選択肢で最も顕著です。例えば、プレイヤーが Underdocks Act で Neow's Bones を選択した場合、受け取るランダム呪いが Debt になる確率は約 54% ですが、Overgrowth Act では同じ遺物で Writhe が出る確率は 73% です。
他の Neow の遺物もバイアスを示します:
- Large Capsule: この遺物は決してコモンになりません。Overgrowth では約 70% がアンコモン、約 30% がレアです。Underdocks では約 63% がレア、約 37% がアンコモンです。
- Leafy Poultice: 最初のカード変換は、キャラクターの 80 枚のカードプールのうち 22 通りにしか限定されません。
- Hefty Tablet: 最初のレアカードオプションは Overgrowth で 11 通り、Underdocks で 3 通りに限定されます。
戦闘とマップイベント
相関は戦闘メカニクスやマップ固有のイベントにも及びます:
- Potion Drops: 最初の戦闘でポーションがドロップする確率は Underdocks で 76%、Overgrowth でわずか 4% です。
- Enemy Targeting: Defect の場合、最初の Underdocks 戦闘で最初に出るライトニングオーブが左側の敵に当たる確率は 75% です。
- Trash Heap Event: シングルプレイヤーモードでは、Act RNG と Event RNG が相関しているため、Rebound カードを Trash Heap イベントで取得することは数学的に不可能です。
Act 2 と 3 の予測可能性
予測可能性は後半の Act にも残ります。Act 2 の Doll Room イベントは Neow の初期オファーと高度に相関しています。例えば、ランニング開始時に Hefty Tablet を引いた場合、"one doll" オプションが Mr. Struggles になる確率は 91% です。同様に、Crystal Sphere の遺物ボックス配置は、ランニング開始時の最初の戦闘でドロップしたゴールド量から予測できます。
技術的解決策と推奨事項
この問題は PRNG の選択に起因する構造的欠陥です。相関を排除するために、開発者は System.Random から非線形 PRNG へ移行すべきです。
推奨される修正
- 非線形 PRNG の実装:
System.Randomを PCG32 のような最新アルゴリズムに置き換えることで、シード間の線形相関を即座に除去できます。 - カスタム実装: C# 標準ライブラリに依存せず、ゲームコード内で PRNG を直接実装することで、デスクトップとモバイルなど異なるプラットフォーム間でシードの一貫性が保たれ、Slay the Spire 1 で見られたバージョン固有のシードドリフトを防げます。
- カウンタベース RNG: カウンタベースの乱数生成器を使用すれば、セーブファイルをロードした際に RNG 状態を手動で "進める" 必要がなくなります。
コミュニティの見解
技術的観察者は、この問題がゲームが Godot エンジン上で C# の System.Random を使用して開発されたことに起因すると指摘しています。一方、Godot の GDScript RNG は PCG32 を使用しており、当初からこの問題は回避できたはずです。他の開発者は、ゲームプレイに直結する乱数生成器はプラットフォーム依存のライブラリコードではなく、ゲームコードとして扱うべきだと述べており、これにより一貫性と公平性が確保されます。