この記事はPokémon Past Generation Advent Calendar 2023 70日目の記事です。
scrapbox.io
最新版のXD乱数では「いますぐバトル」でシードの特定および調整を行うのが主流になっています。
この「いますぐバトル」でのポケモンの生成には、生成されるポケモンは色違いにならないような色回避処理が施されています。この処理の中で、ポケモンの生成の前に生成されるダミーIDが色回避に用いられると考えられていましたが、実際はそうではなく、セーブデータ中のトレーナー自身のIDで色回避処理が走っていることが判明し、一部の界隈で話題になりました。セーブデータがない場合は、起動時の1000消費後の乱数値からIDを生成しているらしいです。
このせいで、シードを正しく計算するにはセーブデータ中のトレーナーIDの情報が必要になります。ただし、無視したところで該当するのはごく少数のシードのみのため、あんまり考慮する意味はないと思われました。
現状の消費の調整手段は、上記のいますぐバトル内で最強を選択したときに生成される消費を用いて行うものですが、これに加えて、以前私が調査した内容であるところの「いますぐバトルに遷移するときの消費」および「つないでバトルに遷移するときの消費」を用いることで、さらに自由度の高い消費の調整が実現できるので、その宣伝をしてゆきたいです。
ポケモンXDでは、ゲームを開始する前の段階で再現可能な消費を行う手段は主に4つあると思われます。※欧州版では5つ
・いますぐバトル内で最強を選択したときに起こるポケモンの生成による消費
・いますぐバトルの画面に遷移するときの消費
・つないでバトルの画面に遷移するときの消費
・設定変更によるセーブでの固定値の40消費
・※最初から始めるを選択したときの固定値の2消費
今までは最強でのポケモンの生成と設定変更の40消費を組み合わせて消費を行っていましたが、これだと一部のシードはえげつない回数の設定変更を強いられ、非常に非人道的です。
そこで提案したいのが、いますぐバトルおよびつないでバトルに遷移するときの消費を組み合わせてさらに消費の自由度を上げる方法になります。
こちらだと、どのシードでも割と人道的な範囲の設定変更で消費の調整が可能です。実装コードは汚すぎて見せられないよ。
試しに適当なシードを入力するとこうなります。
最強のみを消費に使うと、最短でも最強×58+設定が1458回必要になりますが、こちらの手法だと設定が17回にまで抑えられました。
欧州版だとさらに楽々ちんちんになります。
ただ、これを手動でやろうとすると絶対にどこかでミスるので、自動化を推奨します。
実装にあたって簡単な注意ですが、最強による消費は事前の消費契機が「いますぐ」もしくは「最強」でないと使えません(あたりまえだが)。「最強」の消費を行うためには「いますぐ」を経由しないといけないので「つないで」や「設定」は使えないんですね(1敗)。
さて、この画像の中でfound 0xbeefbeefと表示されているのを目撃したと思いますが、何を見つけたのか気になりますよね?
さらなる調査の結果、通常のトレーナーIDでの色回避に加えて、ダミーIDでの色回避も発生していることが分かりました。
これは通常の色回避処理の中に組み込まれているのではなく、一連のすべてのポケモンの生成が終わったタイミングで再度ダミーIDでの色判定処理が走り、これに引っかかるとダミーIDの再生成が行われます。
これを疑似コードとして実装したのが次のコードになります。
def GenerateParty(RNG, tid, sid): hp = [0 for i in range(4)] team = [0 for i in range(2)] RNG.Advance(1) #player team[0] = (RNG.Advance(1) >> 16) % 5 #cpu team[1] = (RNG.Advance(1) >> 16) % 5 RNG.Advance(1) #cpu dummytid = RNG.Advance(1) >> 16 dummysid = RNG.Advance(1) >> 16 generate_pids = [] for i in range(2): RNG.Advance(2) hiv = (RNG.Advance(1) >> 16) & 31 RNG.Advance(2) pid = GeneratePID(RNG, tid, sid) generate_pids.append(pid) ev = GenerateEV(RNG) hp[i] = math.floor(ev[0] / 4) + hiv #""" for pid in generate_pids: dummytid, dummysid = GenerateDummyID(RNG, pid, dummytid, dummysid) #""" RNG.Advance(1) #player dummytid = RNG.Advance(1) >> 16 dummysid = RNG.Advance(1) >> 16 generate_pids = [] for i in range(2): RNG.Advance(2) hiv = (RNG.Advance(1) >> 16) & 31 RNG.Advance(2) pid = GeneratePID(RNG, tid, sid) generate_pids.append(pid) ev = GenerateEV(RNG) #print(ev) hp[i+2] = math.floor(ev[0] / 4) + hiv #""" for pid in generate_pids: dummytid, dummysid = GenerateDummyID(RNG, pid, dummytid, dummysid) #""" return team, hp def GenerateDummyID(RNG, pid, dummytid, dummysid): while(ShinyCheck(pid, dummytid, dummysid) < 8): dummytid = RNG.Advance(1) >> 16 dummysid = RNG.Advance(1) >> 16 return dummytid, dummysid
見ての通り、この処理は結構頻繁に呼ばれるので、実際に処理に組み込む上では無視できないレベルで誤差を生みます。
最強は4回しかpidを生成しないから起こりにくいが、いますぐは8回、つないでは12回も生成を行っているから、その分2、3倍誤差が起こりやすくなる
— 空 (@kakerando) 2023年12月4日
これはたぶん、どのツールでも考慮されていなかった処理だと思うので、ちゃんと実装したい人は参考にしてみてください。
めんどくさくなってきたのでまとめると、これ便利だと思うので、みんなもつかってみてね!ってことで。
これをXD乱数自動化に組み込みたいと思っているが、それぞれの消費行動を正しい順番で動作させるのが面倒そうだったのと、モチベがつきてしまっていたのでまだできてません。
あとぺぇそんだと計算が遅すぎてカップ焼きそばがふやけてしまうので、C++に実装し直したいが、それも面倒。ChatGPTの有料版が中々イケてるらしく、試してみたいので誰かトークンをください。