かけらの記録ノート

かけらの記録ノート

主にポケモンの乱数について語るブログ

【XD乱数】ダミーIDによる色回避処理と新しい消費調整手段についての提案

この記事は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

見ての通り、この処理は結構頻繁に呼ばれるので、実際に処理に組み込む上では無視できないレベルで誤差を生みます。


これはたぶん、どのツールでも考慮されていなかった処理だと思うので、ちゃんと実装したい人は参考にしてみてください。

めんどくさくなってきたのでまとめると、これ便利だと思うので、みんなもつかってみてね!ってことで。
これをXD乱数自動化に組み込みたいと思っているが、それぞれの消費行動を正しい順番で動作させるのが面倒そうだったのと、モチベがつきてしまっていたのでまだできてません。
あとぺぇそんだと計算が遅すぎてカップ焼きそばがふやけてしまうので、C++に実装し直したいが、それも面倒。ChatGPTの有料版が中々イケてるらしく、試してみたいので誰かトークンをください。

【7世代乱数】仲間呼び乱数調整用のツールを作った

この記事はPokémon Past Generation Advent Calendar 2023 8日目の記事です。
adventar.org

時は遡ること6月


ついにサイファーさんが不可能だと思われていた実機での仲間呼び(SOS)乱数を成功させました。すごい。
元々は海外で解析が進んでおり、改造した3dsを用いるかエミュなどで内部値を覗くことで乱数調整が可能な状況でしたが、総当たりを行うことでSOSシードの特定を行ったり、ポケモンの瞬きを観測して消費数の特定を行うことで実機のみでも乱数調整が可能なことが分かりました。

仕組み自体は確立できたものの、しかしながら乱数調整の手順が煩雑であったり、元となっている海外製のツールだけでは対応しづらい部分もあったので、サイファー氏からコードをお借りして、実機での乱数調整に特化したツールを作成したのでここで公開させていただきます。

drive.google.com
・既知のバグ
瞬きのfpsが正しくないことによる経過時間の微妙なズレ
SOSシード検索時の低呼び出し率(bcr3)に非対応
やる人がいるならいつか更新します

詳しいやり方は開拓者であるサイファー氏にまとめていただきました。読んでいただくと分かりますが、(6世代ほどじゃないけど)とても複雑です。

私の方でも簡単に補足しておきますが、まず言えることは、この乱数調整では色理想個体の捕獲がほぼ不可能に近い難易度になっています。
仲間呼びによる連鎖では4Vまでの生成が保証されるのになぜこのようなことが起こるのかというと、Vが自然発生した場合はそのVを含んで4Vまでが上限となるように再計算が発生するためです。つまり、本来連鎖による補正で通常の生成から「V-A-B-V-V-V」のようにHCDSがVの個体が生成されるところ、例えばBが自然発生でVになると、本来4つのVが生成されるところが3つになり、「V-A-V-C-V-V」のような置き換えが発生します。クソ野郎。
そのため、純粋な5V以上を狙う場合は連鎖の補正を抜きにした自然発生で5V以上の生成が起こるシードの厳選が必要になります。とても苦行です。
仲間呼び乱数における理想値としては、あえてSの個体値を落とす必要のあるポケモンや1か所がUで妥協するのが限界と言えます。マジでなんでこんな生成にしたのか謎なんだが。

私のツールでは例として以下のようなポケモンを育成しておくことを推奨します。
・PP管理用のオーロット

・連鎖および消費数特定用のドーブル

・最後の消費数調整用のドーブル

・シンクロかつ自身で眠り状態になれるポケモン

