えいむーさんは明日も頑張るよ

同時存在数上限による代替オオモノの誤解と見解

オオモノ湧きアルゴリズム

サーモンランで同時に同一オオモノシャケは三体までしか出現しないことが知られています。

そしてそれを利用したいくつかの戦法が生み出されてきたのですが、いろいろと誤解があるのでアルゴリズム解析からわかった最新の情報をお届けします。

オオモノシャケに関するウワサ

この章ではオオモノシャケに関するウワサに対するアルゴリズム解析の観点からの回答をまとめていきます。

意外と知られていないことがあるかもしれません。

同一のオオモノシャケは同時に三体までしか存在できない。

これは正しい情報です。

Coop_System.bprmというファイルにより、いかなるオオモノシャケも内部パラメータ的に同時に三体までしか出現できないような制約がかかっています。

オオモノシャケの出現に偏りがある

これは誤った情報です。

スプラトゥーン 2 の疑似乱数生成器(以後 PRNG とします)は精度は低いですが、生成される乱数に考えられているような偏りはありません。

四体目のオオモノシャケが出現しようとすると別のオオモノシャケに変化する

これは誤った情報である可能性が高いです。

しかし、四体目のオオモノシャケがでないからといってオオモノシャケが何もでないというわけではありません。

詳しくは後述します。

カタパッドを片翼放置すると得をする

これは誤った情報です。

カタパッドが同時存在数上限に引っかかった場合、たしかに新たに四体目のカタパッドが湧くことはありません。

しかし、カタパッドがモグラやテッパンのような比較的金イクラの納品数を増やしやすいオオモノシャケに変化する確率などを考えると有効な作戦とは言えません。

詳しくはこの記事 (opens new window)を読んでください。

オオモノシャケ出現アルゴリズム

出現するオオモノシャケを決定するアルゴリズムは以下のとおりです。

private func getEnemyId(mEnemySeed: UInt32) -> SalmonType {
    let rnd: Random = Random(seed: mEnemySeed)
    let shakeTypes: [SalmonType] = [.shakebomber, .shakecup, .shakeshield, .shakesnake, .shaketower, .shakediver, .shakerocket]
    var mRareId: SalmonType = .shakebomber
    for salmonId in shakeTypes {
        if !Bool((UInt64(rnd.getU32()) * UInt64((salmonId.rawValue + 1))) >> 0x20) {
            mRareId = salmonId
        }
    }
    return mRareId
}

また、それぞれの Enum は以下の表の通りの意味を持ちます。

Enum RawValue Name
shakebomber 0 バクダン
shakecup 1 カタパッド
shakeshield 2 テッパン
shakesnake 3 ヘビ
shaketower 4 タワー
shakediver 5 モグラ
shakerocket 6 コウモリ

シード値

アルゴリズム解説にあたって、サーモンランの WAVE の挙動を決定する三つのシード値について解説します。

GameSeed

  • WAVE 内容など全てのサーモンランの挙動を司るシード値
    • 唯一のシードで、一つしか存在しない
  • IPSwitch などで固定しているのはこのシードです

WaveSeed

  • 各 WAVE の内容を司るシード値
    • それぞれの WAVE に一つずつなので三つしか存在しない
  • GameSeed で初期化された PRNG から生成されるので、GameSeed さえ決まればここの値は一意に決定できます

EventSeed

  • 各種イベントで使用されるシード値
    • 他の二つのシードと比べて何度も生成される
      • キンシャケ探しであればキンシャケが隠れる度
      • 湧き方向が変わる度、オオモノシャケが出現する度に生成される
  • WaveSeed で初期化された PRNG から生成されるので、GameSeed さえ決まればここの値は一意に決定できます
  • キンシャケ探しであればキンシャケのアタリ位置、イベントなしであれば出現するオオモノシャケなどを決定する際に利用されます

そして出現するオオモノシャケを決定するのは EventSeed であるということです。

アルゴリズム解説

private func getEnemyId(mEnemySeed: UInt32) -> SalmonType {
    // PRNGをmEnemySeedで初期化(無視して良い)
    let rnd: Random = Random(seed: mEnemySeed)
    // オオモノシャケの配列(無視して良い)
    let shakeTypes: [SalmonType] = [.shakebomber, .shakecup, .shakeshield, .shakesnake, .shaketower, .shakediver, .shakerocket]
    // 初期オオモノはバクダン(ちょっとだけ大事)
    var mRareId: SalmonType = .shakebomber
    // オオモノシャケの種類の数だけループする(今回の場合は7回)
    for salmonId in shakeTypes {
        // PRNGが生成した乱数とループ回数を掛け算する
        // その値を2^32(4294967296)で割る
        // 結果が0ならそのオオモノを出現するオオモノとして上書きする
        if !Bool((UInt64(rnd.getU32()) * UInt64((salmonId.rawValue + 1))) >> 0x20) {
            mRareId = salmonId
        }
    }
    return mRareId
}

