ごちゃペディア

はなまるデジタル創作紀行(DTM、TAS、いろいろな技術)

Bizhawk - Multitrack recording とは

ホットキー設定の一覧を眺めていると「Multitrack」で始まる項目がいくつかあることに気づきます。これは複数のプレイヤーの入力を個々に入力するための機能です(1フレームずつではなく、もっとまとまった単位で)。ただ、どこにも使い方の説明がなかったので、IRCで聞いてみました。

簡単な例**

  1. 2プレイヤーで NES のムービー記録を開始する。(スロット0とかに)ステートセーブして toggle multiple track キーを押したら、increment/decrement を使ってプレイヤー1に設定して、適当に操作する。
  2. プレイヤー2にインクリメントしてステートをロードする。プレイヤー1のボタンを使うと今度はプレイヤー2が操作されて、プレイヤー1はさっき記録した入力が繰り返される。
  3. increment/decrement はあんまり賢くなくて、5プレイヤーまでハードコードされてるよ。利用可能でないプレイヤーを記録しようとしないでね。

らしいです。

Lua for Windows の LUA_PATH 設定(EmuLua からモジュールを使う)

Lua for Windows には多数のライブラリが含まれていますが、それらをTAS用エミュレータ組み込みの Lua から使おうとしてもロードに失敗します。これはパス設定が適切ではないためです。ロードが成功するようにパス設定を見直しましょう。

このようなタイトルですが、他の環境でライブラリがうまく検索されない場合も同じ解決策を取ることになるでしょう。

環境変数の設定

以下、Lua for Windows v5.1.4-46 についての設定です。

gd.dll などのバイナリモジュールは package.cpath から検索されます。環境変数 LUA_CPATH を設定することで任意のパスを追加できますが、Lua for Windows のインストールでは設定されないようです。よって自前で設定することになります。

LUA_CPATH=;;C:\Program Files (x86)\Lua\5.1\clibs\?.dll

冒頭の ";;" はデフォルトパスを展開するための記述なので、消してはいけません。

Lua モジュールは同様に package.path から検索され、環境変数 LUA_PATH で設定します。一見するとインストール時に設定されるように見えるのですが、拡張子が ?.clua となっているため、*.lua を読み込もうとすると失敗します。よって、こちらも値を変更しなければなりません。

# ?.lua と ?.luac を両方含むように設定する
LUA_PATH=;;C:\Program Files (x86)\Lua\5.1\lua\?.lua;C:\Program Files (x86)\Lua\5.1\lua\?.luac

これで無事にモジュールが使えるようになります。個々のエミュレータディレクトリを汚す必要がなくなるのですっきりしますよ。

DeSmuMEに3D表示抑制機能を足してみた

Download Windows binary & source code (desmume 0.9.8-gfx3dHack)

ゲーム中のキャラクタの画像だけとか、地形の画像だけキャプチャしたい時ってありますよね。他のエミュレータと同じくDeSmuMEにもレイヤー表示・非表示を切り替える機能はあるのですが、ゲームによっては前景から背景に至るまで同一レイヤーで3D描画していることがあり、この場合には前述の機能は役に立ちません。

じゃあ何かしら役に立つ機能をつけてみようというのが本日の趣旨です。

改造の概要

「3D描画を部分的に抑制したい」というのが今回求める機能です。

内部的には3Dデータは多数のポリゴンで構成されています。これを位置情報に基づいて、3Dエンジンが背面〜前面にかけて描画することで処理されます。表示を抑制したければ、一部のポリゴンの描画処理をあえて行わなければ良いのです。簡単ですね。

レイヤーと同様にポリゴンの表示・非表示機能をつければ良さそうですが、困ったことにポリゴンの数は軽く100を超えるくらいには多いです。個別切り替えでは使い勝手もよくなさそうなので、「前の方のポリゴンを非表示」「後ろの方のポリゴンを非表示」という形で対応することにしました。「前の方」「後ろの方」の度合いを切り替えできるようにすることで、抑制度合に柔軟性をもたせます。

コードと使い方

0.9.8 のソースコードとの差分を作成して、改造版を作成しました。ダウンロードリンクは記事の冒頭にあります。

