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

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

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

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

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

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

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

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

これは正しい情報です。

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

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

これは誤った情報です。

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

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

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

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

詳しくは後述します。

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

これは誤った情報です。

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

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

詳しくはこの記事を読んでください。

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

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

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 は以下の表の通りの意味を持ちます。

EnumRawValueName
shakebomber0バクダン
shakecup1カタパッド
shakeshield2テッパン
shakesnake3ヘビ
shaketower4タワー
shakediver5モグラ
shakerocket6コウモリ

シード値#

アルゴリズム解説にあたって、サーモンランの 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になります。

::: tip WAVE1 における WaveSeed

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

:::

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

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

GameSeedWaveSeed(Wave1)EventSeed(1st)
7190FC217190FC21BB05BB53

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

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

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

計算結果が 0 になる確率#

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

n計算結果が 0 になる確率
1100.00%(1/1)
250.00%(1/2)
333.33%(1/3)
425.00%(1/4)
520.00%(1/5)
616.66%(1/6)
714.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 で割った結果(整数)
110EA9D330
23C73485C0
3FE6FC9D92
440C692841
504819CAA0
6A21605CE3
722B731540

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

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

EventSeed と出現するオオモノ#

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

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

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

nEventSeedSalmonType
1BB05BB53shakerocket
24792EF9Fshakediver
3941BFB5Eshakediver
4025215ADshakediver
5EAD807F0shakebomber
6A152D0FEshakediver
7333F4604shakerocket
8AC48AB3Fshakebomber
99BAA4B68shakebomber
10526C317Fshakecup
111365248Eshakerocket
12DA5DDC81shakesnake
13E989E97Cshakeshield
14C2B35FCEshakediver
15F63E2ACDshakeshield
16505AFF7Fshakesnake
17B295D7F5shakebomber
18B5FAEB28shakeshield
1932DC5BEFshakerocket
20C1C7594Cshakecup

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

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

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

代替オオモノの真の法則#

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

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

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

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

:::

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

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

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

526C317F が生成する乱数#

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

生成される乱数ループ回数オオモノシャケ ID結果
0D608A2A1shakebomber0
6BCFAD6F2shakecup0
A7658FA33shakeshield1
7F1E175A4shakesnake1
7626F3495shaketower2
60948C376shakediver2
EB070F117shakerocket6

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

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

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

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

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

生成される乱数ループ回数オオモノシャケ ID結果
--shakebomber-
0D608A2A1shakecup0
6BCFAD6F2shakeshield0
A7658FA33shakesnake1
7F1E175A4shaketower1
7626F3495shakediver2
60948C376shakerocket2

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

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

::: tip オオモノは消滅しない

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

:::

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

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

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

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

最後に#

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

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

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

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

記事は以上。s

同時存在数上限による代替オオモノの誤解と見解
https://fuwari.vercel.app/posts/2021/11/bossappear/
Author
tkgling
Published at
2021-11-08