例えば GameSeed として7190FC21が選ばれた場合には WAVE1 の WaveSeed は7190FC21になります。

WAVE1 における WaveSeed

イカ研究所の実装ミスにより WAVE1 の WaveSeed は常に GameSeed と同じ値が選ばれてしまう。これによってインデックスバグなどの重大な WAVE の内容の偏りが発生してしまう。

ではこの WaveSeed からどんな EventSeed が生成されるかということになるが、最初にオオモノ出現判定に使われるのはBB05BB53という値である。

BB05BB53 で初期化された PRNG 生成する乱数

GameSeed WaveSeed(Wave1) EventSeed(1st)
7190FC21 7190FC21 BB05BB53

ここまでをまとめると上のような表になる。

最初に出現するオオモノシャケの種類を決めるのはEventSeed=mEnemySeedであるBB05BB53で初期化された PRNG である。

アルゴリズムではこの PRNG が七回乱数を生成し、ループ回数との積を 2^32 で割った結果(整数)が 0 であれば出現するオオモノを上書きするという処理になる。

計算結果が 0 になる確率

で、このアルゴリズムだが、よく考えれば一回目のループは絶対に 0 になることがわかる。何故なら PRNG が出力する最大の数は2^32 - 1であり、それに 1 をかけて2^32で割れば必ず 1 未満(整数 0)になるからだ。

n 計算結果が 0 になる確率
1 100.00%(1/1)
2 50.00%(1/2)
3 33.33%(1/3)
4 25.00%(1/4)
5 20.00%(1/5)
6 16.66%(1/6)
7 14.28%(1/7)

これはかけられる数 n が大きくなるとだんだん確率が下がっていき、最終的に約 14.28%になる。

七回目のループはコウモリが出現するかどうかの判定になるが、確率が一番低いからといってコウモリが一番出にくいことにはならない。何故なら、計算結果が 0 であれば「出現するオオモノシャケを上書きする」という処理になっているからだ。

そのため、最初の六回のループの結果に関わらず、最終的に七回目のコウモリの出現判定は 14.28%の確率で成功し、結果的に 14.28%の確率でコウモリが出現することになる。

その手前のモグラは、出現確率が 16.66%(1/6)だが、14.28%の確率で次のループでコウモリが出現するので「モグラのループが成功、コウモリのループが失敗」のときだけモグラが出現できることになる。よって、1/6×(11/7)=1/71/6 * (1 - 1/7) = 1/7でモグラもやはり 14.28%の確率で出現することになるのである。

結果的に、どのオオモノも 1/7 の確率で出現することになる。

実際に出現するオオモノ

では実際にどのオオモノが出現するのかを計算してみよう。

BB05BB53で初期化された PRNG はループの中で七回乱数を生成するが、それとループ回数をかけて 2^32 で割ったものを以下の表にまとめた。

n 生成される乱数 乱数と n の積を 2^32 で割った結果(整数)
1 10EA9D33 0
2 3C73485C 0
3 FE6FC9D9 2
4 40C69284 1
5 04819CAA 0
6 A21605CE 3
7 22B73154 0

すると出現成功(結果が 0)になるのは 1, 2, 5, 7 の場合であることがわかる。これはそれぞれバクダン、カタパッド、タワー、コウモリの出現成功を意味する。

ただし、さっき述べたように「最後に出現成功したオオモノ」が実際には出現するため、出現するのはコウモリであることがわかる。

EventSeed と出現するオオモノ

もしもオオモノが全く同時存在数上限に引っかからないのであれば、どんなオオモノが出現するかは EventSeed から一意に決定するし、これはつまり GameSeed から計算可能であることを意味する。

実際、Ocean Calc (opens new window)Seed Hack (opens new window)では全く詰まらない「理想状態」でのオオモノ湧きを表示している。

以下の表は GameSeed として7190FC21が選ばれた場合の WAVE1 の理想のオオモノ湧きである。

n EventSeed SalmonType
1 BB05BB53 shakerocket
2 4792EF9F shakediver
3 941BFB5E shakediver
4 025215AD shakediver
5 EAD807F0 shakebomber
6 A152D0FE shakediver
7 333F4604 shakerocket
8 AC48AB3F shakebomber
9 9BAA4B68 shakebomber
10 526C317F shakecup
11 1365248E shakerocket
12 DA5DDC81 shakesnake
13 E989E97C shakeshield
14 C2B35FCE shakediver
15 F63E2ACD shakeshield
16 505AFF7F shakesnake
17 B295D7F5 shakebomber
18 B5FAEB28 shakeshield
19 32DC5BEF shakerocket
20 C1C7594C shakecup

さて、ここで中盤(5~9 のタイミング)にshakebomberつまりバクダンが三体ほとんど同時に湧くということに注意していただきたい。従来の考え方であれば代替オオモノ(以後、湧き変化とする)が発生するのはバクダンの場合は四体目の出現の瞬間 17 のタイミングであると考えられてきた。

