PCSX2で遊んでいるPS2版『ドラゴンクエストV 天空の花嫁』(※)で、神の塔に出るメタルスライムを仲間にしたくなりました。初回加入率は1/256。せっかくなので、Claude Codeと一緒に「セーブステートを使って何度もリトライする」作業を自動化してみた記録です。

※著者はパッケージ版のソフト、PS2実機を所有し自己利用しています

最初はLLMに手動で操作してもらっていた

やりたいことはシンプルで、戦闘直前のセーブステート(スロット3)をロードして、攻撃してメタルスライムを倒し、「仲間になりたそうにこちらを見ている」のメッセージが出るのを待つ、というだけです。

最初はClaude Codeにcomputer use(画面を見ながらマウス・キーボード操作をするやつ)でPCSX2を直接操作してもらっていました。スクリーンショットを撮って、状況を判断して、キーを送って、また撮って……という感じです。

トークン消費が気になったのでプログラミングタスク化

何十回も繰り返すうちに、「これ、毎回LLMがスクリーンショットを見て判断する必要ある?」という話になりました。1/256を狙うには最低でも数百回は試行が必要で、その都度LLMが画像を見て判断していてはトークンがいくらあっても足りません。

そこで方針転換。やることは単純な繰り返しなので、Pythonスクリプトに落とし込んで完全に自動化することにしました。

やっていることは次の4つです。

  • 状態のロード:AppleScriptで「システム→ステートロード→スロット3」をメニュー名指定でクリックする(座標に依存しないので画面サイズが変わっても壊れない)
  • キー入力:Swiftで書いた小さな実行ファイルが、macOSのCGEvent APIでキー入力を合成してPCSX2に送る。攻撃コマンドを送るまでにランダムな待機時間を挟んで、乱数のズレを作る
  • 結果の記録:PCSX2の内蔵スクリーンショット機能を使って、ゲーム画面をそのままファイル保存する(macOSの画面収録権限が要らないのが地味に便利)
  • 判定:保存された画像をPythonのPillow/numpyでピクセル解析。メッセージウィンドウの枠線(決まった位置に決まった明るさで出る)を検出して、「まだメッセージが表示中か」「フィールドに戻ったか」を機械的に判定する

これを`grind.py`という1本のスクリプトにまとめて、`python3 grind.py <回数>`で好きなだけバックグラウンドで回せるようにしました。結果は1行1JSONのログファイルに溜めていきます。

ハマったポイント

メタルスライムのゴールドは固定ドロップだった

最初、「乱数がちゃんとズレているか」を確認するために、撃破後に落とすゴールドの金額が試行ごとに変わるかを見ようとしました。ところが毎回きっちり同じ金額。「乱数が固定されてしまっているのでは」と焦りましたが、調べてみたらメタルスライムの所持金はそもそも75G固定でした。ゲームデータの仕様を疑うところから入る、というありがちな落とし穴でした。

PCSX2が「フォーカスを失うと自動で一時停止する」設定になっていた

自動化を始めてしばらくすると、突然キー入力もスクリーンショットも一切反応しなくなる現象が頻発しました。何時間も原因を探った結果、PCSX2の設定に「非フォーカス時に一時停止」という項目があり、これがオンになっていたことが判明。スクリーンショットを撮るためにウィンドウのフォーカスが一瞬でも外れると、ゲームが一時停止してしまい、以降の入力が全部無視されていたのです。オフにしたら安定しました。

スクリーンショットボタンをクリックすると、それ以降のキー入力が効かなくなる

一時停止問題を直したあとも、たまに入力が完全に効かなくなることがありました。原因は、スクリーンショット機能をAppleScriptでツールバーのボタンをクリックして呼び出していたことです。ボタンをクリックすると、キーボードフォーカスがそのボタンに残ってしまい、次に送ったキー入力がゲーム画面ではなくボタンに向かって消えていました。F8ホットキー経由でスクリーンショットを撮る方式に戻したことで解決しました。

統計で考えると、まだ全然「詰み」じゃない

自動化が安定してからは、数百回単位でバックグラウンド実行しては結果を確認する、という運用に切り替えました。最終的に873回試行して、実際に撃破(=仲間化判定が発生した)した回数は308回。仲間化には至りませんでした。

1/256の確率で308回引いてゼロ成功になる確率は約30%です。宝くじで言えば「まだ外れ数十枚目」くらいの感覚で、システムがおかしいわけではなく、単純にツキがなかっただけという話になります。

まとめ

  • 「LLMがずっと画面を見て操作する」タスクは、繰り返しが単純なら普通のプログラムに落とし込める
  • AppleScript(UI操作)+ CGEvent(キー入力合成)+ PCSX2内蔵スクリーンショット+ Pillow(画像判定)の組み合わせで、ゲームの自動リトライは十分組める
  • 沼ったポイントは大体「見えない設定」(フォーカスロス時の一時停止)と「見えないフォーカスの移動」(ボタンクリック後のキーボードフォーカス)だった
  • 1/256は素直にしんどい。873回撃破してもまだ「よくある外れ方」の範囲

メタルスライムはまだ仲間になっていませんが、grind.pyは手元に残っているので、気が向いたらまた回そうと思います。

投稿者: hirobel

JavaScriptが好きです

コメントをどうぞ

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

PAGE TOP
Close Bitnami banner
Bitnami