新しくダイアログを設けるのは実装の手間がかかるので、表示の抑制はLuaスクリプトを通じて行う形にしました。

-- desmume 0.9.8-gfx3dHack sample script

local gfx3dMin, gfx3dMax = 0.000000, 1.000000
function modifyVisibility()
	gui.text(0, 0, string.format("%f %f", gfx3dMin, gfx3dMax))
	gui.setgfx3dvisibility(gfx3dMin, gfx3dMax)
end

local keys = { {}, {} }
gui.register(function()
	keys[1] = input.get()

	if keys[1]["6"] then
		gfx3dMin = math.min(math.max(gfx3dMin - 0.001, 0.0), 1.0)
	end
	if keys[1]["7"] then
		gfx3dMin = math.min(math.max(gfx3dMin + 0.001, 0.0), 1.0)
	end
	if keys[1]["8"] then
		gfx3dMax = math.min(math.max(gfx3dMax - 0.001, 0.0), 1.0)
	end
	if keys[1]["9"] then
		gfx3dMax = math.min(math.max(gfx3dMax + 0.001, 0.0), 1.0)
	end

	modifyVisibility()

	keys[2] = keys[1]
end)

emu.registerexit(function()
	gui.setgfx3dvisibility(0.0, 1.0)
end)

追加した関数は1つだけです。

function gui.setgfx3dvisibility(gfx3dStart, gfx3dEnd)
-- gfx3dStart: 0.0 = no effect, 1.0 = hide all polygons
-- gfx3dEnd: 0.0 = hide all polygons, 1.0 = no effect

3Dポリゴンの表示割合を設定します。最前面・最背面にあるポリゴンの一部を隠すことができるようになっていて、例えば 0.2, 0.9 と指定すると、全ポリゴンのうち、最背面にある 20% のポリゴンと、最前面にある 10% (100%-90%) のポリゴンは表示されなくなります。サンプルスクリプトでは、数字の 6, 7, 8, 9 キーを使ってこの表示割合を変更できるようにしてあります(スクリプト自体の利便性は高くないので、目的に応じて改良することができると思います)。

感想

万能ではないですが、良い感じに欲しい画像が得られるようになりました。

総ポリゴン数は細かい状況の違いによって簡単に変動するので、少しゲームを操作すると表示がちらついて、思うような表示結果が継続されないという利便性の問題があります。不便さはありますが、数値を細かく調整する、欲しいフレームを狙ってキャプチャするなど、工夫次第で目的は果たせるかなあと思います。

あとは背景色が自前で指定できるとか、描画のない部分はアルファチャンネル付きの透明ピクセルとして保存できるようにするとか、そんな対応があると背景の除去や合成がぐっとしやすくなると思うのですが、今日のところは放っておくことにします。誰か興味があれば改良してくれると喜びます。

それでは、良いリッピングライフをお楽しみください☆

追伸

背景色の操作ですが、Memory Viewer で Palette 0 を操作したら変更できました。15 bit color かな?

  5000000h Engine A Standard BG Palette (512 bytes)
  5000200h Engine A Standard OBJ Palette (512 bytes)
  5000400h Engine B Standard BG Palette (512 bytes)
  5000600h Engine B Standard OBJ Palette (512 bytes)
  7000000h Engine A OAM (1024 bytes)
  7000400h Engine B OAM (1024 bytes)
  http://nocash.emubase.de/gbatek.htm#dsmemorycontrolvram

Soft Rasterizer はピクセルずれを起こすのでオススメ出来ません。OpenGLレンダリングしましょう。条件によっては選びたいエンジンが変わるかもしれませんが。

なぜTASはdesyncする?原因追求とデバッグの仕方

エミュレータのバグによるdesyncの傾向と対策。直接直したい人や、開発者に効果的なデバッグ依頼をしたいときに。

なお、TAS向けエミュレータで満たすべき要件は TASVideos / Laws Of TAS にまとめられています。

原因:矛盾したステートのセーブ・ロード

矛盾したステートのセーブ・ロードをするとdesyncします。例えば、AAAAと入力されたムービーに対して、AABBと入力されたステートをロードして、続きを作ってしまった場合などです。