しかし、そうであるならオオモノが変化するタイミングはバクダンの 17 のタイミングかコウモリの 19 のタイミングしかありえないことになる。

ところが、実際にプレイしてみると 10 番目のカタパッドがテッパンに変化するという現象が見られた、これは何故か?

代替オオモノの真の法則

それはオオモノ変化が起きるのは「四体目が湧こうとしたタイミング」ではなく、「あるオオモノが三体湧いている状態で何かしらのオオモノが湧こうとしたタイミング」だからである。わかりやすく言うと「例えばモグラが三体いる状態で何でもいいので何かオオモノが湧こうとしたのであればそのオオモノは理想状態の本来のオオモノから変化する可能性がある」ということになる。

このとき三体出現しているオオモノはモグラでなくてもカタパッドでもバクダンでもなんでもいい。何かのオオモノが同時存在数上限に達しているということがポイントになってくる。

代替オオモノは 100%ではない

三体上限に達しているからといって 100%変化するとは限らない、と思う。

代替オオモノアルゴリズム

では代替オオモノは何になるのかという問題が発生する。勘の良い方は「バクダンが詰まったときはカタパッドになりやすい」となんとなく感じているかもしれない。

そして、その予想は概ねあたっている。

526C317F が生成する乱数

ではどのようにしてカタパッドがテッパンに変化するのかを考えてみよう。

生成される乱数 ループ回数 オオモノシャケ ID 結果
0D608A2A 1 shakebomber 0
6BCFAD6F 2 shakecup 0
A7658FA3 3 shakeshield 1
7F1E175A 4 shakesnake 1
7626F349 5 shaketower 2
60948C37 6 shakediver 2
EB070F11 7 shakerocket 6

生成される乱数と演算結果は上の表のようになる。要するにチェックとクリアして出現することができるのはバクダンとカタパッドになるわけである。

そして実際に出現するのは最後にチェックをクリアしたカタパッドになっている。

詰まりが発生していないとき、ループ回数と乱数に掛けられる数は一致する。では、詰まりが発生したときはどうなるのだろうか?

これは代替オオモノアルゴリズムが完全に解明できていない以上、推測を多分に含むのだが「詰まっているオオモノに対しては乱数生成を行わず、チェックも行わない」という処理が入っている可能性が高い。

そのような処理がされていた場合、単にループと ID が乱数に対して一つズレるので以下のような結果になる。

生成される乱数 ループ回数 オオモノシャケ ID 結果
- - shakebomber -
0D608A2A 1 shakecup 0
6BCFAD6F 2 shakeshield 0
A7658FA3 3 shakesnake 1
7F1E175A 4 shaketower 1
7626F349 5 shakediver 2
60948C37 6 shakerocket 2

つまり、出現するオオモノがカタパッドからテッパンにズレるという現象が発生するわけである。

ここでポイントになるのはカタパッドを別のオオモノにズラすためにはカタパッドを四体湧かせるよりもバクダンを三体湧かせる方がてっとり早くて確実ということである。

オオモノは消滅しない

バクダンの出現するパターンが減った分だけオオモノが出現する確率が減ってしまいそうだが、そうはならない。この場合は全てのオオモノが 1/6 の出現率を持っていることになる。

このロジックで何故 100%出現するオオモノがズレないかというと、詰まっているオオモノ以下のオオモノは変化のしようがないからである。

例えばテッパンが詰まっても ID がそれより小さいカタパッドが何かに変化することはありえない。何故なら「本来カタパッドが出現した」という状態は「カタパッドが最後の計算結果が 0 になるループ」である必要があるためだ。テッパンが詰まっているとテッパンのループがスキップされて使われる乱数が変化するがそれはカタパッドの計算結果に何の影響も及ぼさない。カタパッドの計算はすでに終わっているからだ。

例外的にコウモリだけはバクダンに変化しうる。コウモリだけが計算結果が 0 の場合にはループ回数が一回減ってしまってその結果がなくなってしまうのでデフォルト値であるバクダンに変化する。

ただし、このコウモリからバクダンの変化は 100%起こるものではない。何故なら、例えばタワーとコウモリの計算結果が 0 の場合にはコウモリが出現するのだが、このときループの消滅が発生した場合にはコウモリの計算結果が消滅して「最後の計算結果 0 のオオモノ」がタワーになるため、コウモリがタワーに変化すると考えられる。

最後に

オオモノ代替のアルゴリズムはまだ完全には解明されていない。

最初にも述べたが、この考察は多分に推測を含んでいる。

ただ、それでも四体目で変化するというのは誤りだし、必ず一つ後ろのオオモノに変化するというのも誤りである。

今後この手の解析が進むかどうかはぼくの気分次第、そして解析にとりくむ諸君らの気分次第である。

記事は以上。s

    えいむーさんは明日も頑張るよ © 2022