読者です 読者をやめる 読者になる 読者になる

FC『麻雀』がFCEUXで正しくエミュレートされない件

このゲームは、電源投入時からAとSTARTを押しっぱなしにしていると大物手が入るという裏技*1がありますが、これをFCEUXで実行すると実機とは異なる結果になります:
http://i399.photobucket.com/albums/pp73/TaoTao1942/TAS/Mahjong_FC/AT-NNNesterJ-023.png http://i399.photobucket.com/albums/pp73/TaoTao1942/TAS/Mahjong_FC/AT-FCEUX-213-oldppu.png
左がニューファミコンでの結果(100回以上やって全て同じ)、右がFCEUX 2.1.3での結果(r2052, r2194でも同じ。newPPUでも変わらず)です。初代ファミコンについては今手元にないため実験してませんが、確か左の結果になったはず(万一違ってたら教えて頂けるとうれしいです)。

他のエミュレータについても試してみたところ、VirtuaNES 0.97, NNNesterJ 0.23, Nestopia 1.40, Nintendulator 0.970-unicode は左の結果になり、G-NES 0.618 はどちらとも異なる結果になりました(乱数20個分ほどズレている)。

エミュレータと実機の動作が異なる原因の1つとしてはRAMの初期状態の違いが挙げられます(例: FF2のエンカウント歩数)が、このゲームは起動時にRAM全体を初期化しているため、その点は問題ないはずです。私はPPUの実装に問題があるのではないかと推測していますが、正確なところはわかっていません。

左の手牌に対応する乱数は(0から数えて)8185番目であり、右は8186番目です。よって、FCEUXは乱数更新ルーチン($FCC2)を1回余計に実行していることになります。このゲームの乱数更新はNMI待ち中に行われるため、各フレームの処理量がクロック単位で変化しただけでも乱数に影響が及びます(キー入力で乱数調整ができるのはこのため)。よって、PPU周りのタイミング制御が正確でないと乱数がおかしくなる可能性があります。

ということで、試しにVirtuaNESに超適当なパッチを当ててプログラムカウンタのログをとるように改造し、FCEUXのトレースログと比較してみた…のですが、私の実力では何が原因なのかさっぱりわかりませんでした。ただ1つ気になった点として、FCEUXは最初のVBLANKが発生するまでが59683CPUクロックで、VirtuaNESの27409CPUクロックに比べてかなり長くかかっていました。この件については、NesDevWikiによると:

The VBL flag ($2002 bit 7) is usually clear at power, and unchanged by reset. It is next set around 27384, then around 57165.

とのことなので、VirtuaNESの方が正確な気はします*2。そこでFCEUX r2194のoldPPUのコードにパッチを当てて、最初のVBLANKまでにかかる時間をVirtuaNESと同じにしてみたのですが、やはり配牌は変化しなかったため、これは最初の問題とは無関係っぽいです。

ただ、パッチを当てたFCEUXであっても配牌ルーチン($D2BF)が実行されるまでにVirtuaNESよりも140命令ほど多く実行しており、乱数更新ルーチンは1回当たり約140命令かかるので、NMIのタイミングがわずかにずれているのは確かだろうと思います。

もしかしたらわかる人がいるかもしれないという期待を込めて、パッチを当てたエミュレータとトレースログをアップしておきます:

VS2010でVirtuaNESをビルドする場合、VirtuaNES-0.97-vs2010.patch を当てる必要があります。また、場合によっては依存ライブラリの dinput.lib を dinput8.lib に書き換える必要があるかも。

追記: どうもスプライトDMAの消費クロックがおかしいような気がします。詳しくは次回

*1:裏技といっても特別な処理がなされるわけではなく、乱数位置がたまたま一致するだけ

*2:ただ、FCEUXがこうなっているのは "Needed for Knight Rider, possibly others." らしいですが