ここではそういう、ある種ヒューマンエラー的なdesyncはさておいて、矛盾なく扱ったはずなのにdesyncする場合に的を絞ります。

根気でdesyncを乗り切るのは体力の無駄です。公式の Snes9x 1.51 でムービーを作成していた時期がありましたが、はっきり言って無駄な戦いに時間を使いました。まずエミュレータを直すべきです。

原因:ステートに保存すべき変数が保存されていない

ステートに保存すべき変数が保存されていなくてdesyncする場合です。ステートというのはハードウェアの状態(メモリの値、動作をエミュレートするためのカウンタ変数など)をすべてファイルに格納したものです。格納内容が足りていないとロード時に状態が元に戻りませんが、中には「何か足りていなくても、プレイする分には全然支障がなくて気づかない」ことがあります。TASは動きが細かいので、そういう細かいものがdesyncとして現れてきたりします。

desyncが起きる例(及び内部の動き)
  1. ムービー再生中の適当なタイミングで、ステート1を作成(このときエミュレータ内部の count という変数が count = 1 であるとする)
  2. 適当に1000フレーム動作させる(count = 2 になったとする)
  3. ステート1をロードする(count = 1 に戻って欲しいが、ステートに count が含まれていないと count = 2 になってしまう)
  4. そのまま動かしていくと、desyncする
どう報告する?(デバッグ材料の用意)

材料を用意しましょう。まず、適当なムービーを用意しておきます。TASのように「少し動きが変わると破綻しやすい」ものほど良いです。

  1. エミュレータを起動して(起動から始めること!)ムービーを再生します。ムービー再生時に開くROMを指定できればベストです。0フレーム目でポーズするなど、事前に極力余計なエミュレーションを行わないようにしてください。
  2. 再生中にいくつかステートを作成する(多いほど良い、動きが緻密でずれが禁物な箇所ほど良い、再生中のロードは厳禁)
  3. ステート作成後、適当な場所で適当なステートをロード→再生させて上記の最初の再生結果とずれがないか確認 を繰り返す。
  4. ずれを確認できたら、フレームカウンタを見つつ「ムービーをNフレームまで再生後、ステートAをロードするとずれる」というようにどのタイミングでロードすると起きるかを厳密に調べる。
  5. 開発者にムービーとステートAを渡して、「ただ再生した場合と、Mフレーム時点(ステートA作成時点)でステートセーブしておいて、その後Nフレーム目まで再生してステートをロードしたときで再生結果が違う」と伝える。「再生結果が違う」では曖昧なので「Xフレームあたりでキャラクターの攻撃が当たるはずが外れる」など、ゲームの内容に疎くてもわかるような表現で具体的に伝えると良い。

だいたいこのくらいできれば材料になると思います。再現「手順」を明確にすることが大切です。

いつのバージョンから起きる?

過去のバージョンでは問題がなかったのに、今のバージョンでは問題が起きているという場合、まずいつのバージョンから起きているかを明確にするとよいです。開発者の方は、SVNやGitなどのどのコミットが原因なのか、古いバージョンを受信・コンパイルしつつ二分探索的に探していきましょう。開発者でない方は開発途中の細かいバージョンの特定がしづらいかもしれませんが、SkyDrive - Orleans / Emulation などから過去の開発時ビルドを探してテストすると、範囲を絞り込むことができます。

どうデバッグする?

まずは寄せられた材料で状況が再現できることを確認しましょう。

大抵のエミュレータでは、主要な構造体がいくつかあって(CPU、PPU、SPU 等)その中にエミュレーションに関係する変数があります。時々そうではなく、何気なく使われているグローバル変数が重要なこともあります。手順としては、

  1. 主要な構造体、怪しげなグローバル変数をウォッチに追加する
  2. ステートセーブ後の位置にブレークポイント設置、ムービーを頭から再生して、Mフレーム時点でポーズしてステートセーブ、ウォッチ内容をコピペするなどして控える
  3. ステートロード後の位置にブレークポイント設置、Nフレームまで再生してステートロード、ウォッチ内容をコピペするなどして控える
  4. 2つのウォッチ内容を比較して差異のある箇所に着目する

