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.RandomPCG32 のような最新アルゴリズムに置き換えることで、シード間の線形相関を即座に除去できます。
  • カスタム実装: C# 標準ライブラリに依存せず、ゲームコード内で PRNG を直接実装することで、デスクトップとモバイルなど異なるプラットフォーム間でシードの一貫性が保たれ、Slay the Spire 1 で見られたバージョン固有のシードドリフトを防げます。
  • カウンタベース RNG: カウンタベースの乱数生成器を使用すれば、セーブファイルをロードした際に RNG 状態を手動で "進める" 必要がなくなります。

コミュニティの見解

技術的観察者は、この問題がゲームが Godot エンジン上で C# の System.Random を使用して開発されたことに起因すると指摘しています。一方、Godot の GDScript RNG は PCG32 を使用しており、当初からこの問題は回避できたはずです。他の開発者は、ゲームプレイに直結する乱数生成器はプラットフォーム依存のライブラリコードではなく、ゲームコードとして扱うべきだと述べており、これにより一貫性と公平性が確保されます。

Sources