・(バフ用のドーブル

・(デバフ用のドーブル

最後の2匹は補助要員で、連鎖の安定化やゴーストタイプなどのみねうちが入らないポケモンに使います。
通常のポケモンの場合なら大抵この並びでいけると思いますが、反動技持ちであったり、目が見にくいポケモンの場合だと別に専用のポケモンを育成する必要があります。

このようにとても面倒な部類の乱数なので、昨今のバンクのサ終が迫っている中、どうしても欲しい個体がいる人くらいにしかおすすめできません。

明日の記事はFRLGメソッドズレ野生乱数についてだそうです。私も何度か挑戦したことはありますが、ただのメソッド4を狙うくらいなら簡単にできるので、個体の幅を広げられるのは魅力的ですね!

【SV S8 最高&最終レート2030】人でなしループ【最終208位】

  • はじめに
    私が最後に本気でレートに挑戦したのはUSUMの頃だった。その時の最高レートも2000と少しである。そこから剣盾が発売され、ランクマッチに名前が変わった後も多少潜ったが、ダイマックスというシステムに適応できなかったため、しばらく対戦からは離れてポケモン乱数の解析などを主に行っていた(このブログの趣旨はそういった内容である)。長くポケモン対戦から離れていたが、SVではダイマックスに代わってテラスタルという新しい要素が発表され、以前の環境では勝てなかった自分でもなんとかなるのではないかと思い挑戦してみた結果、そこそこの戦績を残すことができたと思うので、場違い感は否めないと思うが今回はじめて構築記事なるものを書いてみようと思った。
    今回、ランクマッチに挑戦するに当たり、構築記事などをまとめて参考にさせていただいた受けルーパーの方々、ランクマッチでマッチングし、並びを参考にさせていただいた受けルーパーの方々、その中でも私が構築をパクった大きく影響を受けたTN : Nadir氏に感謝をするとともに、この構築記事を投稿したい思う。

 

  • 受けループという構築について


無駄な御祈りなんかせったら
涙を誘うものなんか かなぐりすてろ
まア一杯いこう 好いことばかり思出して
よけいな心づかいなんか忘れっちまいな
  
不安や恐怖もて人を脅かす奴輩やから
みずからの作りし大それた罪におび
死にしものの復讐ふくしゅうに備えんと
みずからの頭にたえず計いを
  
―――「人間失格」よりルバイヤットの詩句

現環境で受けループを使っていて感じたのは、やはり運要素が極めて高いことである。相手にしていて厄介なのがで、どうしても受けにまわる以上、(零度は論外として)技の追加効果や急所による運ゲを避けることができない。

また、こちらの決定打となるメインウェポンが地割れなため、どうしても運に左右されてしまう部分がある。そこで今作の受けループでは、いかにこちらの運が良くなるように立ち回れるのかが重要だと感じた。具体的には、地割れの試行回数を稼ぐようなプレイングを心がけることや、できるだけ相手の追加効果を引いても受けが崩れないようなHP管理を行うことが大切である。

冒頭の引用は、原文の意味合いとは異なると思うが、今の受けループを端的に表していて面白いと思ったので書きました。カッコイイ!

太宰はあまり読んだことないですが、私は人間失格よりも斜陽の方が好きです。

 

  • 使用構築

当初は@3という構築を使っていたが、レート1900あたりから中々勝つことができずにいた。そこで当たった受けルーパーの方が抜きの構築を使用していたため、それを参考にして少し構築をいじってからそこそこ勝てるようになった。具体的には、レート1900から2000まで無敗で上がることができたり、サブロム2つでレート2000から勝ち上がることができました。

自分の使っていたの型が悪かったせいかもしれないが、そもそも現環境ではのような並びが多く、や相手のも増加していたため、どくびしの刺さりが弱く、毒を刺すことができる機会が少なかった。また、上手い人は大抵どくびしに刺さらないようなプレイングをしてくる。そこで、この枠を水に強いに変えたところ、うまくはまり、そこそこ勝てるようになりました。私が参考にした並びでは、の枠がでしたが、選出率がとても低かったため変更しました。

ヒトデちゃんは熱湯が解禁されたら使ってあげるから待っててね。

  • コンセプト

純正受けループ。

  • 個体解説

このブログは乱数調整を推奨、促進するためのものなので、使用個体の乱数情報についても軽く解説してゆきます。

FRLG自然エンカウント乱数産リボンコンプラッキー(全角7文字NN) 31-7-31-31-31-31 かんばりや🔷

326(4)-11-57(252)-55-172(252)-70 テラス @

地球投げ/瞑想/ステルスロック/卵産み

すべての特殊を受ける枠。こいつで受からないとNNの通り死にます。への打点としてシャドボが欲しくなる場面も多かったが、それよりサイクルで有利に立ち回ることができるステロの方が刺さる場面が多く、技構成はこれで結論だと思っています。

アグレッシブなヘイラッシャ♀ 性別&雰囲気色証厳選

257(252)-121(4)-183(252)-x-85-55 テラス @

地割れ/雪雪崩/守る/眠る

アグレッシブに地割れを打つポケモン。相手の交換読みや、のはねやすめに合わせて積極的に地割れを狙っていく。一時期、守るの枠を寝言で採用していたが、HP管理やこだわり確認などに有用な守るが正解な気がします。テラスを切るタイミングが重要で、相手の悪に対して安直に切ってしまうと、その後の切り替えしのテラスで破壊されることがあるので注意が必要です。

あわてんぼうなミミズズ♀ 性別&雰囲気色証厳選

177(252)-x-216(252)-x-76(4)-85 テラス @

ボディプレス/鉄壁/ステルスロック/眠る

こいつの色厳選をした経験がある人なら分かると思うが、画面いっぱいにこいつが湧くのを見るのがキモ過ぎる。

受けのポケモン剣舞を積まれたりすると無理やり突破されてしまうこともあるが、裏と合わせてなんとか11交換まで持っていけるように立ち回る。以外にもにも鉢巻持ちのテラバも受かるので、汎用性は悪くない。

BW野生乱数産リボンコンプゴチルゼルinマスボ (混合12文字NN)31-0-31-31-31-31🔷

175(236)-54-149(164)-115-138(60)-91(44) テラス @

トリック/癒しの波動/いちゃもん/眠る

このパーティの地雷枠。こちらの陰キャポケモン達をいじめてくる陰キャポケモンを嵌め殺す最強の陰キャポケモンポケモン実況者のリドルさんが昔使っていたような気がする。努力値はスカーフ込みで最速抜き抜きまでSに振っているが、後になって考えたら振る意味がないかもしれない。Dに少し振っているのはのあくは意識です。役割対象は意外と広く、受けループで採用されるようなメンツに加えて、や型によってはも見ることができる。役割対象をキャッチした時点でほぼ勝ちが決まる。スカーフトリックを決めた後に、相手がPP16以上の技を打ってくれば、いちゃもんでPPの節約をしながら相手の悪あがきと癒しの波動による嵌めにより、丁度相手のPPが切れるタイミングまでにTODに持っていける。また、相手のHPが奇数の場合なら、PPが切れた後でも眠った後の癒しの波動が間に合うため、問題なく嵌めることができる。ふきとばされたりテラスで逃げられたりしても、最悪スカーフを押し付けることができればそれだけでその後の展開をある程度有利に立ち回ることができるので腐りにくい。基本相手のパーティーに役割対象が2匹以上いる場合にしか選出しないが、選出できた試合は大体勝つことができた。

なにもかんがえてないドオー♀ 性別&雰囲気色証厳選

227(172)-106(84)-80-x-167(252)-40 テラス @

地震/毒づき/毒々/自己再生

お気持ちだけの受け。Aに84振ることで、地震でH252を87.5%で持っていける。特殊受けとしての数値が足りないため残りをHDにぶっぱしているが、それでも数値が足りていないため、クッションとして利用することが多かった。相手のボルチェンの一貫を切り、裏に毒を入れるのが仕事です。おんみつマントを持たせているが、相手にがいる場合はを積極的に投げていたため、あまり役にたっていない...

BW野生乱数産リボンコンプモロバレルinマスボ (混合NN)31-0-31-31-31-31🔷

221(252)-81-134(252)-105-101(4)-50

キガドレイン/ヘドロ爆弾/キノコの胞子/光合成 テラス @

従来の受けループにおける殴れるドヒドイデ枠。トリック受けとして相手にくろいヘドロを押し付けてスリップダメージで倒したり、零度との再戦時には先発に出して分からせたりしていた。ヘドロを渡した後は、こちらのに渡らないようにうまく立ち回る必要がある。に強いのも〇。ボルチェンに安定して後出しできるのも強い。テラスは相手に何度か切ることがあったので無駄ではなかったです。技構成は参考にさせていただいた型をそのまま使ったが、ギガドレの枠をじだんだにしてもいいかもしれない。

  • 基本選出

先発のの守るから入り、相手の型を把握しながら柔軟に立ち回る。

・相手にがいる場合

を何とかして残りを2匹で詰める。

の役割対象が2匹以上いる場合

でサイクルを回しつつ、役割対象をでキャッチすることを目標にして立ち回る。こちらの2匹がどれだけ削れていようが、HPさえ残っていれば相手の1匹を嵌めるだけでTOD勝ちできる。

・低レートや相手が特殊に厚い場合

(順不同)

を無理やりにでも11に持っていき、残りの2匹で詰める。高レートのは大体挑発型ではないと思ったので、単体で見るようにしていた。

  • 最終順位

久しぶりかつ今作初めてのランクマでしたが、そこそこの成績を収めることができたと思うので満足です。欲を言えば100位台で終わりたかったですが、サブロムを溶かしていたため、これ以上潜る勇気はありませんでした。最終日の夜6時で140位台だったのでなんとかなるかなぁ程度に思っていましたが、甘かったみたいです。目標であったレート2000を達成できてしまったのでしばらくはランクマには潜らないでしょうが、もし次に挑戦する機会があれば最終100位台を目標にしたいです。

ここまで長い文章を読んでいただきありがとうございました。初めての構築記事なので、おかしな部分があっても許してね。

【7世代乱数】瞬きを利用した乱数調整

この記事はPokémon Past Generation Advent Calendar 2022 23日目の記事です。
温めていたやつを公開します。
adventar.org

7世代のフィールド上では、常に高速で消費が起こっており、これが瞬きの判定に使われていることは有名です。

サンムーン固定乱数仕組み : ただの雑記byさき

今までに確立された乱数調整では、瞬きの消費を考慮して待機時間の計算を行い、目標の個体とエンカウントする、という方法でした。しかし、このやり方では、複数npcがいる場合だと正確に目標を狙うことができないという欠点があります。これは、フィールドに遷移するときの不定消費によって乱数がずれるため、フレーム当たりの瞬きの順序が不定になるためです。この辺の説明は難しいので、解説サイトに丸投げします。
pokerng.forumcommunity.net
このずれは、直接瞬きを観測することによって解消することができます。瞬きの順序を考慮した消費の系列(TimeLine)を特定することで、引くことのできる個体を正確に予測ができるようになります。ただし欠点があり、エンカウント直前にカットインが入る固定シンボルは、不定消費を挟むため、その処理を考慮しなければ正確な計算ができません。3DSRNGtoolで考慮されてたかわ忘れました!されてたら問題ない。そもそもnpc1ごときで瞬きを使う必要性がないが。
ついでにTimeLeapという概念について説明します。ダイバージェンス1%で時をかけそうな雰囲気がありますが、だいたいその通りで、TimeLineの系列の間に不定消費を挟むことで、目標のTimeLineに到達する手法のことを指します。要はTimeLineの調整です。

そんな感じで、瞬きを考慮して乱数調整を行うことができるツールを作ったので、主な使い方について説明します。

  • 7genBlinkSearch

drive.google.com

2022 12-23

  • 後悔

今回は例として、npcが4である配達員乱数を行っていきます。

  • 準備

ゲーム内では、オハナタウンのポケモンセンターで所定の場所でセーブしておきます。主人公の瞬きが見えることが重要です。

  • TimeLineの特定

初期シードを求めたあと、従来の7世代乱数と同様にQRスキャンに入って針から消費数を特定します。消費数が分かったら、ツールの一番左の枠に入力できる「シード」と「消費数」にここで求められている初期シードと消費数を入力しておきます。

真ん中左の瞬きの設定に移ります。検索範囲はデフォルトのままで良いです。これは上で指定した消費数から瞬きの観測を始めるまでの待機フレームの範囲を表しています。デフォルトでは余裕を持って10000Fとしていますが、さらに短くても構いません。
ゲーム内でQRスキャンを閉じてフィールドに戻ります。そして、配布ポケモンを受け取る直前の画面まで進めてから主人公の瞬きを観測していきます。

下の「開始」ボタンをクリックして、主人公が瞬きをするのと同時に「shiftキー」を入力して瞬きを記録します。ここらへんはCoと同じ。7世代では2回連続で瞬きをする場合がありますが、これも1回とカウントしてください。
うまく入力ができたら、「Timelineを計算」をクリックします。うまく入力ができていればResultが表示されます。

ここでうまく結果が出ない場合は、npc数の設定が間違っている場合があります。入力ミスがないか確認するか、もう一度特定し直してみてください。また、入力する瞬きが極端に少ない場合、間違った結果を表示する場合があります。余裕を持って入力してください。

  • オフセットの特定

まず初めに、Coでおなじみのオフセットを求めます。仕組み的には大体同じで、空白時間を考慮する意味合いです。
真ん中右のタイマーの設定に進みます。まずは、求めたTimelineが正しいかを検証します。「目標の消費数」を適当な数字に設定して右のタブに進み、「計算」を押します。そうするとタイマーが有効になるので、「BlinkTimer」を起動します。ここでタイマーの音と主人公の瞬きが一致していれば正しいTimelineを求められていることが分かります。

タイミングの調整を行ったら、そのままタイマーが終わるまで待機して、終わると同時に配布ポケモンを受け取ります。
※ツール上で正しいfpsが設定できていないため、長時間待機すると微妙にずれます。なのでなるべく同じ待機時間でタイミングの調整を行って再現性が取れるようにしてください。
受け取った配布ポケモンの消費数を3dsRNGtoolなどを使って検索します。ここでDelayの設定は無効にしておいてください。
ここで出た消費数とツール上の計算結果の一番下に表示された消費数の待機時間の差がオフセットです。※Timelineの関係上、目標の消費数で設定した消費数と同じものになるとは限りません。これを調整するのがTimeLeapと呼ばれる手法になります。
下に進んで、オフセット検査の項に「目標の消費数」->「実際に出た消費数」となるように入力して計算をクリックします。正しく入力されていれば結果が出ます。私の環境では、なんと「0」でした。ほんとか?

こちらのオフセットはCoとは違い、猶予時間は存在しないので1でもずれると失敗します。何度か求めて安定する値を探してください。また、展開があるポケモンだと正しいオフセットを求めるのが困難なので、V固定のないポケモンでオフセットを求めることをおすすめします。
オフセットが安定したら③に進みます。

  • TimeLeap

事前にツール上の「ふしぎなカード」から受け取る配布ポケモンの情報を入力しておきます。※これが正しくないとうまくTimeLeapを行うことができません。

①の項で配達員からポケモンを受け取る直前ではなく、そのひとつ前の画面でTimelineを特定します。

理想個体を行う場合などは、事前に大量消費を行った状態で消費数を入力しておいてください。
うまく求めることができたら、真ん中右の枠で「TimeLeap」を選択して、目標の消費数に欲しい個体の消費数を入力します。※ここで注意として、現在の消費数から目標の消費数までの間隔が短いとうまい具合に候補が出ないことがあります。ツールの操作中も待機が行われていることを考慮して、少し長めにしておくと良いです。
そして計算をクリックします。そうすると、計算結果に消費数が並んだものが出力されると思います。これがTimeLeapを行うことができる消費数です。ここで、消費数がなるべく連続しているところを狙います。これがいわゆる展開のようなもので、猶予フレームになります。npcの活性化状態にもよりますが、前後で4消費未満の消費数が多く連続しているところを探し、その中間あたりを選択したら、右クリックから「消費数を選択する」を押します。

そして、うまく選択した消費数までの計算結果が表示されたら、BlinkTimerでその消費数まで消費を行います。ここでオフセットは事前に求めたものを入力しておいてください。
タイマーが終わると同時にAボタンを入力して画面を進めます。

ここで再度Timelineの特定を行います。そして、目標の消費数を設定して検索を行い、計算結果の最後がうまく目標の消費数と一致していればTimeLeapの成功です。もし一致しなかった場合は待機時間のずれ、もしくはふしぎなカードの設定ミスが考えられます。
一致したら、そのままタイマーが終わると同時にポケモンを受け取ります。目標の個体なら成功です。
sina-poke.hatenablog.com

今回は配達員乱数での説明を行いましたが、この手法が完全に理解できれば他の固定シンボルにも応用することができると思います。検証してくださった有志の方によると、タイプ:ヌルやサトピカ、べベノムなどの乱数調整が簡単になるらしいです。あとついでに、御三家もできるらしい。
このツールはもともと配達員専用みたいな想定で作っていたのですが、いろいろ応用が利くと分かって驚きました。でも、TimeLeapは配達員にしか対応していないので注意してください。

この仕組みをすんごく応用すればSOS乱数が実機でできる可能性があります。調べてないので分からんけど、仕組みはCoの連続戦闘をそのまま使えそう。今のところ興味がないし大変なので調べないが、誰かやってみてはイカがでしょう?

24日目の記事はイカゲームの乱数調整についてだそうです。ギアの乱数調整などが話題になっていたようですが、さらにそこから進歩したとのこと。バイトしかやってないにわかなのであまりありがたみを感じられませんが、仕組みはとても気になります。バイトにもギアを実装してくれ...

【6世代乱数】フィールド上でのTinyMTの制御

この記事はPokémon Past Generation Advent Calendar 2022 22日目の記事です。
adventar.org
6世代では、個体値や性格値の生成にはMTが使われていることは有名ですが、それ以外の目立たない部分(野生ポケモンのスロットやシンクロ、ポケモンの瞬きなど)ではTinyMTによる乱数が使われています。今までの固定乱数でシンクロの制御ができなかったのはこのためだったんですね。
larvesta10.hatenablog.com

元々、TinyMTの制御自体はID調整を行うために必要でしたが、今回は実際にゲーム本編のフィールド上で行うことを目的としました。ID調整ではトレーナーの瞬きによって内部状態の特定を行いましたが、今回はポケモンの瞬きを利用します。
ポケモンの瞬きの時間は、TinyMTの乱数値から次のように求められます。

blink = (RNG.Advance(1) * 240) >> 32 + 250

乱数の初期化には、内部時間のミリ秒によって変動する32bitの値が与えられます。ここはMTと同じです。ID調整と同様に、瞬きの時間を測定し、総当たりをすることで基準シードを特定します。
一度、基準シードが特定できれば、時間からおおよそのシードを逆算することが可能です。大きく消費を行ったあとは、同様にしてポケモンの瞬きから現在地を特定できます。消費の微調整にもポケモンの瞬きを使います。瞬き再強!瞬き最強!

TinyMTを制御することで、今まで考慮することができなかった様々な要素を乱数調整できるようになりました。

とても夢が広がりますね!仕組みを解析してくれた海外勢に感謝。

今回作成したツールでは、図鑑ナビのみ対応しました。仕組みはTinyFinderを参考にしています。
github.com
こちらのツールではほぼすべてが網羅されているので、開拓意欲のある人は使って挑戦してみてください。私のツールでも気が向いたら実装すると思います。

  • 6genBlinkSearch


drive.google.com
2022 12-13 v0.12

  • リリース

使い方などは、13日目のサイファーさんの記事にまとめられていて、とても分かりやすいです!(丸投げ)ここまで書いて力尽きたので、あとはあなた自身で体験してみてくだるしあ。

つづきまして、23日目は世代が一つ進みます。やはり瞬きは最強。

【チャンネル乱数】タイトルデモを利用した乱数調整

この記事では、ポケモンチャンネルでの乱数調整における新しい消費方法について提案します。

・はじめに
今までのポケモンチャンネルにおける乱数調整では、消費の微調整にタイトル画面の表示による消費が使われていました。
ジラーチを受け取るまでの過程で、必ずタイトルを経由しなければならないため、乱数調整を行うにあたってはこの消費を考慮する必要があります。
どのような仕組みかは以前に記事に書きましたが、再計算を伴う処理になっているため、シードに展開が現れるようになります。(これは受け取ることのできるジラーチの個体の範囲を狭めていたりしてとてもごみカスです。)
さらに、この再計算自身によって通常の契機では理論上到達不可能な個体が存在してしまいます。それらは「ペイント」による消費によってのみ到達可能でした。しかし、ペイントを使って消費を行うと、シードが固定値によって初期化されてしまうため、現実的な時間で調整することは困難です。

そこで、ポさんがタイトルデモを見ることで消費が発生することを発見しました。
これはタイトル画面の表示による消費とは独立して消費が行われるため、再計算を回避して目標のシードにたどり着くことが可能です。これを調査し実用化に成功したので、この記事で簡単な仕組みとやり方について紹介します。

・デモ消費について
ポケモンチャンネルでは、タイトル画面で放置することによって、本編で見ることのできるチャンネルのデモが流れます。これを見ることで、固定または不定値の消費が発生します。

見るチャンネルによって消費数は異なり、中にはセーブデータ依存による処理も含まれます。これを再現するためには各自で調査を行う必要があります。
セーブデータの進行度によって流れるチャンネルの種類が変わり、ジラーチを受け取ることのできるクリア済みのデータでは合計10種類のデモが存在します。
それぞれの説明を以下に示します。

・チャンネルデモの種類
・PNNN
コダックがニュースを紹介する番組です。

消費数は固定値で、2回に分けて合計4消費されます。

・ゼニゼニショッピング
ゼニガメが商品のプロモーションを行う番組です。

このチャンネルではセーブデータ依存の不定消費が起こります。
消費に組み込むには独自に調査が必要ですが、無視しても大体の個体には影響はありません。
これについては、初期化直後のセーブデータを使うことで回避することができます。ただし、その場合だと閲覧可能なチャンネルが制限されるため、消費の自由度が下がります。

item_list = []
while(len(item_list) < self.roll):
    shop = (RNG.Advance(1) >> 16) * 0x4 >> 16
    if(shop == 3):
        while(len(item_list) < self.roll3):
            item = (RNG.Advance(1) >> 16) * 0x5b >> 16
            frame += 1
            if(self.Item3[item].get() and  item not in item_list):
                item_list.append(item)
        break
    else:
        item = (RNG.Advance(1) >> 16) * 0x5b >> 16
        frame += 1
        if(self.Item[item].get() and  item not in item_list):
            item_list.append(item)

上のコードに含まれるroll、roll3、item、item3がセーブデータ依存で変化します。
これらは初期化直後のデータでは、固定値となります。

    self.item = [0x17,0x18,0x19,0x1b,0x1c,0x24,0x2c,0x3b,0x44,0x45,0x46,0x47]
    rand_list = []
    while(len(rand_list) < 2):
        r = (RNG.Advance(1) >> 16) * 0x5b >> 16
        if(r in self.item and r not in rand_list):
            rand_list.append(r)

こちらでは商品の種類による分岐が起こらず、値も固定です。
簡略化していますが、上のコードと同じ内容です。

ムチュール★エクササイズ
ムチュールがエクササイズする番組です。

消費数は固定値で、2回に分けて合計2消費されます。

・クイズソーナンス
ソーナンスによるクイズ番組です。

消費は不定で、以下の処理が行われます。

rand_list = []
while(len(rand_list) < 3):
    r = (RNG.Advance(1) >> 16) * 0x17 >> 16
    if(r not in rand_list):
        rand_list.append(r)

ドーブルびじゅつかん
ドーブルによるお絵描き番組です。

消費は不定で、以下の処理が行われます。

rand_list = []
while(len(rand_list) < 3):
    r = (RNG.Advance(1) >> 16) * 0x37 >> 16
    if(r not in rand_list):
        rand_list.append(r)
rand_list = []
while(len(rand_list) < 2):
    r = (RNG.Advance(1) >> 16) * 0x23 >> 16
    if(r not in rand_list):
        rand_list.append(r)
rand_list = []
while(len(rand_list) < 2):
    r = (RNG.Advance(1) >> 16) * 0x3d >> 16
    if(r not in rand_list):
        rand_list.append(r)
RNG.Advance(1)

ヤドン天気予報

ヤドンによる天気予報の番組です。ゲーム内で見ることによってフィールドの天候が変わります。
このチャンネルは特殊で、初回に見るときと2回目以降に見るときでアニメーションの内容が異なり、消費も変化します。
また、セーブデータ依存でフィールドが異なり、フィールドによっても消費が変わります。

ただしこちらはゼニガメのように細かい場合分けは起こらず、4種類存在するフィールドによってのみ契機が変わるため、大掛かりな調査の必要はありません。
現在2つのフィールドにのみ対応しています。
※これは、デモにおけるフィールドの変更契機が不明なためです。何を契機としているかは分かりませんが、ゲーム内でチャンネルを見たり、日を跨いだりすることでまれに変わるようです。調査の必要があります。
・モエギそうげん初回
消費数は固定値で、2回に分けて合計2消費されます。
・モエギそうげん2回目以降

sleep = (RNG.Advance(1) >> 16) * 5 >> 16 == 0
if(sleep):
    RNG.Advance(1)
RNG.Advance(1)

・スオウかいがん初回
消費数は固定値で、2回に分けて合計4消費されます。
・スオウかいがん2回目以降

RNG.Advance(1)
sleep = (RNG.Advance(1) >> 16) * 5 >> 16 == 0
RNG.Advance(1)
if(sleep):
    RNG.Advance(1)
RNG.Advance(1)

2回目以降では、アニメーション内でヤドンが寝ているかの判定が行われていて、寝ていた場合だと追加で1消費が起こります。

・タマゴなにかな?
ラッキーによるタマゴの中身を当てる番組です。

消費数は固定値で、3消費されます。

ポケモンあまるかな?
画面に映るポケモンの数が奇数か偶数かを当てる番組です。

消費は起こりません。

メリープぼくじょう
牧場を駆け抜けるメリープの数を数える番組です。

このチャンネルもヤドンと同様に、初回と2回目以降で消費が変わります。
・初回
消費数は固定値で、3消費されます。
・2回目以降
消費数は固定値で、10回に分けて11消費されます。
また、冒頭の2消費目の乱数値によってアニメの開始時に寝ているポケモンが変わります。

RNG.Advance(1)
poke = "ヤドン" if RNG.Advance(1) >> 31 == 0 else "カビゴン"

・ムチュムチュランキング
ムチュールによる格付け番組です。

消費は起こりません。

以上の10種類のチャンネルデモが存在していて、チャンネル開始直前のノイズが走る画面でr(10)により決定されます。
ただしこの時、同じ番組が被らないように、直前のチャンネルと同じ場合は再計算が起こります。これを意図的に利用することでシードの調整の幅が広がります。
また、チャンネルデモは5回流れると、間に再度アニメーションを挟みます。これはチャンネルデモを中断した場合でも継続されるため、実際に消費を行うときに注意する必要があります。
分かりにくいので例を挙げてみます。
通常ではデモを5回見るとその次の5回の間にアニメーションを挟みます。しかし、デモを2回見て一度タイトルに戻った場合、次にデモを3回見るととアニメーションが流れます。
デモの番組の調整を行う場合は5回ごとに区切って行うと効率が良いです。本番の調整を行う場合は、タイマーを考慮してデモが5回流れる状態にしておく必要があります。

・初期化済みのセーブデータについて
初期化済みのセーブデータを用いることで、セーブデータ依存の処理を固定させることができます。よって、調査の必要なしにゼニガメを消費に組み込むことが可能です。
クリア済みのデータとは別のメモリーカードを用意して、そちらに初期化直後のデータを作成します。
開始直後のデータとなるので、消費に使えるチャンネルの数が10から4つに減ります(上から4つ)。消費の計算の自由度が多少下がるため候補が少なくなると思われますが、こちらではゼニガメによる計算エラーがなくなるため、あまり影響はありませんでした。
計算結果にもよりますが、乱数調整を行う際は安定性の面からも初期化済みのデータを用いて行うのを推奨します。

ここで、セーブデータを作成する際の注意点について述べます。
「New Game」でゲームを開始した直後にアニメが流れます。

その後、会話を進めていくとセーブを要求されます。そこで20$もらえます。

この段階ではまたセーブデータが固定されないので注意してください。
もう少し話を進めると、ピカチュウがやってきます。そして、博士から「Note Pad」が貰えます。

この段階で自由に行動ができるようになるため、ここでセーブをします。ここでセーブをして初めて固定が完了となります。
ここで作成した初期化済みのメモリーカードを利用することで、ツール上で「初期化済み」にチェックを入れた状態の消費を行うことができます。
ただし、このデータではジラーチを受け取ることができないため消費にのみ利用して、実際に受け取る場合にはメモリーカードを差し替える必要があります。

・乱数調整のやり方
ここまでの仕組みを利用して、実際に乱数調整を行ってみます。
ツールにはDDJCとChannelDemoSearch v0.90を使用します。
drive.google.com

①目標個体の検索
DDJCを用いて、狙う個体の開始時シードを求めます。

求めたシードを「ジラーチ検索」タブのシード欄に入力し、計算をクリックします。

初期化済みと通常の場合を検索して、猶予時間が長い方を使用してください。出力された結果を見ながらその通りにセットアップを行っていきます。直前に見たチャンネルを事前に指定されたものになるようにして、必要であればヤドンメリープをあらかじめ視聴しておく必要があります。
うまく結果が表示されない場合は、デモによって到達できない個体となります。その場合は、ゼニガメのセットアップを行うか、ヤドンのフィールドを変えることで結果がヒットする場合があります。

②チャンネルのセットアップ
まず初めに、「スペシャル検索」タブで最初に流れるチャンネルを決定します。
上でチャンネルの決定方法について説明しましたが、ゲーム内ではあらかじめ直前に見たチャンネルが保存されており、次にチャンネルを視聴する際には同じものが流れないように再計算されるようになっています。
ゲームを始めた直後の場合、直前のチャンネルは初期シードから1消費された乱数値から決定されます。タイトル消費の仕様によって、これをあらかじめ予測することは困難です。
ただし、一度もデモを見ていない場合に限り、スペシャルでの消費を行うことで再設定が可能です。
スペシャル検索タブに現在のシードと適当なチャンネルを入力し、検索をします。

結果に出力された回数タイトル消費を行い、その後スペシャルによる消費を行うことで直前のチャンネルを任意のものにすることができます。

ヤドンメリープが未視聴で良い場合はこれでセットアップは完了です。
そうでない場合は「チャンネル検索」タブで必要なチャンネルを検索して、細かい調整を行っていきます。
ヤドンもしくはメリープの視聴が必要な場合は、ここで検索しながら視聴します。

事前に上で適当に直前のチャンネルを決定しておき、それを入力します。そして、見たいチャンネルを選び、検索をします。
指定した数タイトル消費を行った後、もう一度タイトルに戻って放置することで目標のチャンネルが流れます。目標のチャンネルが終わり、ノイズが始まると同時にタイトルに戻ることで、終了時シードの状態になります。
これを繰り返して再生回数が合計で5回となるまでに必要なチャンネルを視聴します。ここで、最後の5回目に流れるチャンネルを指定されたものになるようにします。
ヤドンメリープの指定と最後に見るチャンネル以外はどのチャンネルを見ても問題はありません。

⓷タイトルシードの調整
現在のシードが指定された開始時シードの直前になるまで消費を行います。
「シード調整」タブを利用して、必要回数のタイトル消費を行うことで調整を行います。
ここで、目標となる開始タイトルシードの一つ手前になるまで消費を行います。

上の画像でいうところの、Continue→NewGame→Option→Specialと表示され、シードが0xc2fdaaf2になる状態まで消費を行います。
次のタイトルの表示で開始タイトルシードに達する状態です。ここまで準備ができたら、Optionに入った状態で待機します。
これですべてのセットアップが完了です。

④デモの視聴とタイマーの調整
ジラーチ検索」タブの「DemoTimer」を起動します。

そして、ゲーム内でBボタンを押してタイトルに戻ると同時にEnterキーを押すことでタイマーを起動します。
タイマーの譜面に合わせてゲーム内のBGMが流れるように左右矢印キーでタイミングの調整を行います。
初期化済みのデータでデモを再生している場合は、デモが始まるまでの間にクリア済みのデータが入っているメモリーカードに差し替えます。
タイトルに戻った時にデータの再読み込みが行われるため、デモが流れている間は初期化済みのデータを参照するため問題はありません。
タイマーの譜面はデモが始まる直前まで続きます。その後、デモのアニメーションと消費に合わせて譜面が流れます。
ここで注意なのですが、冒頭のアニメーションが終わる時間が不定で、およそ半分の確率で50F早く終わることがあります。よって、デモの開始が50F早まることがあります。
原因は不明です。フシギダネに祈りましょう。もし早く終わった場合はrshiftキーを押すことで大体の調整を行うことができます。余裕がある場合は、さらにそこから左右矢印キーで微調整を行ってください。
DemoTimerは「リスト表示」タブで練習できるので、適当なシードで使用感を試しておくと良いかもしれません。

ジラーチを受け取る!!!
タイマーが終了すると同時にSTARTボタンでタイトルに戻ります。ここでタイトルの表示順が目標個体の終了時表示順と一致しているかを確認してください。一致していればおそらく成功です。そのままジラーチを受け取ってください。


これらの手順は事前に大量消費を行ったうえで、従来の受け取り調整の段階で行うことを想定しています。ゲーム内で余計な行動をしたり、セーブデータを上書きしてしまうとツール通りの結果が出力されない場合があるので注意してください。

ここからはおま〇けです。
ゼニガメのセットアップ
ゼニガメの消費は、未初期化データではセーブデータ依存で変わるため、独自の調査が必要です。
その調査を補助する機能が「ゼニガメ検索」タブになります。ゼニガメを視聴した後のチャンネルから消費数を絞り込み、内部値の推論を行います。
まず初めに現在シードを入力して、検索します。

指定した回数タイトルを見たあとにデモを流し、指定された再生回数のときにゼニガメが流れます。
検索結果をダブルクリックすることでシードが選択されます。
そしてデモを見た後、ゼニガメの直後に流れる番組を2つ記録します。そしてデモを5回見終わったらタイトルに戻ります。
「商品を検索」をクリックして、記録したチャンネルを上から入力し、計算をクリックします。

いくつか結果が出ることがありますが、その中で商品の数はある程度少ないものが正しい結果です。
ほとんどないと思いますが、もし同じような結果が複数出た場合はスルーするようにします。
途中でチャンネルの再計算が発生した場合はうまく結果が出ないことがあるので注意してください。
うまく結果が出たら、右クリックメニューから「シードを選択する」をクリックします。
計算の途中でゼニガメによるエラーが起きていなかった場合、自動でシード欄に現在のタイトルシードと、直前のチャンネルが入力されます。
その後に、履歴に追加するか聞かれるので、「はい」を選択します。ここで仮想的な内部状態の更新がされます。
これらを繰り返すことで、内部状態の絞り込みを行っていきます。

ここで、内部状態にあたる商品リストを見てみましょう。

ここで、青であらわされているのが確定した商品です。緑であらわされているのは不確定だが視聴済みの商品です。赤であらわされているのが未視聴の商品です。
これらをすべて青にすることでゼニガメを完全に扱えるようになります。すべて埋めなくても、埋めた分だけエラーの数を減らすことができます。
緑のものは、商品の個数が確定した後に「再計算」を行うことで青になることがあります。

内部状態の絞り込みを行っていくと、途中で個数が確定します。まずは、これを目標に進めていきます。
個数が確定した場合は、ツールの「再計算」ボタンを押すことで、一部の不確定の商品を確定させることができます。
すべての商品を埋めるためには、おそらく100回以上ゼニガメを視聴する必要があります。


およそ30回の試行で品目4の個数が確定

とても非人道的なのでおすすめしませんが、どうしても結果が出ない場合などあれば挑戦してみてください...

【XD乱数】いますぐつないで

「いますぐバトル」と「つないでバトル」に入った時の生成処理です。

ポケモンXDが流行ってるらしいので調べた。
今回は日本版で調査したので、逆に欧州だとうまく動かない可能性があります。おそらく大丈夫だと思われるが...
なお、表裏IDが主人公のを参照してるかは調べてません。

def EnterQuickBattle(RNG):

    RNG.Advance(122)

    for i in range(4):
        tid = RNG.Advance(1) >> 16
        sid = RNG.Advance(1) >> 16

        for j in range(2):
            RNG.Advance(5)
            GeneratePID(RNG, tid, sid)
            GenerateEV(RNG)

    return RNG

def EnterConnectBattle(RNG):

    RNG.Advance(122)

    tid = RNG.Advance(1) >> 16
    sid = RNG.Advance(1) >> 16
    for j in range(3):
        RNG.Advance(5)
        GeneratePID(RNG, tid, sid)
        GenerateEV(RNG)
    generate_list = [
        ["♂1♀1","♂","わんぱく"],
        ["♂1♀1","♂","わんぱく"],
        ["♂3♀1","♂","わんぱく"]
    ]
    for j in range(3):
        RNG.Advance(5)
        pid = GeneratePID(RNG, tid, sid, genderRatio=generate_list[j][0], gender=generate_list[j][1], nature=generate_list[j][2])

    tid = RNG.Advance(1) >> 16
    sid = RNG.Advance(1) >> 16

    RNG.Advance(5)
    pid = GeneratePID(RNG, tid, sid)
    GenerateEV(RNG)

    generate_list = [
        ["-","-","むじゃき"],
    ]
    RNG.Advance(5)
    pid = GeneratePID(RNG, tid, sid, genderRatio=generate_list[0][0], gender=generate_list[0][1], nature=generate_list[0][2])

    generate_list = [
        ["-","-","のんき"],
        ["♂1♀1","♂","うっかりや"],
    ]
    tid = RNG.Advance(1) >> 16
    sid = RNG.Advance(1) >> 16
    for j in range(2):
        RNG.Advance(5)
        pid = GeneratePID(RNG, tid, sid, genderRatio=generate_list[j][0], gender=generate_list[j][1], nature=generate_list[j][2])

    tid = RNG.Advance(1) >> 16
    sid = RNG.Advance(1) >> 16
    for j in range(2):
        RNG.Advance(5)
        pid = GeneratePID(RNG, tid, sid)
        GenerateEV(RNG)
    return RNG

いますぐバトルは単純に4×2匹の性格値と努力値を生成してるだけでした。
つないでバトルは所々で性格が固定されてたり努力値が生成されたりして謎です。
いみわからん生成してるせいで調査が少し大変だった。

「つないでバトル」内で、設定の変更によるセーブによる消費と名前選択時の不定消費が使えることが分かりましたが、何かに使えるかは分かりません。
対戦プロファイルの名前を変えるときと、メモカに保存するときに発生します。

日本版ではタイトルでの2消費が使えないことによって消費の微調整が面倒でしたが、これらを使えばちょっとはマシになるんじゃないでしょうか。
XDは色回避処理のせいでモチベがないので、ツールの作成はしません。興味のある人は参考にして実装してみてください。

以下使用した汎用関数です。

def GetGender(pid, ratio):
    r = pid & 0xFF
    if(ratio == "-"):
        g = "-"
    elif(ratio == "♀" or
        (ratio == "♂1♀3" and r <= 190) or
        (ratio == "♂1♀1" and r <= 126) or
        (ratio == "♂3♀1" and r <= 62) or
        (ratio == "♂8♀1" and r <= 30)):
            g = "♀"
    else:
        g = "♂"
    return g

def GetNature(pid):
    n = pid % 25
    return NATURE[n]

def ShinyCheck(pid, tid, sid):
    return tid ^ sid ^ (pid & 0xFFFF) ^ (pid >> 16)

def GeneratePID(RNG, tid, sid, isshiny=None, gender=None, genderRatio="-", nature=None):
    while(True):
        hid = RNG.Advance(1) >> 16
        lid = RNG.Advance(1) >> 16
        pid = (hid << 16) | lid

        if(gender is not None):

            if(GetGender(pid, genderRatio) != gender):
                continue

        if(nature is not None):
            if(GetNature(pid) != nature):
                continue

        if(isshiny is not None):
            if(not isshiny):
                if(not ShinyCheck(pid, tid, sid) < 8):
                    return pid
                continue
            elif(isshiny):
                if(ShinyCheck(pid, tid, sid) < 8):
                    return pid
                continue
            else:
                return pid
        break

    return pid


def GenerateEV(RNG):
    ev = np.zeros((6), dtype=np.uint8)
    for j in range(101):
        sumev = 0
        for k in range(6):
            ev[k] += (RNG.Advance(1) >> 16) & 0xFF
            sumev += ev[k]
        if(sumev <= 510):
            return ev
        else:
            if(j >= 100):
                continue
            ev.fill(0)
    return ev