こういう感じでしょうか。簡単にわかることもわからないこともあります。怪しい変数があればステートに変数を追加してみて、同じ手順を行なってみましょう。ずれがなくなれば修正成功です。

メモリの値で追えないならトレースログをとって比較するのも手段としてありそうです。HDD容量と実行速度がネックですが……。

原因:ステートロード時の誤った変数初期化

上記の亜種です。ステートに保存しないといけないのに、0に初期化している場合などです。

desyncが起きる例(及び内部の動き)
  1. ムービー再生中の適当なタイミングで、ステート1を作成(このとき count = 1 であるとする)
  2. セーブ直後に何もせずにステート1をロードする(ロード時の不当な初期化で count = 0 になる)
  3. そのまま動かしていくと、desyncする

同じ場所でセーブ・ロードしてもずれるのが特徴です。

どう報告する?(デバッグ材料の用意)

「ステートに保存すべき変数が保存されていない」ケースと同じで構いません。ただ、「その場でセーブロード」しても起きるのがこのケースの特徴なので、「Nフレームまで再生後、その場でセーブ・ロードして再生継続するとずれる」という形で報告可能であれば、そのほうが良いです。

どうデバッグする?

「ステートに保存すべき変数が保存されていない」ケースと同様です。

原因:リセット(ROM読込)時に初期化されない変数がある

これが結構厄介です。

desyncが起きる例(及び内部の動き)

適当なムービーを用意しておく。

  1. エミュレータを起動して(起動するところから始めること!)、「ステートに保存すべき変数が保存されていない」ケースと同様にムービー再生を開始します。(例:count = 0 で再生開始)
  2. エミュレータを起動して(起動するところから始めること!)、ROMを開いて、フレームカウンタがXフレームになったらポーズしてムービーを再生します(例:ROM起動後ちょっと動作させたことで count = 50 になっている、count = 0 にして再生開始して欲しいが、count = 50 を初期値としてムービー再生が始まる)
  3. 1回目と2回目で再生結果が異なる

特徴は、一切のステート操作を介さずにdesyncする点です。

どう報告する?(デバッグ材料の用意)

「例」のような手順が再現できたら、ムービーを渡してその手順をそのまま伝えてください。

どうデバッグする?

ムービー再生開始時のROMロード直後にブレークポイントを仕掛けて、1回目と2回目の各種変数の値を比較します。

コードを目視しながら「この変数ってリセット時に初期化しなくていいの?」と気になる点を探してもいいと思います。

「Reset」では初期化されないけれど、「LoadROM」では初期化されるような事例も Snes9x で過去にありました。せっかくなのでリセットとROM読み込みのコードを両方見ながら、変な箇所を潰すとよいです。

その他

  • 直面したことはないですが、マルチスレッド環境特有の desync 事例もあったとかなかったとか
  • 開発者向けの再現手順を作ろうとしてもうまく作れないこともあると思います。そういうのはなかなか直せなくて厄介です。「ムービー」と「変なステート」だけ送っても材料にはなると思うので、一応送ってみてください。Luaスクリプトを利用して操作を自動化していた場合、それが思わぬ影響を及ぼしている可能性もあるので、スクリプトを使っていた旨を伝えたり、そのスクリプトを渡したりするとよいと思います。期待は禁物。
  • 過去のバージョンでは起きないが、いつのバージョンから起きるという情報も有用?
  • みんなでがんばって desync をたおしましょう

ポイント

  • とにかくムービーファイルを送ってあげてください! 開発者はdesync確認に適したムービーファイルなんて持っていません。作成には手間がかかりますし、作成しても再現できない可能性も高まります。その他、ステートファイルとか関係するものは一式送ってあげてください。あればなんとかなるとも言えませんが、ないよりは喜ばれます。
  • できるだけ確実でシンプルな再現手順を教えてあげてください! 異なる「再生結果1」と「再生結果2」を再現する手順です。ステート作成は開発者側で行わせるような手順で。
  • 「ずれちゃう」ことを伝えるときは、「何フレーム目から○○の動きが変わる」などを、なるべくゲームを知らなくても理解できる表現で伝えるとよいです。