MHXXの護石スナイプをArduino Leonardoで自動化 + 待ち時間短縮の新手法の提案
2026年3月初頭にMHXXにおいて護石のスナイプ手法が確立された (情報が一般に公開された).護石(お守り)のスナイプとは,消費乱数の調整(乱数調整)によって,マカ錬金で出る護石を調整する手法である.これは,護石のパラメータの決定が疑似乱数 (Xorshift) に基づいており,かつ乱数の初期seed値がソフトによらず固定であることから実現可能となっている.乱数の消費は,1フレームごとに1消費される(他にも消費要因がある)ので,手動でもゲーム起動からマカ錬金投入までの時間を調整することで原理上は可能である.
しかし,1フレーム単位での操作精度を要求され,試行回数を増やせば解決できるが,手動では容易ではない.ただ,できないわけではなく,乱数消費量が小さい場合に限り,手動での成功者も多数存在する.あくまで手動は安定しないということである.スナイプを安定させるために,このノートではマイコン (Arduino Leonardo) によるNintendo Switch1/2 + MHXXの制御方法について紹介を行う.また,ノートの末尾に待ち時間短縮の新手法を紹介する (continue連打法).精度が荒く,安定した手法として確立されていないが,自分はこの手法で痛撃6達人10S3を短時間 (5分程度)で入手した.またこの手法は護石スナイプをしない方々にとっても利点となるだろう.なお,このノートは痛達理論値を確実に得るようにするものではないことは申し添えておく.
誤解されやすい点を先に言っておこう.まず,マイコンを使用してボタン押しのタイミングを完璧に制御しても最終的には少し運が絡む.とはいえ1/10~1/60程度の確率になるので十分にマイコンを使用する利点はある.X上での痛撃6達人10S3の「スナイプでの」入手者は,確実にマイコンを使用しているであろう.
また,任意の護石を(それこそセーブデータの改造のように)得られるわけではなく,スナイプが実現可能な時間(消費乱数,フレーム数)の範囲にある護石が得られるだけである(だけといっても十分であるが).このため,ブラキ炭鉱をして抽選回数を増やすことで,目当ての護石を得る確率を上げる手法が無意味になるわけではない.
なお,自分自身はゲーム自体の解析は行っておらず,他の人によって公開された情報から推定しているだけであるので,内容の誤りがある可能性が高いことは注意してほしい.また,有名人のXでの投稿を見る限り,より進んだ手法(だが公開されていない手法)が存在する可能性が十分にある.基本的には以下の記述を参照した.
このノートは情報量が多く (LLMでの推敲も行っていないため) 読みにくいと思われる.目当ての情報を探す場合はChatGPTやGeminiなどのLLMにこのノートのURLを渡し,質問をするのがよいだろう.
護石スナイプの是非について
初めに護石スナイプに対する筆者の考えを述べておく.護石スナイプ(乱数調整)を改造と類似するものとして扱う人も多くいるが,両者は決定的に異なる.一番の違いは,内部データの書き換えがないため,セーブデータを破損させる心配がないということである.ここでの「セーブデータ」は自分のものだけでなく,オンライン通信した際に共に遊ぶプレイヤーのものも含む.また,MHというゲームは対人戦 (PvP) ではなく,モンスターに対するPvEであるので,これらからスナイプで得られた護石を使用しても他のプレイヤーに一切悪影響が無い (心理的悪影響はあるかもしれないが).TAなどでは同一の装備を使用できるようになれば,純粋な腕前で競争ができるようになるだろう.
なお,護石の乱数調整が可能なMHとしてはMH3GやMH4 (G) などもあるようだ.乱数調整と改造の比較や同一視などに関しては,MH以外ではポケモン(こちらはPvPなので,乱数調整が容易であった5世代では特に問題となった)などでも長い論争の歴史があるので,それらも調べて総合的に判断を行ってほしい.筆者としては,乱数調整と改造は上述の理由などから明確に異なると言っておく.
次に,ゲームのプログラムを解析することの是非についてである.これに関しては護石スナイプに関して公開された情報が,実際どのように得られたかは不明であるとしか言えない.地道な実験から推定された可能性も微粒子レベルには存在する.また,広く利用されているサイト(実名は挙げない)として,肉質や報酬確率,個体のサイズ確率など,ゲーム内で得られない情報を公開しているものもある.ゲーム自体の解析を行っていると仮定すれば,両者は大差ないであろう.もちろん,攻略本などソフトメーカーからの情報を元にしている可能性もあるので,断定はできない.
最後に,改造との区別が付きにくいという懸念点だが,これは同意する.護石だけではなく,ハンター名,チャットの発言,挙動(通常ではありえない動きやゲームの進行が正常でないなど),護石以外の装備(例えばHR解放前でGXミラルーツ装備など)などの情報を合わせて判断し,疑わしければ罰するの精神でキック+ブロックして対処を行えばよいだろう.
フレーム数と護石の対応
フレーム数について
この記事で指すフレーム数とは,実際は乱数消費量である.1フレーム (MHXXは30 fpsなので1/30 s ≈ 33 ms) に1つ消費するので便宜上フレーム数と呼んでいるが,ロードなど他の要因でも消費される (後述).このため,フレーム数がそのままゲーム選択から護石投入までの時間と等しくなるわけではない.
機種やゲームの保存場所(パッケージ版かダウンロード版か)によって使用されている初期乱数のseed値は変化しないため,任意の環境で護石のスナイプは可能である.ただし,ロード時間が異なり,小さいフレーム数の護石 (1716 framesの斬味5 痛撃4 S3など) は最速でも間に合わない可能性があり,また進行するフレーム数の安定性から,基本的にはSwitch2 + ダウンロード版を推奨する.
スナイプの一番の難点は,ゲーム開始におけるロード時の乱数消費量が一定ではないという点である (消費量は概ね 700 ± 30のようだ).このため,マイコンで同じ制御を行っても結果が必ずしも同一ではない.よって,複数回施行したフレーム数の平均値で,指定した待ち時間を評価するべきである.
フレーム数と護石の対応の解析
誤解がないように初めに言うと,ここでの解析はゲーム自体のプログラムに対して行うものではない.筆者はapmnnn氏によって https://github.com/apmnnn/mhxx-rng で公開されているコード(関数)を利用してフレーム数と護石の対応についての解析を行っている.コード (mhxx-rng.ipynb) はPython 3.xで書かれており,ライブラリとしてはNumPy, Numbaが必要.誰でも利用可能なようにGoogle Colabで使用できるようにコードが記述されている.Google ColabはGoogleのサーバー上でコードを実行できるサービスであり,自分のパソコンにPython環境を入れる必要がない.しかし,取り回しが不便なので,筆者はlocalのPython環境でコードを実行している.Pythonに関しては機械学習で主に使用されている言語であることからインターネット上での文書が充実しており(そのためLLM内の知識も豊富),本体およびライブラリのインストールに関してはここでは紹介しない.
再実行が多いので,筆者はJupyter note (.ipynbファイル) をJupyter Labから使用している.最初に実行すべきコード (mhxx-rng.ipynb の2-4セル目) を全てutils.pyという名称のファイルに入れ,以下のようにして変数を読み込んでいる.面倒なので特段コードの追記はしていない.
%run utils.py1セル目の使用法に関しては,https://github.com/apmnnn/mhxx-rngを参照してほしい.Pythonが少しは分かっていないと難しいかもしれない.また,見落としやすいが,護石の名称は,以下の略記を使用されている.
'毒 ','麻痺','睡眠','気絶','聴覚','風圧','耐震','だる','耐暑','耐寒',
'寒冷','炎熱','盗み','対防','狂撃','細菌','裂傷','攻撃','防御','体力',
'火耐','水耐','雷耐','氷耐','龍耐','属耐','火攻','水攻','雷攻','氷攻',
'龍攻','属攻','特攻','研師','匠 ','斬味','剣術','研磨','鈍器','抜会',
'抜減','納刀','納研','刃鱗','装速','反動','精密','通強','貫強','散強',
'重強','通追','貫追','散追','榴追','拡追','毒追','麻追','睡追','強追',
'属追','接追','減追','爆追','速射','射法','装数','変則','弾節','達人',
'痛撃','連撃','特会','属会','会心','裏会','溜短','スタ','体術','気力',
'走行','回性','回距','泡沫','ガ性','ガ強','KO','減攻','笛 ','砲術',
'重撃','爆弾','本気','闘魂','無傷','チャ','龍気','底力','逆境','逆上',
'窮地','根性','気配','采配','号令','乗り','跳躍','無心','我慢','SP',
'千里','観察','狩人','運搬','加護','英雄','回量','回速','効果','広域',
'腹減','食い','食事','節食','肉食','茸食','野草','調成','調数','高速',
'採取','ハチ','護石','気ま','運気','剥取','捕獲','ベル','ココ','ポッ',
'ユク','龍識','飛行','紅兜','大雪','矛砕','岩穿','紫毒','宝纏','白疾',
'隻眼','黒炎','金雷','荒鉤','燼滅','朧隠','鎧裂','天眼','青電','銀嶺',
'鏖魔','真紅','真大','真矛','真岩','真紫','真宝','真白','真隻','真黒',
'真金','真荒','真燼','真朧','真鎧','真天','真青','真銀','真鏖','北辰',
'斬術','食欲','職工','剛腕','祈願','裏稼','刀匠','射手','状態','怒 ',
'回術','居合','頑強','剛撃','盾持','潔癖','増幅','護収','強欲','対鋼',
'対霞','対炎','胴倍','秘術','護強'なお,2026/03/09時点では2個目のスキルが空白の場合にフレーム数を調べる機能が(関数の中身を確認したところ)ない.コードを追加で書くことも面倒なので2番目以降の2つスキルがある護石を調べてそのフレーム周辺の護石を確認することで1番目の護石のフレームを確認している.1番目と2番目の天の護石は経験上,多くの場合6フレーム離れている (稀に4フレームであることもある).
手動でやる場合の手順と考察
マイコンを使用せずに,手動でスナイプする場合も次節の「セーブデータの事前準備」と「Switch本体の事前準備」を参照のこと.また,「Arduinoのコード」に記載の操作も参考になるだろう.以下,手動でやる場合に結果に影響しやすい手順の考察である.
まず,最速起動・最速操作から1秒以下の猶予しかないが,斬味5 痛撃4 S3 (1716 frames) を狙うとよい.難しい場合は時点の逆上5, 達人9 S3 (4418 frames) などであろうか.
基準となる試行で得られた秒数に,フレーム数の差分を秒に変換したものを加算して,他の護石を狙う秒数を決定する.複数の護石セットを錬金に投入してもよいが,基本的に1セット目の1番目の護石に対応するフレームを参照するべきである.
乱数の決定時刻はゲーム選択後の暗転時からだが,ゲームのロード時間がほぼ一定である場合は,ゲーム選択と同時(要するに制御しやすいタイミング)にタイマーをスタートさせるとよいだろう.
待つ場所であるが,可能な限り「ゲームモードの選択」画面 (continueを押す前) がよい.短時間であればよいが,長時間の場合は村で時間経過以上のフレーム数が消費されるためである.これは経験則であるが,次のような情報もあり正しいと思われる.とはいえ,手動の場合はぎりぎりまで「ゲームモードの選択」画面で粘ることは難しいので,マカ錬金の投入画面で1-2秒残る程度に調整するとよいと考えられる.
タイトル画面では、次の場合を除き、時間経過以外で乱数は進みません。
タイトル画面で「continue」を押した時、700±30ほど追加で進みます。押す度に進みます。(つまり、タイトル画面で100万フレーム待つと、乱数はちょうど100万進む)
セーブデータをロードした時、追加で進みます。タイトル画面に戻ってロードする度に進みます。
村(ココット)では、初期位置で、1800フレームに1980〜1990ほど進みます。
Arduino Leonardoでの自動化
Arduino Leonardoと使用手順
ここからが本題である.Arduinoはマイコン(電子機器を制御する小型のコンピュータ)であり,Arduino Leonardoはその中でUSBを介した接続が可能なモデルである.このため,特別な機器を挟まずにPCとSwitchの双方に接続可能である.誤解する人など存在しないとは思うが,マイコンはかの悪名高いマジコンとは異なる.名前が似ているだけで,マイコンの使用自体は違法ではない.
事前の調整を除いた使用手順は,以下の2ステップである.
Arduino LeonardoとPCをUSB接続し,Arduino IDEを介してコード(操作)をArduino Leonardoに書き込む.
コードが書き込まれたArduino LeonardoをPCから外し,SwitchにUSB接続する.すると自動で書き込まれたコード(操作)が実行される.
マクロコントローラとの比較
Arduino Leonardoについて詳しく説明する前に,スナイプの際に使用するマイコン以外の選択肢として,マクロコントローラ (BIGBIG WON CHOCOなど) に触れておく.マクロコントローラはアプリやコントローラの操作などを介したマクロを設定することで,決められた操作を実行する機能を持ったコントローラである.これだけならマクロコントローラに利便性の点から軍配があがりそうだが,マイコンと比較すると,制御の安定性がやや劣り,連打などの処理を書くことが煩雑である.両方を使った経験から,5分以内のスナイプならマクロコントローラでも可能だが,それ以上の時間のスナイプを行う場合は安定性のためにマイコンを使用することを推奨する.Proコンが欲しい,など他の理由もある場合はマクロコントローラがよいだろう.他には,例えば筆者が所有している BIGBIG WON CHOCO はPCを使用せず,スマホアプリ(ただし,中華製で安全性にやや不安あり)から操作でき,USBをPCとSwitch間で接続しなおす手間が少ないという利点もある.
Arduino Leonardoの事前準備
Arduino Leonardoに話を戻そう.Arduinoはオープンソースハードウェアであり,純正品以外も販売されている.純正品が最も信頼できるが,安価な代替品を使用しても大きな問題はない.価格としては2000円程度で,通常のProコンよりも安価に購入できる.
筆者は以下の互換機を購入した.USBケーブルも付属しているが,PC/Switchに接続する側の端子はUSB Type-Aであることに注意してほしい.筆者は,Switchのドックを使用しないので,USB Type-AとType-Cの変換コードも購入した.
次に,Arduino Leonardoにコードを書き込むため,PCにArduino IDEをインストールする.ここから,Arduino LeonardoをSwitchコントローラーとして認識させるまでの流れは,主にポケモンの操作自動化を対象として様々な記事が作成されている.Switchの操作には NintendoSwitchControlLibrary を用いる.導入などに関しては,このライブラリを作成されたレフマーナ氏による以下の記事を参照してほしい.Arduino Leonardoの使い方を含め,画像付きで分かりやすい.
記事を参考にして,Switchへの入力がArduino Leonardoから制御できるようになれば,Arduino Leonardoのコード以外の事前準備は終了である.
以下では,後のコードに合わせてセーブデータとSwitch本体の事前準備を記載している.手動でやる場合も参考になると思われるので,一読してほしい.
セーブデータの事前準備
マカフシギ錬金術用の女王 (レア5), 王 (レア6), 龍 (レア7) の護石 (3つ以上) を用意.必要でないものを先頭にすること.十分に女王, 王の護石があれば問題ない.
持ち物にケルビの角 (3つ以上) を入れておく.
ココット村でセーブしてゲームを終了しておく.
必須ではないが,Switch本体の計算量を下げるため,オトモは連れない方がよいだろう.
装備に関しては任意で問題ないだろうが,計算量を下げるために裸+範馬刃牙双剣がよい可能性もある.
「本日の調査対象」などを決定するために乱数が消費される可能性もあるので,一度ゲームを開始して決定づけた後の方が良いかもしれない.同様に,検証していないが,5時・17時をなるべく跨がないようにした方が良いかもしれない.
Switch本体の事前準備
不用意な通信を避けるため,機内モードに設定する.
長時間待つ場合は設定からスリープで落ちないように変更する.待機時間に一定間隔で入力を入れる実装を行う場合は必ずしも必要ではないと思われる.
マイコンを使用する場合は設定から有線のProコントローラを認識できるようにする.ドックを使用しない場合,Switch2はUSB-Cポートが2つあるので,有線コントローラ接続と充電が同時に行えるという利点もある.
Arduinoのコード
筆者が書いたコードの全体の流れは以下の通りである.loopにならないように,setup関数内で全て実装する.保守管理を行うのであれば,動作ごとに関数を分けた方がよいが,面倒なので筆者はsetup関数内に全てべた書きした.
Switchのホーム画面でMHXXにカーソルを合わせているところから開始.
ゲームを起動し,「ゲームモードの選択」画面までAボタン連打.
「ゲームモードの選択」画面で希望の護石に合わせてフレームを消費する (コード内の変数 wait_ms を調整).待ち時間が10秒以上の場合は,コントローラーの接続が切れないよう,1周期10秒でXボタンを押す (詳細はコードを参照).待ち時間の消費後はAボタン連打する.
ココット村入口から開始.左斜め前にダッシュしてマカ錬金屋さんに話しかける.マカフシギ錬金術を選択し,1~3番目の護石を投入 (1セットのみ).
護石投入後,鑑定のため右斜め前にダッシュして,受付嬢から下位 Lv1「森の中のケルビ」を受注.左,上と移動して門からクエスト出発.
ケルビの角を納品.報酬は売却し,セーブせずに終了.
ココット村に戻ったら自宅へ移動.ルームサービスに話しかけて護石を確認する画面で停止.
ここからマイコンでの操作とコードに関しての補足である.
筆者のSwitch2にはメイン以外のアカウントがあるため,ユーザー選択が入る.このため複数ユーザーがない場合は確認していないので,Aボタン連打の回数を減らす必要がある可能性がある.
遅延 (delay) の時間は,本体機種やパッケージ版,ダウンロード版等で異なるため,これ通りでは上手く行かない可能性がある.また,筆者はSwitch2 + ダウンロード版を使用しているが,個体差(稼働時間による影響)などで前後する可能性もある.
護石投入時の乱数消費量(投入時までの経過時刻と同一ではないことに注意)が重要であり,それ以降は結果に影響しないので時間調整は適当でかまわない.マカ錬金の確認も自宅ではなくマカ錬金屋さんでよい.
護石投入時に2セット以降はズレが生じるので,手動でない場合は1セット投入して確認する方が,効率が良いと思われる.もちろん,待ち時間が長い場合には確率を上げるため,コードを改変し,for loopで複数投入してもよいだろう.
なお,以下のコードは恐らく安全(もし匿名の筆者を信用するなら)に書いたものであるが,悪意ある人がセーブデータや装備を削除する操作を実行するなど,不利益を与えるコードを書いている可能性もある.筆者のことは信用せず,初回の実行時にはArduino LeonardoとSwitch本体のUSB接続をいつでも外せるように,画面を見ながら異常時に備えてスタンバイしておこう.
筆者が暫定的に使用しているコードを以下に示す.数値の時間の単位は全てmsである.再三であるが,33 msecが概ね1 frameに対応する.待ち時間の変数 wait_msは1716 frames の斬味5 痛撃4 S3が筆者の環境で得られた 816 msに設定している.ただし,ロード時のランダムな乱数消費があるため,± 30フレーム,すなわち± 1000 msはずれる可能性がある.関数の説明はNintendoSwitchControlLibrary を参照してほしい.
// ライブラリを読み込むためのコード
#include <NintendoSwitchControlLibrary.h>
/*
待ち時間を設定するglobal変数.
*/
unsigned long wait_ms = 816;
// 待ち時間に一定間隔でボタンを押し,接続を維持する関数
void waitWithKeepAlive(unsigned long total_ms,
uint16_t button = Button::X,
unsigned long interval_ms = 5000,
unsigned long press_ms = 100)
{
// 待ち時間が10000 ms以下の場合は単にdelayを使う
if (total_ms <= 10000) {
delay(total_ms);
return;
}
unsigned long cycle = interval_ms; // 一周期の時間
unsigned long n = total_ms / cycle; // ボタン押しをする回数
unsigned long rem = total_ms % cycle; // 余りの時間
for (unsigned long i = 0; i < n; i++) {
SwitchControlLibrary().pressButton(button);
SwitchControlLibrary().sendReport();
delay(press_ms);
SwitchControlLibrary().releaseButton(button);
SwitchControlLibrary().sendReport();
delay(interval_ms - press_ms);
}
if (rem > 0) {
delay(rem);
}
}
// マイコンのセット時に1度だけ行われる処理
void setup(){
delay(50); // 書き込み猶予
// MHXXの選択から250 msごとにA連打し,「ゲームモードの選択」で止める
// 止まらない場合は連打数を36回から増減すること
pushButton(Button::A, 250, 36);
// ここで待機時間を決定する (最重要)
// wait_ms の間,10 sec以上待つならXを5 secごとに押す
waitWithKeepAlive(wait_ms);
pushButton(Button::A, 250, 4); // Continueを選択し,キャラクター選択までA連打
delay(9500); // ゲームのロード時の待機時間
SwitchControlLibrary().pressButton(Button::R); //Rを押し始める
SwitchControlLibrary().sendReport();
tiltLeftStick(100, Stick::MIN, 3200); //左上にダッシュ
SwitchControlLibrary().releaseButton(Button::R); // Rを離す
SwitchControlLibrary().sendReport();
pushButton(Button::A, 100); // マカ錬金屋さんに話しかけ
pushButton(Button::B, 250, 6); // 会話スキップ
pushButton(Button::A, 100); // 護石錬金
pushHat(Hat::UP); // マカフシギ錬金術を選択
pushButton(Button::A, 10);
pushButton(Button::A, 10); // 1番目の護石選択
pushHat(Hat::DOWN, 10);
pushButton(Button::A, 10); // 2番目の護石選択
pushHat(Hat::DOWN, 10);
pushButton(Button::A, 10); // 3番目の護石選択
pushButton(Button::A, 100, 2); // 護石投入
pushButton(Button::B, 100, 5); // 会話を終了する
// delay(1000);
// ケルマラに移行
SwitchControlLibrary().pressButton(Button::R); //Rを押し始める
SwitchControlLibrary().sendReport();
tiltLeftStick(Stick::MAX, Stick::MIN, 1500); // 右上にダッシュ
SwitchControlLibrary().releaseButton(Button::R);
SwitchControlLibrary().sendReport();
pushButton(Button::A, 250, 3); // 受付嬢に話しかけ
pushButton(Button::B, 250, 4); // 会話スキップ
delay(100);
pushHat(Hat::UP); // 下位クエストを選択
pushButton(Button::A, 100);
pushHat(Hat::DOWN); // Lv1を選択
pushButton(Button::A, 100);
pushHat(Hat::DOWN, 50, 3); // 森の中のケルビを選択
pushButton(Button::A, 50, 5); // クエスト受注
pushButton(Button::B, 250, 4); // 会話スキップ
//クエストに出発
SwitchControlLibrary().pressButton(Button::R); //Rを押し始める
SwitchControlLibrary().sendReport();
tiltLeftStick(Stick::MIN, Stick::NEUTRAL, 800); // 左に移動
tiltLeftStick(Stick::NEUTRAL, Stick::MIN, 2300); // 右に移動
SwitchControlLibrary().releaseButton(Button::R);
SwitchControlLibrary().sendReport();
pushButton(Button::A, 50, 5); // クエストに出発
delay(8700); // クエスト開始までの待機時間
pushButton(Button::PLUS, 250); //メニューを開く
pushButton(Button::A, 250, 2); //ケルビの角を選択
pushHat(Hat::DOWN, 50, 2); // 納品を選択
pushButton(Button::A, 250);
pushHat(Hat::RIGHT); // 上限の3つを選択
pushButton(Button::A, 250, 4); // 納品
delay(36000); // クエスト終了までの待機時間
//報酬売却
pushHat(Hat::UP);
pushButton(Button::A, 250);
pushHat(Hat::LEFT);
pushButton(Button::A, 250, 5);
pushButton(Button::B, 250); //セーブしない
pushButton(Button::A, 250);
delay(7900); // クエストから帰還するまでの待機時間
// ココット村自宅へ移動
pushButton(Button::X, 250);
pushButton(Button::A, 250, 2);
//マカ鑑定
delay(2000); // 自宅までのロード時間
tiltLeftStick(Stick::MAX, Stick::NEUTRAL, 700);
pushButton(Button::A, 250, 2); // ルームサービスに話しかけ
pushButton(Button::B, 250, 2);
delay(100);
pushButton(Button::A, 250);
pushHat(Hat::DOWN, 50, 3);
pushButton(Button::A, 250); // マカ錬金
pushHat(Hat::DOWN, 50);
pushButton(Button::A, 250, 2); // 鑑定
}
// ここに記述した内容がループされ続ける
void loop(){
}Arduino LeonardoがPCから認識されない場合
本筋とは関係ないが,コードの書き込み途中にUSBを引き抜くなどしてしまい,Arduino LeonardoがPCで認識できなくなった (1敗).この場合,認識されていない状態で空ファイル (setup, loop関数の中身がないファイル) を書き込み開始し,その直後にArduino Leonardo本体のリセットボタンを2回押して認識させて書き込むことで復旧可能であった.少し補足すると,Arduino Leonardo本体のリセットボタンを2回押してから5秒程度は書き込み待機状態(ブートローダーモード)になる.書き込み待機状態にしてからArduino IDEを操作するのは間に合わないので,先にArduino IDEを操作してから書き込み待機状態にする.
待ち時間短縮の新手法 (continue連打法)
最後に待ち時間短縮法について紹介する.以下にapmnnn氏がさらっと書いていることが発想の元となっている.
タイトル画面で「continue」を押した時、700±30ほど追加で進みます。押す度に進みます。
さらっと書いていいことではない.要するに,continueを押すだけで 700 frames ≈ 23 secに相当するフレームを進めることができるのである.キャラクターモデルの読み込みがあるため,これだけ消費されるのだろう.これは繰り返し行えるので,ゲームモード選択とゲームデータ選択をA, B, A, B…と連打して行き来すれば短時間で乱数を大幅に消費できる.
この記載内容が正しいか確認するため,対照実験を行った.
次のように「A 100 ms 押す」→「100 ms 待機」→「B 100 ms 押す」 → 「100 ms待機」の計400 msを一周として繰り返すコードを作成した.
for (int i = 0; i < 20; i++){
SwitchControlLibrary().pressButton(Button::A);
SwitchControlLibrary().sendReport();
delay(100);
SwitchControlLibrary().releaseButton(Button::A);
SwitchControlLibrary().sendReport();
delay(100);
SwitchControlLibrary().pressButton(Button::B);
SwitchControlLibrary().sendReport();
delay(100);
SwitchControlLibrary().releaseButton(Button::B);
SwitchControlLibrary().sendReport();
delay(100);
}ここでは20回繰り返す場合を提示している.これを(無関係なキー入力は行うが)決まった時間待つだけのwaitWithKeepAlive関数と置き換える.同じ時間だけ消費するが,continueを連打する試行としない試行 (対照試行) で出る護石からフレーム数を比較した.
まず,continue連打を20回行った場合,8000 ms = 8 secかかる.結果は以下の通り.
連打試行:15252 frames (特攻 5 装速 6 S0 RARE10)
対照試行:1952 frames (底力 2 ◯◯---- S1 RARE8)
1回の消費フレームの平均値は (15252 - 1952) / 20 = 665 frames であった.これは,apmnnn氏が記載の700 ± 30 framesに整合する.
次に,continue連打を400回行った場合,160000 ms = 160 secかかる.結果は以下の通り.
連打試行:287329 frames (護石 6 真燼 3 S2 RARE10)
対照試行:6507 frames (加護 6 ◯◯---- S1 RARE9)
1回の消費フレームの平均値は (287329 - 6507) / 400 = 702 frames であった.大数の法則から,平均値は収束するため,continueの連打回数を増やすことは結果の安定化に繋がる (もう少し正確に言えば,独立同分布のサンプル平均の分散はサンプル数に反比例することを利用する).また,MHXXにおける需要がトップレベルに高い,痛撃6 達人10 S3 が296260 framesであり,2時間44分待機しなければならなかったところを,3-5分程度に短縮することができる.
痛達理論値の入手
実際に研究を進め,ゲームモードの選択画面で事前に30000 ms待機し,continueを410回連打 (400 ms周期),再度ゲームモードの選択画面で33360 ms程度待った後に準最速でマカ錬金を入れることで痛撃6達人10S3を入手することができた.
マカ錬金への投入時刻はゲーム開始から4分17秒であった.2時間44分待たなければいけなかった従来手法と比較して圧倒的な時間の短縮となった.ただし,同じコードを自前で再度実行しても,やはりランダムな乱数消費により安定はしない.以下に自分が使用したコードをそのまま示す.
// ライブラリを読み込むためのコード
#include <NintendoSwitchControlLibrary.h>
/*
continue回数を設定する変数と
待ち時間を設定するglobal変数.
*/
unsigned long num_continue = 410;
unsigned long wait_ms = 33360;
// 待ち時間に一定間隔でボタンを押し,接続を維持する関数
void waitWithKeepAlive(unsigned long total_ms,
uint16_t button = Button::X,
unsigned long interval_ms = 5000,
unsigned long press_ms = 100)
{
// 待ち時間が10000 ms以下の場合は単にdelayを使う
if (total_ms <= 10000) {
delay(total_ms);
return;
}
unsigned long cycle = interval_ms; // 一周期の時間
unsigned long n = total_ms / cycle; // ボタン押しをする回数
unsigned long rem = total_ms % cycle; // 余りの時間
for (unsigned long i = 0; i < n; i++) {
SwitchControlLibrary().pressButton(button);
SwitchControlLibrary().sendReport();
delay(press_ms);
SwitchControlLibrary().releaseButton(button);
SwitchControlLibrary().sendReport();
delay(interval_ms - press_ms);
}
if (rem > 0) {
delay(rem);
}
}
// マイコンのセット時に1度だけ行われる処理
void setup(){
delay(50); // 書き込み猶予
// MHXXの選択から250 msごとにA連打し,「ゲームモードの選択」で止める
// 止まらない場合は連打数を36回から増減すること
pushButton(Button::A, 250, 35);
delay(100);
pushButton(Button::A, 250);
// ここで待機時間を決定する (最重要)
waitWithKeepAlive(30000); // 事前の待ち時間
// continue連打
for (unsigned long i = 0; i < num_continue; i++){
SwitchControlLibrary().pressButton(Button::A);
SwitchControlLibrary().sendReport();
delay(100);
SwitchControlLibrary().releaseButton(Button::A);
SwitchControlLibrary().sendReport();
delay(100);
SwitchControlLibrary().pressButton(Button::B);
SwitchControlLibrary().sendReport();
delay(100);
SwitchControlLibrary().releaseButton(Button::B);
SwitchControlLibrary().sendReport();
delay(100);
}
waitWithKeepAlive(wait_ms); // 事後の待ち時間
pushButton(Button::A, 250, 4); // Continueを選択し,キャラクター選択までA連打
delay(9500); // ゲームのロード時の待機時間
SwitchControlLibrary().pressButton(Button::R); //Rを押し始める
SwitchControlLibrary().sendReport();
tiltLeftStick(100, Stick::MIN, 3200); //左上にダッシュ
SwitchControlLibrary().releaseButton(Button::R); // Rを離す
SwitchControlLibrary().sendReport();
pushButton(Button::A, 100); // マカ錬金屋さんに話しかけ
pushButton(Button::B, 250, 6); // 会話スキップ
pushButton(Button::A, 100); // 護石錬金
pushHat(Hat::UP); // マカフシギ錬金術を選択
pushButton(Button::A, 10);
pushButton(Button::A, 10); // 1番目の護石選択
pushHat(Hat::DOWN, 10);
pushButton(Button::A, 10); // 2番目の護石選択
pushHat(Hat::DOWN, 10);
pushButton(Button::A, 10); // 3番目の護石選択
pushButton(Button::A, 100, 2); // 護石投入
pushButton(Button::B, 100, 5); // 会話を終了する
// delay(1000);
// ケルマラに移行
SwitchControlLibrary().pressButton(Button::R); //Rを押し始める
SwitchControlLibrary().sendReport();
tiltLeftStick(Stick::MAX, Stick::MIN, 1500); // 右上にダッシュ
SwitchControlLibrary().releaseButton(Button::R);
SwitchControlLibrary().sendReport();
pushButton(Button::A, 250, 3); // 受付嬢に話しかけ
pushButton(Button::B, 250, 4); // 会話スキップ
delay(100);
pushHat(Hat::UP); // 下位クエストを選択
pushButton(Button::A, 100);
pushHat(Hat::DOWN); // Lv1を選択
pushButton(Button::A, 100);
pushHat(Hat::DOWN, 50, 3); // 森の中のケルビを選択
pushButton(Button::A, 50, 5); // クエスト受注
pushButton(Button::B, 250, 4); // 会話スキップ
//クエストに出発
SwitchControlLibrary().pressButton(Button::R); //Rを押し始める
SwitchControlLibrary().sendReport();
tiltLeftStick(Stick::MIN, Stick::NEUTRAL, 800); // 左に移動
tiltLeftStick(Stick::NEUTRAL, Stick::MIN, 2300); // 右に移動
SwitchControlLibrary().releaseButton(Button::R);
SwitchControlLibrary().sendReport();
pushButton(Button::A, 50, 5); // クエストに出発
delay(8700); // クエスト開始までの待機時間
pushButton(Button::PLUS, 250); //メニューを開く
pushButton(Button::A, 250, 2); //ケルビの角を選択
pushHat(Hat::DOWN, 50, 2); // 納品を選択
pushButton(Button::A, 250);
pushHat(Hat::RIGHT); // 上限の3つを選択
pushButton(Button::A, 250, 4); // 納品
delay(36000); // クエスト終了までの待機時間
//報酬売却
pushHat(Hat::UP);
pushButton(Button::A, 250);
pushHat(Hat::LEFT);
pushButton(Button::A, 250, 5);
pushButton(Button::B, 250); //セーブしない
pushButton(Button::A, 250);
delay(7900); // クエストから帰還するまでの待機時間
// ココット村自宅へ移動
pushButton(Button::X, 250);
pushButton(Button::A, 250, 2);
//マカ鑑定
delay(2000); // 自宅までのロード時間
tiltLeftStick(Stick::MAX, Stick::NEUTRAL, 700);
pushButton(Button::A, 250, 2); // ルームサービスに話しかけ
pushButton(Button::B, 250, 2);
delay(100);
pushButton(Button::A, 250);
pushHat(Hat::DOWN, 50, 3);
pushButton(Button::A, 250); // マカ錬金
pushHat(Hat::DOWN, 50);
pushButton(Button::A, 250, 2); // 鑑定
}
// ここに記述した内容がループされ続ける
void loop(){
}手動でどのようにして安定させるかに関しては今後の研究が必要となると思われる.
2026/03/10追記
やや冗長であるが,数式と実験による数値を表示しておく.
1回のcontinueで消費されるフレーム数の期待値を
と表せる.ここで,以下のことが分かった.
変更しないパラメータを全てまとめて
次に,最後のデータから,
となり,目標の
ただし,この他にも10回程度実験したが,
さて,長いフレーム数の護石を狙う場合,やみくもに
初期値
T_2^{(1)} は上述のF(N_c, T_2) の推定式から,F_1 \approx F^* を満たすT_2 を設定する.2個目は
T_2^{(2)} = T_2^{(1)} + (F^* - F_1) / 30 \times 1000 として外挿により決定する.3個目の
T_2^{(3)} は 2点間の直線公式からF^* = (F_2 - F_1) / (T_2^{(2)} - T_2^{(1)}) \times (T_2^{(3)} - T_2^{(1)})+ F_1 を満たすT_2^{(3)} を決定する.4個目以上の
T_2^{(i)}\quad (i\geq 4) に対しては1, 2, \ldots, i-1 番目までのデータを使用して(逐次)線形回帰を行う.モデルをy=ax + b + \epsilon とする.ただし,\epsilon は誤差項である.このとき,パラメータの推定値\hat{a}, \hat{b} を用いてT_2^{(i)} = (F^* - \hat{b}) / \hat{a} により決定する.
ほぼ自分の備忘録として書いたので読みにくいかもしれませんが,何かの助けになれば幸いです.
最後ではありますが,このノートを読んで実行したことによって生じた不利益に関して筆者は責任を一切負いません.自己責任でお願いいたします.


コメント