MAME解体新書

エミュレーターを読む

Keijiro Takahashi(企画・監修)

Claude, Anthropic(調査・執筆)

2026-07-17

はじめに

MAME(Multiple Arcade Machine Emulator)を、アーケードゲームを遊ぶためのソフトウェアだと思っている人は多いでしょう。それは間違いではありません。実際、MAME を起動すれば数万本のアーケードゲームが動きます。しかし MAME 自身は、自分のことをそう定義していません。

公式ドキュメントの「What is MAME」は、こう述べています。MAME の目的はソフトウェアの歴史を保存することであり、それはハードウェアとその動作をドキュメント化することで達成される。そして——ソースコードこそがそのドキュメントである。ゲームが実際に遊べるという事実は、主としてドキュメントの正確さを検証するために役立つにすぎない。「ほかにどうやって、ハードウェアを忠実に再現できたと証明できるだろうか?」

本書は、この自己定義を額面どおりに受け取ります。つまり MAME を「遊ぶ道具」としてではなく、「読む対象」として扱います。30 年近く書き継がれてきた巨大な C++ コードベースを、失われゆくアーケード基板の技術資料として読む。それが本書の一貫した姿勢です。

本書の狙い

本書は 3 つのことを目指します。

第一に、30 年続く巨大オープンソースプロジェクトとしての MAME のアーキテクチャを理解すること。数万種のハードウェアを単一のフレームワークで扱う設計が、どんな抽象化の上に成り立っているのかを見ます。

第二に、MAME のソースコードが実際に「基板の技術資料」として読めることを、具体的なコードとともに確かめること。水晶発振子の周波数、アドレスデコード回路の癖、チップのピン配線——それらがどうコードに写し取られているかを見ていきます。

第三に、MAME というレンズを通して、名作ゲームそのものの設計・実装・そしてバグまでもが解明されてきた過程を追体験すること。パックマンのゴーストがなぜあの動きをするのか、スペースインベーダーはなぜ敵が減ると速くなるのか——その答えは、正確に再現された基板の中にあります。

想定する読者

本書は、C/C++ とアセンブラを一通り理解し、アーケードゲームの基礎技術(CPU、メモリマップド I/O、スプライト、タイルマップといった概念)にある程度なじみのあるプログラマーを想定しています。MAME のソースコードから多くの断片を引用しますが、その一行一行を暗記する必要はありません。設計の意図と、そこに込められた「正確さへの執念」を読み取っていただければ十分です。

本書が扱わないこと

本書は MAME の使い方マニュアルではありません。インストール手順やゲームの起動方法、フロントエンドの設定といった実用情報は、公式ドキュメントに譲ります(デバッガーや検証ツールに触れる場面もありますが、それも「解析の道具」としてです)。

また、本書は ROM イメージの入手方法には一切触れません。MAME の公式見解は明快で、ROM は著作物であり、権利者の許諾なく配布することはできません。本書が扱うのは、あくまで公開されている MAME のソースコードと、コミュニティが出典を明示して公開してきた解析成果です。歴史的な事実や技術的な詳細は、可能なかぎり一次資料——公式サイト、GitHub 上のソースコード、開発者自身の文書——で裏を取っています。

本書の構成

第1部では MAME の歴史を、ソフトウェア保存運動という視点から辿ります。第2部と第3部で、MAME というソフトウェアそのもののアーキテクチャ——コアの設計と、CPU・ビデオ・サウンドといった部品のエミュレーション——を解剖します。第4部では、その仕組みが実際のさまざまな基板でどう組み合わさるかを事例研究として読みます。そして第5部では視点を裏返し、MAME を顕微鏡としてゲームそのもののコードを覗きます——ゴーストの思考、当たり判定と乱数、有名なバグ、そして製品に残された隠しコードまで。

それでは、世界最大のゲーム保存プロジェクトが「クリスマス休暇の暇つぶし」から始まった、その歴史から見ていきましょう。

第1部 MAME とは何か

MAME はゲームを「動かす」ためのソフトウェアだと思われがちですが、その本質はむしろ「保存する」ことにあります。第1部では、一介のアーケードエミュレーターがなぜ 30 年近くも開発され続け、数万種の基板を記録するに至ったのか——その歴史と、背後にある保存という思想・技術・倫理を追います。

MAME の歴史 — 保存運動としてのエミュレーション

クリスマスイブの暇つぶし

史上最大のゲーム保存プロジェクトは、クリスマス休暇の暇つぶしから始まりました。

1996年12月24日、イタリアのプログラマー Nicola Salmoria(ニコラ・サルモリア)は、アーケードゲームのエミュレーターを書き始めます。きっかけは、当時エミュレーター開発者たちの情報ハブだった「Arcade Emulation Programming Repository」というサイトから、初歩的なパックマンのエミュレーターのソースコードをダウンロードしたことでした。後年のインタビューで本人が語るところによれば、このパックマンエミュレーター(Multi-Pac)を精度良く仕上げた後、ペンゴ、クレイジークライマー、レディバグ、ラリーX と単体エミュレーターを次々に作っていったといいます。

Salmoria は無名の趣味プログラマーではありませんでした。Amiga のソフトウェアシーンでユーティリティ「NewIcons」の作者として知られた、経験豊富な開発者です。後に彼はカプコン CPS-2 やネオジオの ROM 暗号を解読することになる、生粋のリバースエンジニアでもありました。

そして、複数の単体エミュレーターを作った彼を待っていたのは、理念ではなく実務上の苦痛でした。「別々のプロジェクトを維持するのは悪夢になった」——本人がそう振り返るとおり、MAME 誕生の直接の動機は、共通コードを一本化したいというソフトウェア工学的な必然だったのです。

開発開始からわずか 6 週間後の 1997年2月5日、統合されたエミュレーターは MAME 0.1(Multiple Arcade Machine Emulator)としてリリースされます。対応ゲームは 5 本。パックマン、ミズ・パックマン(海賊版基板)、クラッシュローラー、ペンゴ、レディバグです。MS-DOS 用で、Z80 コアには Marcel de Kogel の Z80Em を借用していました。

この最初のリリースの readme には、後の MAME を象徴する記述が既に含まれています。既知の問題の欄に、Salmoria はこう書きました——パックマンの赤とピンクの敵が 1 ピクセル右にずれて見える。本当に苛立たしいが原因が分からない。「それとも、実機がもともとそういう挙動だったのだろうか?」。単純な位置補正で誤魔化さず、「実機の真の挙動は何か」を問う。バージョン 0.1 の時点で、正確性の哲学は芽生えていました。

乱立から統合へ — 1997年のエミュレーターシーン

MAME が登場した 1996〜97年のアーケードエミュレーションシーンは、それぞれが「小さな縄張り」を持つ単体エミュレーターの乱立状態でした。後に MAME の第4代コーディネーターとなる Aaron Giles は、回想録で当時の様子をそう表現しています。

Dave Spicer の Sparcade はパックマンやギャラクシアンなど複数タイトルに対応した先行マルチエミュレーター。Neil Bradley の EMU はアステロイドやテンペストなど Atari ベクターゲーム専門。ほかにも Bally/Midway 基板専門の KEM、ガントレット専用の MGE、そしてアセンブラーで書かれ圧倒的な速度を誇った CPS-1 専用エミュレーター Callus。CAGE、HiVE、RAGE、Retrocade といったマルチエミュレーターが現れては消えていきました。

なぜ MAME だけが生き残ったのか。Giles の分析は明快です。第一に「完成の罠」——対象システムの全ゲームが動いてしまうと、開発の動機が消える(EMU が典型でした)。第二にクローズドソースの限界——単独作者のプロジェクトは、作者が飽きた時点で停滞する(Sparcade は海賊版業者への嫌気から公開を停止しました)。対して MAME は当初からオープンソースで、C 言語によるクロスプラットフォーム志向を貫き、あらゆる OS のユーザーから貢献を受け入れ、そして「すべてのアーケード基板」という終わりのない対象範囲を持っていた。競合たちの縄張りを、MAME は初日から吸収し続けたのです。

進化のペースは凄まじいものでした。0.10(1997年3月)で初の非 Z80 ゲーム(センチピード、6502)、0.12 で初のマルチ CPU ゲーム(バーガータイム)、0.26(同年7月)でベクターゲーム、0.28(同年9月)で初の 68000 ゲーム(ラスタンサーガ)。リリースから 7 ヶ月で、MAME は 8 ビットの Z80 基板専用ツールから、世代を横断する汎用フレームワークへ変貌していました。

「遅い」と言われ続けて — 正確さの哲学

MAME の思想を一言で表すなら「速度より正確さ」です。ただしこれは、最初から掲げられたスローガンではなく、論争の中で鍛えられた哲学でした。

象徴的な論争が Usenet に残っています。1998年4月、comp.emulators.misc に立った「the speed of mame」というスレッドで、あるユーザーが不満をぶちまけました。「Callus なら Pentium 133MHz で 60fps 出るのに、MAME は這うように遅い」。もっともな不満です。Callus はアセンブラーで書かれた PC 専用の CPS-1 特化エミュレーターであり、C 言語で書かれ何百種類もの基板を抽象化する MAME が速度で敵うはずはありません。

このとき MAME を最も熱く擁護したのが、よりによって競合エミュレーター EMU の作者 Neil Bradley だったというのは、このシーンの気風を物語る逸話です。彼はこう書きました。「MAME はエミュレーター製品ではない——ドキュメント化とアーカイブのプロジェクトなのだ!」。別の擁護者は移植性の観点から反論しました。Callus は PC でしか動かないが、C で書かれた MAME のおかげで Mac も Amiga も Unix のユーザーも CPS-1 のゲームを遊べるのだ、と。当時のエミュレーターはアセンブラーで書くのが主流であり、あえて C を選ぶこと自体が「速度を捨てて何を取るか」という思想の表明でした。

実際、当時の MAME の公式ドキュメントには、既にこう明記されていました。「MAME の主目的は、エミュレートされるアーケード機の内部動作のリファレンスであること」であり、ゲームが遊べることは「好ましい副次効果(nice side effect)と考えてよい」——。世界で最も有名なゲームエミュレーターが、公式文書で「遊べることは副作用」と言い切る。この逆説こそが MAME の本質です。Salmoria 自身も 1999年のインタビューで明言しています。「ドキュメント化と保存。それが私に MAME を始めさせた目標だった」

この哲学は現在の公式ドキュメントに、より洗練された形で受け継がれています。いわく、MAME はハードウェアとその動作をドキュメント化することでソフトウェアの歴史を保存する。ソースコードこそがそのドキュメントである。そしてソフトウェアが実際に動くという事実は、主としてドキュメントの正確さを検証するために役立つ——「ほかにどうやって、ハードウェアを忠実に再現できたと証明できるだろうか?」

Aaron Giles は 2008年の講演でこの関係を一言に要約しています。「ドキュメントが正しいことの最良の証明は『このコードが動くか』だ」。動くことは目的ではなく証明手段である——この転倒した優先順位が、初期の「とにかく動かす」ためのハックを許さず、実測とリバースエンジニアリングに基づく実装への書き直しを 30 年間駆動し続けることになります。2003年に ROM の識別へ SHA1 ハッシュが導入されたのも、公式年表によれば「ハックを減らす」ため——つまり実基板から吸い出された正しいデータだけを基準とするためでした。この思想の展開は、以降の章で繰り返し目にすることになるでしょう。

プロジェクトの成長と開発体制

MAME の開発体制は、長らく「プロジェクトコーディネーター」と呼ばれる個人がリリースを取りまとめる形で運営されてきました。その変遷は公式の歴史年表に正確に記録されています。

初代はもちろん Salmoria ですが、彼はリリースからわずか 80 日後の 1997年4月、兵役義務のためプロジェクトを離れることを余儀なくされます。同じイタリア人開発者の Mirko Buffoni が代行を引き受け、Salmoria は同年8月に復帰。この「創設者不在の約 3 ヶ月半」を無事に乗り切ったことは、MAME が既に個人のプロジェクトではなくコミュニティとして回り始めていたことの、最初期の証明でした。単独作者に依存して消えていった競合たちとの違いは、ここで実地に示されたのです。

復帰した Salmoria は 2003年まで約 6 年間コーディネーターを続け、その後は David Haywood(2003年)、Aaron Giles(2005年)、Angelo Salese(2011年)、Miodrag Milanović(2012年)へと引き継がれていきます。創設者自身のコミットは年々減り、Salmoria の最後の貢献は 2009年。彼は現在、iOS 向けのパズルゲームを開発しているそうです。

なお初期の開発は今から見ると牧歌的なもので、開発者同士が連絡を取らないまま同じゲームのドライバーを重複開発してしまうこともあったといいます。後の第4代コーディネーター Aaron Giles の初仕事は、1997年に投稿したマッピーのドライバーでした。

MESS 統合 — アーケードの外へ

MAME の歴史で見逃せないのが、姉妹プロジェクト MESS(Multi Emulator Super System)の存在です。1998年に始まった MESS は、MAME のコードベースを土台に、家庭用ゲーム機やパーソナルコンピューターをエミュレートするプロジェクトでした。両者は長年、単一のソースリポジトリで並行開発される事実上の兄弟であり、開発者 David Haywood は正式統合に先立って両者を単一バイナリにまとめた「UME」を個人配布していたほどです。

2015年5月27日、MAME 0.162 で MESS は MAME に正式統合されました。公式告知は簡潔です。「MESS は今後 MAME の不可欠な一部となり、そのように配布される」。この統合により、MAME の射程は「アーケード機」から「電子機器全般」へと広がりました。現在の公式 README は、MAME がアーケードゲームに加えてコンピューター、家庭用ゲーム機、電卓まで含む広範な機器をドキュメント化するプロジェクトであると自己記述しています。頭字語だったはずの「Multiple Arcade Machine Emulator」という展開形は、もはや実態を表さなくなったため、現在では「MAME」が正式名称です。

オープンソース化 — 10 ヶ月の同意集め

意外に思われるかもしれませんが、MAME は 2016年まで、法的な意味での「オープンソースソフトウェア」ではありませんでした。

ソースコードは公開されていましたが、ライセンスは独自の「MAME ライセンス」。本体とソースの販売禁止、ROM イメージとの同梱禁止、そして著者の書面許可なしの商用利用禁止を定めた非標準ライセンスです。非商用条項の狙いは、アーケードオペレーターが MAME 筐体を設置してメーカーの著作物で利益を得るのを防ぐことでした(興味深いことに、公式の歴史年表によれば初期の MAME は GPL を採用しており、1997年8月の 0.27 で GPL から独自ライセンスへ「離脱」しています)。

しかしこの独自ライセンスは、時代とともに足枷になっていきました。非標準ゆえに法的な執行が難しく、しかも皮肉なことに、ゲームの権利を持つメーカーが自社タイトルの復刻に MAME を使おうにも、ライセンス交渉の窓口すら存在しなかったのです。当時のコーディネーター Milanović は再ライセンスの動機として、開発者の裾野を広げること、メーカーが自社ゲームのエミュレートに MAME を使えるようにすること、開発ボードで動く学習ツールにすることを挙げています。

問題は、その変更手続きでした。MAME には著作権を集中管理する主体が存在せず、約 20 年分・数百人規模の貢献者がそれぞれ自分のコードの著作権を保持しています。再ライセンスには、その全員の同意が必要でした。チームは 10 ヶ月をかけてすべての貢献者に連絡を取り、コア部分は BSD-3-Clause、各ドライバーは著者が BSD/LGPL/GPL から選択、という枠組みで合意を集めます。GitHub の Issue #562 には、連絡のつかない貢献者を公開の場で名指しで探す様子が今も残っています。

2016年3月4日、公式サイトは「MAME is now Free and Open Source Software」を宣言しました。ファイルの 90% 以上は BSD-3-Clause、プロジェクト全体としては GPL-2.0 以降。最初の FOSS 版リリースは同年3月30日の 0.172 です。そして統治の形もこれに続きました。2016年5月、19 年続いた個人コーディネーター制は、選挙で選ばれた 5 人による理事会制に移行します。ライセンスの民主化と統治の民主化が、ほぼ同時に起きたのです。

この再ライセンスの先見性は、3 年後に対照的な事件によって証明されます。2019年、カプコンが公式製品「Capcom Home Arcade」に採用したのは MAME ではなく、依然として非商用条項を持つ FinalBurn Alpha でした。開発者の 1 人が他のメンバーに相談なくライセンス料を受け取っていたことが発覚して炎上し、チームの大半が離反してフォーク「FinalBurn Neo」が生まれることになります。MAME が 10 ヶ月かけて解いた問題を、そのまま踏み抜いた形でした。

保存運動としての MAME へ

MAME の歴史は、ある時点から「開発の歴史」であると同時に「保存の歴史」になっていきます。未ダンプの基板を世界中から買い集めて ROM を吸い出す有志、電池が切れると二度と起動しなくなる基板との時間との闘い、読めないチップを物理的に開封して中身を読み取る技術——こうした泥臭い営みが、エミュレーションの精度を静かに下支えしてきました。ときにはコミュニティの非公式な保存が、権利者による公式の再発売を促すことすらあります。

その技術的な手法と、それを取り巻く倫理・法の問題は、まさに「保存」というテーマそのものとして次章で正面から扱います。ここでは、クリスマスイブの暇つぶしとして始まったプロジェクトが、いつしか電子ゲーム保存運動の中心的な担い手へと育っていった——という事実だけを、歴史の帰結として確認しておきましょう。

現在の MAME

2026年現在、MAME はほぼ月次のリリースを続けています。最新の安定版 0.288(2026年5月)のソースツリーには 3,300 を超えるドライバーソースファイルが含まれ、収録マシン数はクローンセットを含めて 4 万を超えます。開発は 2014年から GitHub 上で行われ、リリース管理は 2016年から Vas Crabb が担っています。

MAME の周囲には派生プロジェクトの生態系も広がっています。ブラウン管アーケードモニターへの正確な映像出力に特化した AdvanceMAME、旧バージョンをベースに低スペック機器で軽快に動くことを狙った MAME4ALL、アーケード機向けホームブルーゲームの保存を掲げる HBMAME。それぞれが本家とは異なる目的を掲げつつ、MAME のコードベースと ROM セットの体系を共有しています。

その影響はコミュニティの外にも及びます。Internet Archive は 2014年、MAME/MESS の JavaScript 移植を使って 900 タイトル超をブラウザで動かす「Internet Arcade」を公開しました。米国では図書館・博物館によるゲーム保存を目的とした DMCA の適用除外が 2003年以降段階的に認められ、エミュレーションによる保存は法制度からの追認を少しずつ獲得しつつあります。

クリスマスイブに書き始められた 5 本のゲームのためのプログラムは、30 年をかけて、人類の電子娯楽の歴史を丸ごとドキュメント化する事業になりました。次章からは、その「ドキュメント」の中身——MAME というソフトウェアが実際にどう作られているのか——を読んでいきます。

保存の技術と倫理 — 失われる前に

前章で見たとおり、MAME は自らを「ドキュメンテーションプロジェクト」と定義しています。しかし、ドキュメント化すべき対象——アーケード基板、ROM チップ、カスタム IC——は、今この瞬間も物理的に朽ちていきます。エポキシは劣化し、電池は切れ、マスク ROM のデータは読めなくなっていく。保存とは、この崩壊との時間との競争です。本章では、前章で歴史として触れた保存活動を、その「技術」と、それを取り巻く「倫理と法」の側から掘り下げます。エミュレーターのソースコードが完成する、はるか手前にある営みの話です。

時間との競争

保存活動を駆り立てているのは、切迫感です。アーケード基板は、そもそも長期保存を想定して作られていません。プラスチックは黄ばみ、はんだは劣化し、電解コンデンサーは液漏れする。そして何より、1980〜90 年代の基板の多くが抱える時限爆弾——スーサイドバッテリーがあります。

第4部で詳しく見るように、カプコン CPS-2 やセガのシステム16 は、コピー防止のために暗号鍵をバッテリーバックアップされた RAM に保持していました。電池が切れれば鍵は永久に失われ、基板は二度と起動しません。これは意図的なコピー対策でしたが、結果として「ゲームそのものが電池の寿命に縛られる」という、保存にとって最悪の事態を生みました。時間が経てば経つほど、救い出せる基板は減っていくのです。

約 1,700 枚ものユニークな基板を所有する日本の著名コレクター ShouTime は、電池切れで自壊していく基板を前に「保存しなければ今日にも失われる」状況を証言しています。事態の深刻さを物語るのは、メーカー自身がすでにデータを失っている例があることです。SNK の『怒III』日本版は長らく未ダンプで、SNK 自身が公式コレクションに収録できませんでした。作った当人が失い、コミュニティのダンプがそれを補う——保存が誰の手に委ねられているかを、これほど象徴する話はありません。

ダンプの技術と、その経済

保存の第一歩は、ROM の中身を吸い出すこと——ダンプです。その中心的な担い手が、前章でも触れた The Dumping Union です。彼らの活動は、技術であると同時に、風変わりな経済でもあります。

未ダンプの基板を世界中の中古市場から探し出し、メンバーの自費と寄付で購入する。ROM を吸い出し、その基板を転売して次の購入資金に充てる——この回転で活動を回しています。しかし彼ら自身が公言するとおり、「ほぼすべてのゲームで部分的、あるいは全額の損失が出る」持ち出しの活動です。希少な基板ほど高価で、ダンプ後の転売価格が購入価格を下回るのが常だからです。保存は、ボランティアの経済的犠牲の上に成り立っています。

コミュニティは「何がまだ失われているか」も系統的に追跡しています。未ダンプのゲームを一覧化した Wiki が維持されており、保存が「どこまで進み、あと何が残っているか」を可視化しています。ダンプは闇雲ではなく、欠けているピースを埋めていく組織的な作業なのです。

そして、吸い出したデータが「正しい」かどうかの検証も欠かせません。MAME 付属の romcmp は、単なるハッシュ照合ではなく、ダンプの物理的な健全性を診断するツールです。常に 0 や 1 に張り付いたビット(バス断線の兆候)、アドレス線のミラー、前半と後半が同一(容量取り違え)——こうした「ダンプ失敗の症状」を名前付きで検出します。保存とは、ビット列が一致していることだけでなく、そのビットを運んだ配線が健全だったことまで確かめる営みなのです。MAME が NO_DUMP(ダンプが存在しない)や BAD_DUMP(既知の不良ダンプ)というフラグを制度として持っているのも、「何が保存できていないか」を正直に記録するためです。

読めないチップを、開ける

ROM の中には、素直には読み出せないものがあります。コピー防止のために、内部にプログラムを封じ込めた保護 MCU やカスタム DSP です。バスに平文が出てこないこれらのチップを保存するには、物理的にこじ開けるしかありません——デキャップ(開封解析)です。

CAPS0ff のような専門プロジェクトがこれを担ってきました。チップのパッケージを化学的に溶かして開封し、顕微鏡でマスク ROM のビットパターンを直接読み取る。保護機構が邪魔をする場合は、レーザーで回路を無効化することもあります。これは半導体の解析技術そのものであり、費用も専門知識も要する営みです(CAPS0ff は Patreon の支援で活動していました)。

この地道な作業の成果は、エミュレーションの質に直結します。第3部で扱う QSound がその好例です。カプコンの立体音響チップの正体は AT&T の DSP と内蔵マスク ROM で、内部を読めなかった時代は聴感を頼りにした近似実装(HLE)でしのいでいました。ところが 2017 年にダイ写真から内蔵 ROM が吸い出されると、翌年にはその ROM を逐語実行する正確な実装(LLE)が可能になったのです。第2部(理論)で論じるように、デキャップとは「HLE の当て推量を、反証可能な事実に置き換える」営みにほかなりません。チップを開けることが、ドキュメントの正しさを一段引き上げるのです。

保存が、商業を追い越すとき

保存活動と、ゲームメーカーの商業的利益は、しばしば対立するものと見なされます。しかし近年、その関係が逆転する興味深い事例が現れています。保存が先に立ち、公式の商業展開があとを追うという現象です。

象徴的なのが Atari の『Akka Arrh』です。1982 年にロケーションテストに失敗して発売されなかったこのプロトタイプは、現存する筐体がわずか 3 台。2019 年、匿名の人物が ROM ダンプを MAME 開発チームに提供し、MAME に収録されました。この「非公式な保存」が呼び水となり、Atari は後にこのゲームをオリジナル版として公式コレクションに収録し、さらに 2023 年には現代版のリメイクまでリリースしています。埋もれていたゲームを、まず保存コミュニティが救い出し、それが権利者による公式の再生を促した——保存と商業の順序が転倒した好例です。

さらに一周した例もあります。第4部で見るように、CPS-2 やシステム16 の暗号鍵は、電池切れ基板を蘇生させるために実機へ書き戻せるようになりました。MAME に蓄積された鍵ファイル群が、そのまま実機修復のデータベースになったのです。エミュレーションのために解析したデータが、巡り巡って物理的な基板を救う道具になる。保存の営みが、デジタルとハードウェアの両方を横断して機能しているのです。

もっとも、保存と所有のあいだには緊張もあります。2023 年、唯一の現存例とみられる『セガソニック・ザ・ヘッジホッグ』海外版基板の所有者に、あるコレクターが「ダンプしないこと」を条件に 4,000 ドルでの買い取りを申し出ました。基板の希少性という資産価値は、複製可能になった瞬間に失われるからです。所有者はこの申し出を断り、翌年、基板がダンプ・保存されたことが明かされました。「独占的に所有する価値」と「広く共有する価値」——保存活動は、この二つの価値観のせめぎ合いの中にあります。

倫理と法のあいだで

保存活動は、著作権という法的な現実と常に隣り合わせです。ここで MAME プロジェクト自身の立場は、驚くほど明確で禁欲的です。

公式ドキュメントは、ROM をはじめとするメディアイメージについてこう述べています——それらはすべて著作物であり、権利者の許諾なく合法的に配布することはできない。「アバンダンウェア(abandonware、見捨てられたソフト)」ではないし、MAME が対応するどのソフトウェアについても著作権は失効していない。MAME は大規模な著作権侵害の道具として使われることを意図していない、と。エミュレーター本体(ソースコード)と ROM イメージを明確に切り分け、前者は自分たちの著作物として自由に配布するが、後者の違法配布には一切与しない——これが一貫した姿勢です。実機から自分で ROM を吸い出す行為の適法性についてすら、「これは不明確で、居住地による。多くの場合、元のメーカーから許諾を得る必要があるだろう」と、断定を避けた慎重な書き方をしています。

一方で、法制度の側も、ゲーム保存を少しずつ追認してきました。米国では、デジタルミレニアム著作権法(DMCA)のもとで、図書館・博物館によるゲーム保存を目的とした技術的保護手段の回避が、3 年ごとの見直しで段階的に認められてきました。2003 年には旧式フォーマットのソフトウェア保存の例外が(Internet Archive の主導で)、2018 年には博物館や図書館によるサーバー依存ゲームの復元の例外が認められています。ただし 2024 年には、保存したゲームへの「遠隔アクセス」を認める拡張は却下されました。保存は認めるが、誰でもアクセスできる形での提供には慎重——法はこの微妙な線引きの上を歩んでいます。

この「保存とアクセスのジレンマ」を体現するのが、Internet Archive の「Internet Arcade」です。2014 年、MAME/MESS を JavaScript に移植する技術を使って、900 タイトルを超えるアーケードゲームがブラウザ上で遊べるようになりました。文化機関が、保存したものを誰もがアクセスできる形で公開する——これは保存の理想形である一方、権利者の許諾という問題を常にはらんでいます。保存する(save)ことと、遊べるようにする(access)ことは、技術的には地続きでも、倫理的・法的には別の重みを持つのです。

何を保存するのか — ドキュメントか、体験か

最後に、より根源的な問いに触れましょう。私たちは結局、何を保存しようとしているのでしょうか。

MAME の答えは明快です——ハードウェアの動作を記述した「ドキュメント」を保存する。ソースコードこそがその成果物であり、ゲームが遊べることは正しさの検証手段にすぎない。この立場からすれば、保存の本体はソースコードという人間が読める知識であって、遊べることは副次的です。

しかし、保存には別の答え方もあります。「当時の体験そのものを再現する」ことを重視する立場です。その代表が、FPGA を使った MiSTer プロジェクトです。「FPGA はハードウェアだから本質的に正確だ」という言説とともに、しばしば MAME と対立するものとして語られます。

けれども実態は、対立というより相補です。MiSTer のアーケードコアは、MAME コミュニティが保存した ROM セットの体系(命名・構成・ハッシュ)をそのまま入力に使います。しかも MiSTer の開発者自身が、「FPGA コアの多くは MAME などのソフトウェア実装から適応されたものだ」と認めています。MAME の目的が「ドキュメント」で、その成果物が人間の読めるソースコードであるのに対し、MiSTer の目的は「体験の再現」で、成果物は実機同等の低遅延な動作です。両者はデータ(ROM セット)と知見(回路解析、デキャップの成果、そして MAME のソースというドキュメント)を共有する関係にあります。対立ではなく、同じ保存エコシステムの、異なる出力先なのです。

そして、この二つの立場は、どちらも同じ前提に立っています——元のハードウェアが失われても、そこに何があったかを後世に残すという意志です。ドキュメントとして残すのか、体験として残すのか。ソースコードで記述するのか、FPGA のロジックで再現するのか。方法は違えど、崩壊していく物理的な機械の前で「これを忘却させない」と決めた点で、両者は同じ営みの一部なのです。

保存は終わりません。基板は朽ち続け、まだデキャップされていないチップが残り、未ダンプのゲームが眠っています。だからこそ MAME の開発は 30 年経っても続き、毎月のリリースで少しずつ正確さを増していく。それは進歩というより、記憶を確かなものにし続ける、終わりのない作業なのです。

第2部 アーキテクチャ

数万種のハードウェアを一つのソフトウェアで再現するには、途方もない多様性を受け止める骨格が要ります。第2部では、MAME というフレームワークそのもの——デバイスツリー、時間管理、メモリシステム、そして四半世紀の移植を支えてきた OSD 層——を解剖し、あわせて「どこまで正確なら正しいのか」というエミュレーションの理論的な問いを掘り下げます。

コア設計 — MAME はどう作られているか

第1部で見たとおり、MAME の公式な自己定義は「ソースコードこそがドキュメントである」というものでした。第2部ではその主張を文字どおりに受け取り、MAME のソースコードを読んでいきます。対象は本書執筆時点の安定版 0.288 です。本章ではまずコアの骨格——ソースツリーの構造、デバイスモデル、ドライバーの記述、時間の管理、そしてセーブステートを見ます。

巨大さの構造 — ソースツリーの歩き方

まず全体の地形を掴みましょう。src/ 直下の主要ディレクトリとファイル数は次のとおりです。

ディレクトリ 役割 ファイル数
src/emu エミュレーションコア 258
src/devices 再利用可能なデバイス群(CPU、サウンド、周辺 IC) 7,509
src/mame 個々のシステムのドライバー 7,947
src/osd OS 依存層(Windows / SDL / macOS) 615
src/lib 汎用ライブラリ(netlist、メディアフォーマット等) 826
src/frontend 内蔵 UI、コマンドライン処理、Lua エンジン 153

この数字の偏りが、MAME の設計を雄弁に物語っています。エミュレーターの「本体」であるコアはわずか 258 ファイル。残りの大部分は、7,500 ファイルの部品カタログ(devices)と、8,000 ファイルの配線記述(mame)です。小さなコアの上に、Z80 も 68000 も 74LS259 ラッチも同列の「部品」として蓄積され、ドライバーがそれらを基板ごとに配線する——この構図さえ頭に入れば、巨大なツリーで迷うことはありません。

src/mame の下は 353 のディレクトリに分かれ、原則はメーカー名(atari/capcom/konami/sega/……)ですが、実際には「ハードウェアファミリー」単位です。たとえばパックマンはナムコのゲームなのに namco/ ではなく専用の pacman/ ディレクトリを持ちます。互換基板や海賊版が膨大なため、基板ファミリーとして独立しているのです。ほかに、動作しないが記録のために登録だけされたシステムを集めた skeleton/(308 ファイル)という象徴的なディレクトリもあります。エミュレーションが未完成でも、ハードウェアが存在した事実は記録する——保存プロジェクトらしい場所です。

すべてはデバイスである

MAME コアの中心概念は「デバイス」です。エミュレート対象のあらゆる構成要素——CPU、サウンドチップ、画面、そして 8 ビットのラッチ IC 1 個まで——が device_t の派生クラスとして表現されます。

デバイスは親子関係を持ち、マシン全体は 1 本のデバイスツリーを構成します。そして重要なのは、ツリーの根もまたデバイスだということです。src/emu/driver.h にはこうあります。

class driver_device : public device_t

ドライバー(=システム全体)自体がデバイスの一種であり、パックマン基板は「pacman_state というルートデバイスの下に、Z80 デバイス、画面デバイス、サウンドデバイスがぶら下がった木」なのです。この一様性のおかげで、すべての部品が同じプロトコル(device_start / device_reset などのライフサイクル、後述するセーブステート登録)に従い、7,500 ファイル分の部品カタログが基板を問わず再利用できます。

ツリー内のデバイスは Unix のパスに似たタグで識別されます。区切りは : で、":maincpu" はルート直下の maincpu、"^screen" は親デバイスの下の screen という具合です。デバイス間の参照はこのタグを使って宣言的に解決されます。

// 起動時に必ず解決される依存(無ければ致命的エラー)
required_device<cpu_device> m_maincpu;
// 見つからなければ nullptr のまま進む
optional_device<ls259_device> m_mainlatch;

コンストラクターで m_maincpu(*this, "maincpu") とタグを渡しておくと、起動時の解決フェーズでツリーが検索され、型チェック付きで結び付けられます。C++ の生ポインタの受け渡しではなく「タグで名指しして、フレームワークが解決する」——依存性注入に似た仕組みが、30 年物の C++ コードベースの秩序を保っています。

機能横断的な性質は device_interface という mix-in で表現されます。CPU デバイスの宣言が典型です。

class cpu_device : public device_t,
    public device_execute_interface,  // 実行できる
    public device_memory_interface,   // メモリ空間を持つ
    public device_state_interface,    // レジスタを公開する
    public device_disasm_interface    // 逆アセンブルできる

「CPU である」とは、これら 4 つの能力を持つデバイスのことである——多重継承をここまで教科書どおりに使う現役コードは、なかなか見られません。

デバイスの起動処理には、いかにも実務的な設計が見られます。数十個のデバイスを起動する順序は、依存グラフを事前に解析するのではなく、例外とリトライで解決されるのです。あるデバイスが device_start() の中でまだ初期化されていない依存先に触れる場合、device_missing_dependencies 例外を投げます。するとフレームワークはそのデバイスを後回しにして次へ進み、全体を繰り返します。1 周しても起動できるデバイスが増えなければ、循環依存として致命的エラーになります。理論的な美しさより「動く単純さ」を取る、30 年のあいだ数千人の貢献を受け入れてきたコードベースらしい選択です。

ドライバーを読む — パックマン基板を C++ で記述する

では、1 枚の基板がどう記述されるのかを src/mame/pacman/pacman.cpp(9,173 行)で見ていきます。ドライバーの中核は、ステートクラスのメンバー関数として書かれるマシンコンフィギュレーションです。

void pacman_state::pacman(machine_config &config)
{
    // 基本のマシンハードウェア
    Z80(config, m_maincpu, 18.432_MHz_XTAL / 6);
    m_maincpu->set_addrmap(
        AS_PROGRAM, &pacman_state::pacman_map);
    m_maincpu->set_irq_acknowledge_callback(
        FUNC(pacman_state::interrupt_vector_r));

1 行目から MAME らしさが凝縮されています。18.432_MHz_XTAL / 6 は「基板上に 18.432 MHz の水晶発振子が実在し、それを 6 分周して CPU に供給している」という物理的事実の記述です。MAME は周波数を裸の数値で書かせず、実在する水晶のカタログと照合可能な XTAL 型を強制します。ソースコードを基板の資料として成立させるための、型システムによる規律です。

画面も 1 個のデバイスです。

SCREEN(config, m_screen, SCREEN_TYPE_RASTER);
// 水平カウントは 128→511、HBLANK は 144〜240
m_screen->set_raw(18.432_MHz_XTAL / 3,
                  384, 0, 288, 264, 0, 224);

set_raw() にはピクセルクロックと水平・垂直の総カウント数、表示範囲を実測値で渡します。「60fps」のような丸めた値ではなく、ブラウン管を駆動していた同期信号のパラメーターそのものからフレームレートが導出されるのです。

さらに、基板上の汎用ロジック IC までデバイスとして現れます。

// 74LS259(基板では 8K の位置、または 7K の 4099)
LS259(config, m_mainlatch);
m_mainlatch->q_out_cb<0>().set(
    FUNC(pacman_state::irq_mask_w));
m_mainlatch->q_out_cb<1>().set("namco",
    FUNC(namco_wsg_device::sound_enable_w));
m_mainlatch->q_out_cb<7>().set(
    FUNC(pacman_state::coin_counter_w));

74LS259 は 8 ビットのアドレサブルラッチ、つまりただの汎用ロジックです。コメントの「at 8K or 4099 at 7K」は基板上のソケット位置。その出力ピン 1 本 1 本に「Q0 は割り込みマスクへ、Q1 はサウンドイネーブルへ、Q7 はコインカウンターへ」とコールバックを配線しています。ここまで来ると、マシンコンフィギュレーションとは回路図のネットリストを C++ で書き直したものだ、という実感が湧いてくるはずです。

ROM の定義も見てみましょう。

ROM_START( pacman )
    ROM_REGION( 0x10000, "maincpu", 0 )
    ROM_LOAD( "pacman.6e", 0x0000, 0x1000,
              CRC(c1e6ab10) SHA1(e87e059c...) )
    ROM_LOAD( "pacman.6f", 0x1000, 0x1000,
              CRC(1a6fb2d4) SHA1(674d3a7f...) )

ファイル名 pacman.6e は、実チップが刺さっていた基板上のソケット位置(6E)です。各 ROM は CRC32 と SHA-1 の両方で検証され、不一致なら起動時に警告が出ます。ダンプが存在しないチップは NO_DUMP、既知の不良ダンプは BAD_DUMP と明示的にフラグ付けされます。「何が保存できていないか」まで制度として記録する誠実さは、保存プロジェクトならではです。

操作系も宣言的に記述されます。入力ポートの定義から DIP スイッチの部分を見てみましょう。

PORT_START("DSW1")
PORT_DIPNAME( 0x03, 0x01, DEF_STR( Coinage ) )
    PORT_DIPLOCATION("SW:1,2")
PORT_DIPSETTING( 0x03, DEF_STR( 2C_1C ) )
PORT_DIPSETTING( 0x01, DEF_STR( 1C_1C ) )
PORT_DIPNAME( 0x0c, 0x08, DEF_STR( Lives ) )
    PORT_DIPLOCATION("SW:3,4")
PORT_DIPSETTING( 0x08, "3" )

コイン設定や残機数を決める基板上の DIP スイッチが、ビットマスクと選択肢の表で記述されています。PORT_DIPLOCATION("SW:1,2") は物理的なスイッチブロックの名前と番号で、MAME の UI にそのまま表示されます。ジョイスティックの IP_ACTIVE_LOW という指定も、実基板の入力線がプルアップされていて GND に落とすと反応する負論理であることの記録です。ここでも「設定画面を作る」のではなく「基板を記述したら設定画面がついてきた」という順序になっています。

最後に、システムはこの 1 行でカタログに登録されます。

GAME( 1980, pacman, puckman, pacman, pacman,
      pacman_state, empty_init, ROT90,
      "Namco (Midway license)",
      "Pac-Man (Midway)", MACHINE_SUPPORTS_SAVE )

この GAME マクロの実体は、発売年・メーカー・親子関係・ROM 定義・コンフィグ関数へのポインタを束ねた game_driver 構造体の静的な定数定義にすぎません。エミュレーションコードとメタデータが完全に分離されているため、mame -listxml のような全カタログの機械可読出力は、エミュレーションを一切実行せずに生成できます。世のフロントエンドや ROM 管理ツールのエコシステムは、この設計の上に成り立っています。

3 番目の引数 puckman は親セットの指定です。ここには少し驚く事実が潜んでいます。MAME の正史では日本オリジナルの『パックマン』こと puckman が親セットであり、世界的に有名な北米 Midway 版 pacman は、数ある海賊版たちと同格の「クローンセット」なのです。クローンは ROM 定義だけを持ち、マシン構成・入力定義はすべて親と共有します。ROM ファイルすら、クローンの zip に無いものは親セットから自動探索されます。pacman.cpp が 1 ファイルで 100 を超えるシステムを登録できるのは、この徹底した差分管理のおかげです。

CPU から見えるメモリ空間の記述——アドレスマップ——も、ドライバーのもう一つの見せ場です。ただしこれはメモリシステムの話と一体なので、次章「メモリシステム・ビルド・OSD」でまとめて扱います。本章では、コアのもう一つの心臓である時間の管理へ進みましょう。

時間の管理 — アト秒、ラウンドロビン、量子

複数の CPU と無数のタイマーを、どうやって矛盾なく進行させるか。ここが汎用エミュレーターの心臓部です。

MAME の時刻は attotime という 96 ビット固定小数点値で表現されます。32 ビットの秒と、64 ビットのアト秒(10⁻¹⁸ 秒)です。アト秒は誇張ではなく設計上の必然で、10¹⁸ = (10⁹)² という値は 64 ビット整数に 1 秒分が余裕で収まり、しかも整数ヘルツのクロック周期を誤差ゼロの整数で表せます(4 MHz の 1 サイクルはちょうど 250,000,000,000 アト秒)。独立した水晶で駆動される複数のクロックを丸め誤差なしに合成するための道具です。ヘッダーのコメント自身が「常軌を逸した精度に見えるだろうが(This may seem insanely accurate, but…)」と自己ツッコミを入れています。ちなみに秒部の上限は 10⁹ 秒 ≈ 31.7 年で、これを超える時刻は「永遠(never)」として飽和します。

実行モデルは意外なほど素朴です。running_machine::run() のメインループは、本質的にこれだけです。

while (!m_exit_pending)
    m_scheduler.timeslice();

ここに「フレーム」の概念はありません。VBLANK も画面描画も、スケジューラーから見ればただのタイマーイベントの一つです。画面デバイスが VBLANK タイマーのコールバックで描画処理を呼び、次のフレームに向けて自分自身を再アームする。1990 年代の「フレーム単位で全部を回す」エミュレーターからの脱却こそが、MAME コア設計の原点でした。

timeslice() の中身はラウンドロビンです。スケジューラーは「次のタイマー発火時刻」か「現在の量子(quantum)」の近い方を目標時刻に定め、全 CPU を順番に、それぞれ目標時刻までのサイクル数だけ実行します。ここに汎用エミュレーターの本質的な妥協があります。CPU コアは命令の途中で止まれないため、各 CPU は目標時刻を少しオーバーシュートします。つまり任意の瞬間において、複数の CPU のローカル時刻は厳密には一致していません。ある CPU は常に他の CPU より少し未来を実行しているのです。

これで何が壊れるのか。量子のデフォルトは 1/60 秒です。CPU A がスライスの冒頭で共有メモリにコマンドを書いても、CPU B がそれを見るのは自分の番が来てから——最悪 1 量子(16.7 ミリ秒)遅れます。多くのゲームはこれで平気です(サウンドコマンドが 1 フレーム遅れても人間には分かりません)。しかしメイン CPU と保護 MCU がタイトなタイムアウト付きでハンドシェイクする基板では、この遅延がプロトコルを破綻させます。

MAME コアはこの問題への道具を段階的に用意しています。量子を細かくする set_maximum_quantum()。指定 CPU の最短命令 1 個分まで量子を詰めて実質的な命令単位インターリーブを実現する set_perfect_quantum()(正確ですが、コンテキスト切り替えが数千倍になる代償を払います)。そして最も基本的な作法が、タイマーを同期バリアとして使うことです。CPU 間の通信路である 8 ビットラッチの実装(gen_latch.cpp)を見ると、書き込みは直接値を格納せず、遅延ゼロのタイマーを経由します。タイマーは必ずタイムスライスの境界——全 CPU の時刻が揃った地点——で発火するため、コールバックの中から見える世界は一貫しているのです。たった 1 バイトのラッチ書き込みのたびにタイマーを 1 個確保する。正確さのために MAME が払っているコストが、最も可視化された箇所です。

セーブステート — 規約と強制

どんな状態でもスナップショットして復元できる機能は、デバイスモデルに深く組み込まれています。各デバイスは device_start() で保存すべきメンバー変数を登録します。

void generic_latch_8_device::device_start()
{
    save_item(NAME(m_latched_value));
}

興味深いのはその強制のしかたです。実行デバイスが save_item を 1 件も登録しないと、MAME は起動すら拒否します(「Device did not register any state to save!」という致命的エラー)。ポインタなど保存不能な型はテンプレートの static_assert がコンパイル時に弾きます。さらに、コールバックだけ持つ「匿名タイマー」が生きているとセーブできないため、それを作る古い API 自体が deprecated 指定され、根絶が進められています。規約を口頭で守らせるのではなく、コンパイルエラーと起動時エラーで守らせる——大規模 OSS の実務的な知恵です。この決定論的な状態管理が、のちに論じる「セーブステート・リプレイ・リグレッションテスト」のすべての土台になっています。

コアの骨格——デバイスツリー、ドライバー記述、時間管理、セーブステート——を見てきました。次章では、本章で保留したメモリシステムの詳細と、この巨大なコードベースを 30 年間あらゆるプラットフォームで動かし続けてきたビルドシステムと OSD 層を扱います。

メモリシステム・ビルド・OSD

前章ではコアの骨格——デバイスツリー、ドライバー記述、時間管理、セーブステート——を見ました。本章では、そこで保留したメモリシステム(CPU がメモリをどう見るか)を掘り下げ、続いてこの巨大なコードベースを 30 年間あらゆるプラットフォームで動かし続けてきたビルドシステムOSD 層を扱います。

アドレスマップ — 回路図の転写

CPU から見えるメモリ空間の記述が、ドライバーの見せ場のひとつです。パックマンのメモリマップを見てみましょう。

void pacman_state::pacman_map(address_map &map)
{
    map(0x0000, 0x3fff).mirror(0x8000).rom();
    map(0x4000, 0x43ff).mirror(0xa000).ram()
        .w(FUNC(pacman_state::pacman_videoram_w))
        .share("videoram");
    map(0x5000, 0x5007).mirror(0xaf38)
        .w(m_mainlatch,
           FUNC(ls259_device::write_d0));
    map(0x5000, 0x5000).mirror(0xaf3f).portr("IN0");
}

メソッドチェーンによる宣言的な記述です。.rom() は ROM 直結、.ram() は RAM、.w(FUNC(...)) は書き込みハンドラー、.portr("IN0") は入力ポートの読み出し。ビデオ RAM のエントリーは「読みは RAM 直結、書きだけフックしてタイルマップに変更を通知する」典型パターンです。

注目してほしいのは .mirror() です。mirror(0x8000) は「アドレスビット 15 が同じ内容の繰り返しになる」という意味で、これは実基板でそのアドレス線がデコード回路に接続されていないことの表現です。0x5000 番地が「読むと入力ポート、書くとラッチ」という非対称な配線になっているのも、実基板のデコード回路の写しです。アドレスマップとは、基板のアドレスデコーダー(どのアドレス線を見て、どのチップを選択するか)をほぼ 1:1 で写し取ったものなのです。

宣言的な記述は、性能のためでもあります。起動時にアドレスマップは基数木(radix tree)状のディスパッチテーブルにコンパイルされ、8 ビット機なら 1〜2 回のテーブル引きでハンドラーに到達します。CPU コア側はさらに直近アクセス範囲をキャッシュし、連続アクセスなら比較 2 回まで削ります。ミラーはテーブル構築時に展開されるので実行時コストはゼロ。「宣言的に書かせて、事前にテーブルへコンパイルする」という、言語処理系にも似た設計です。

このディスパッチ機構の内部では、終端のハンドラーも、アクセスを別のハンドラーへ転送する中間ノードも、データ幅の変換を行うノードも、すべて handler_entry という参照カウント付きのオブジェクトとして、一様な木を構成しています。アドレス幅に応じて木の段数が決まり(14 ビット以下なら一段、64 ビット CPU でも最大四段)、テンプレートによってアクセス経路が型ごとに特殊化されたコードにコンパイルされる——数千種の異なるバスを一つの機構で扱いながら性能を出す工夫です。

余談ですが、公式のメモリシステム文書には、ROM 領域のポインタが常に u8* を返す仕様について「これは歴史的経緯であり、今さら修正はほぼ不可能(historical and pretty much unfixable at this point)」という率直な自嘲が書かれています。30 年物のコードベースの現実も、また正直にドキュメント化されているわけです。

メモリシステムは、ROM のロードとも一体です。ドライバーの ROM_START で宣言された各 ROM は、"maincpu""gfx1" といったタグを持つメモリ領域(リージョン)にロードされ、アドレスマップの .rom() が名前でそれと結び付きます。ロード時には CRC32 と SHA-1 で検証され、クローンセットに欠けている ROM は親セットから——しかもファイル名だけでなく CRC でも——探索されます。「宣言的に書かれたメモリマップと ROM 定義が、起動時に高速なテーブルと検証済みのデータに変換される」——これがメモリサブシステムの全体像です。

1つのコードベースが30年動き続ける理由 — OSDとビルド

MAME は 1997年の MS-DOS 版から、Windows、macOS、Linux、そしてブラウザ(WebAssembly)まで移植され続けてきました。それを支えるのが OSD(OS Dependent)層です。エミュレーションコアが知っているのは osd_interface という純粋仮想クラスだけで、描画(BGFX / Direct3D / OpenGL / SDL)、サウンド(WASAPI / CoreAudio / PipeWire……)、入力はすべてモジュールとして差し替わります。CRT モニター特有の滲みや歪みを再現する BGFX / HLSL のシェーダーチェーンも、この層に実装されています。コアは「画面を描け」「音を出せ」という抽象的な要求だけを出し、それを実際の OS の API に翻訳するのは OSD の仕事——この一枚のインターフェースによる分離が、四半世紀にわたる移植を可能にしてきました。

ビルドシステムは Lua で記述された GENie(premake 系)を使い、make がまず同梱の GENie 自体をビルドしてからプロジェクトファイルを生成する、というブートストラップ構成です。読者が手を動かすなら、部分ビルドが最適の入口でしょう。

make SUBTARGET=pacem \
    SOURCES=src/mame/pacman/pacman.cpp REGENIE=1

これでパックマン系ドライバーだけを含む小さな実行ファイルが作れます。数万システム分のビルドを待つ必要はありません。

なお、前章で見てきた「素直な C++」によるドライバー記述は、比較的新しい姿です。かつてのマシンコンフィグは MCFG_CPU_ADD("maincpu", Z80, 18432000/6) のようなマクロ DSL で書かれていました。0.194(2018年)から 0.212(2019年)にかけてほぼ全ドライバーが現在の形式に書き換えられ、2022年の 0.246 ではソースツリー全体がメーカー別ディレクトリへ機械的に再編されています。数千ファイル規模の破壊的リファクタリングを、月次リリースを止めずに段階的にやり遂げる——これも 30 年物プロジェクトの生存技術です。

第2部を通じて、コアの設計(デバイス、時間、状態)、メモリシステム、そしてビルドと OSD という土台を見てきました。次章からの第3部では、このフレームワークの上に載る「部品」たち——CPU コア、ビデオ、サウンド、そして CPU が存在しない回路のエミュレーション——を見ていきます。

エミュレーションの理論 — どこまで正確なら「正しい」のか

第2部のここまでで、MAME のコアがデバイスをどう組み立て、時間をどう刻むかという実装を見てきました。本章では視点を一段引き上げ、その実装の底に流れる原理を考えます。そもそも「正確なエミュレーション」とは何を意味するのか。どこまで再現すれば「正しい」と言えるのか。HLE と LLE はどう使い分けるのか。なぜ決定論が要るのか。そして——完全な再現が原理的に不可能だとしたら、私たちはどこで妥協の線を引くのか。答えの多くは、これまで見てきた具体例(QSound、保護 MCU、パックマンの乱数、ネットリスト)の中に既にあります。本章はそれらを理論として束ね直す試みです。

「正確さ」は反証可能な主張である

第1部で見たとおり、MAME は自らを「ドキュメンテーションプロジェクト」と定義し、「ソフトウェアが動くという事実は、主としてドキュメントの正確さを検証するために役立つ——ほかにどうやって、ハードウェアを忠実に再現できたと証明できるだろうか?」と述べています。

この一文を、検証の理論として読み直してみましょう。MAME のソースコードは「このハードウェアはこう動く」という仮説です。そして、その仮説の上で実際のゲーム ROM が実機どおりに動くことが、仮説の検証になります。言い換えれば、ゲームが正しく動く限り、その仮説は「まだ反証されていない」。逆に、どこかで挙動が実機とずれれば、それは仮説への反証であり、コードを書き換える理由になります。

これは科学の方法論——完全な正しさは証明できず、反証を潰し続けることで確からしさを高めていく営み——によく似ています。「正確さ」は達成して終わる目標ではなく、反証可能な主張として絶えず検証にさらされ続けるものなのです。この視点は、本章を通じての背骨になります。

正確さの三つの解像度

では、その「正確さ」には段階があります。MAME のコードから、少なくとも三つの解像度が読み取れます。

第一は「動く」レベルです。CPU コアが 1 命令を不可分に実行し、その所要 T ステート(クロック数)分だけ時計を進める。命令の途中で他のデバイスが割り込む余地はありません。多くのゲームはこれで十分に動きます。ただし、命令の内部で起きるバス操作の順序やタイミングは無視されます。

第二はサイクル精度(cycle-accurate)です。MAME の Z80 コアは、命令内部の T ステートまでモデル化しています。ウェイトステートや、メモリを要求しないサイクル(no-MREQ)のコールバックを持ち、「命令の何番目の T ステートで、どのアドレスにアクセスするか」まで外部に見せます。これは ZX Spectrum のメモリコンテンション(ビデオ回路と CPU が RAM を奪い合ってタイミングがずれる現象)のような、命令をブラックボックスにしていては再現できない挙動のために必要です。

第三はサブサイクル、回路レベルです。T ステートよりさらに下、電圧や電流の連続時間シミュレーション。MAME ではネットリスト(後述)がこれにあたり、トランジスタや抵抗・コンデンサーの応答時間、クロックの立ち上がり途中の過渡まで扱います。

興味深いのは、「では最高解像度で全部やればいいのか」という問いに、MAME のコード自身が「否」と答えていることです。そもそも「Z80 を正確にエミュレートする」という文すら、突き詰めると曖昧なのです。Z80 コアのヘッダーには、こんな告白があります。

This Z80 emulator assumes a ZiLOG NMOS model.

なぜ「仮定」なのか。同じ Z80 でも、製造プロセス(NMOS か CMOS か)によって挙動が違うからです。OUT (C),0 という命令は NMOS では 0 を、CMOS では 0xFF を出力する。しかも SCF/CCF 命令の未定義フラグは、近年の研究によれば「直前の命令がフラグレジスタに触れたかどうか」に依存する——つまり文脈依存の副作用まである。「正確な Z80」は、どの製造元の、どのプロセスの、どの個体を基準にするかを決めて初めて定義できるのです。MAME は「既定で NMOS モデルを採る」と明示することで、この線引きを設計判断として引き受けています。正確さとは、基準を定めることから始まるのです。

HLE と LLE — 解剖するか、振る舞いを真似るか

エミュレーションには、大きく二つのアプローチがあります。

LLE(Low-Level Emulation)は、対象チップの内部構造——命令セット、内蔵 ROM のコード、レジスタ——をそのまま実行します。「何をするか」ではなく「どう作られているか」を再現する、いわば解剖です。HLE(High-Level Emulation)は、チップを外から見た振る舞いだけを、ホスト側のコードで真似ます。内部がどう動くかは問わず、入力に対して正しい出力を返せばよい。

この二つには明確なトレードオフがあります。LLE は内部の完全な情報(内蔵 ROM や回路図)を必要とし、実行も遅い代わりに、原理的に実機と同一で、未知の入力にも追従します。HLE は外部からの観察だけで作れて速い代わりに、観察した範囲でしか正しくなく、想定外の入力やタイミングで破綻する危険があります。決定的なのは検証可能性です。HLE は「たまたま合っているだけかもしれない」という疑いを、原理的に晴らせません。

この対立は、これまで見てきた実例の中に鮮やかに現れています。

最も教科書的なのが QSound です(第3部)。この立体音響チップの正体は AT&T の DSP と内蔵マスク ROM でした。1990 年代、内部を知らないコミュニティは聴感を頼りに「それっぽい音」を作りました——純粋な HLE です。2017 年、チップがデキャップされて内蔵 ROM が吸い出され、翌年にはサイクル精度の DSP コアが実装されて、現在は内蔵 ROM を逐語実行する LLE が既定になっています。そして興味深い後日談として、その LLE を教師に、逆アセンブルに基づいて「LLE と同じ出力を出す」検証済みの HLE も書かれました。ここから分かるのは、HLE と LLE は敵対関係ではないということです。LLE が正解を与え、それを基準に HLE を再校正する——情報が増えるにつれ、当てずっぽうが逐語実行に、あるいは根拠のある近似に置き換わっていく。1 個のチップの上に、保存技術の時間発展そのものが刻まれているのです。

対照的に、HLE の限界を作者自身が告白している例もあります。アイレムの保護 MCU をめぐる、あの有名なコメント——「以下の保護ルーチンは完全なでっち上げである。ゲームは動くが、本物のコードから導出したものではない」(第3部)。メイン CPU と MCU が共有 RAM でやり取りする保護を、当初は応答を創作して切り抜けました。HLE の極北です。後に多くのセットは実チップのダンプに移行し、同じソースファイルの中に HLE と LLE が共存しています。「でっち上げ」という言葉が示すのは、HLE が本質的に証明できない近似だということです。だからこそ MAME には、そうした埋め合わせを「これは仮のものだ」と申告するフラグ文化があります。

では、いつ HLE が許され、いつ LLE が要るのか。原理的な線引きはこうです。HLE が許容されるのは、内部を読む手段がない(未デキャップ、消失した個体)か、逐語実行が遅すぎて実用にならず、かつ外部から見える挙動が単純で網羅できるとき。逆に LLE が必要になるのは、チップがプログラム可能でゲームごとに異なる未知のコードを流し込む場合(観察では全パターンを尽くせない)、メイン CPU とサイクル単位で密結合して応答のタイミング自体が意味を持つ場合、そして保存目的として「ドキュメントの正しさ」を証明する必要がある場合です。

決定論 — ホスト依存を引き算する

MAME のもう一つの隠れた原理が、決定論です。同じ初期状態と同じ入力列からは、必ず同じ結果が得られる——この性質は、三つの重要な機能の前提になっています。セーブステート(状態を書き出して完全復元できるのは、そこから先が入力だけで一意に決まるから)、ネットプレイのロールバック(各クライアントが同じ入力から同じ状態を再計算できないと同期が保てない)、そしてリグレッションテスト(同じ入力の再生で挙動の変化を機械的に比較できる)。

決定論を実現するとは、要するに状態空間からホスト依存の偶然を追い出すことです。MAME のセーブステートの原理がそれを物語ります。保存するのは CPU のレジスタ、RAM の内容、タイマー、選択中のメモリバンク番号——といった論理的状態です。しかしポインタそのものは保存しません。ポインタはホスト側のアドレスで、実行のたびに変わる非決定的な値だからです。保存すべきは「バンク番号」という論理であって、それを指す物理ポインタは復元時に再構築する。決定論の設計とは、何が本質的な状態で、何がホストの偶然なのかを分離する作業に等しいのです。

その徹底ぶりを最もよく示すのが、乱数の扱いです。MAME のグローバルな乱数生成関数は、ホストの乱数を一切使いません。固定シードの線形合同法(LCG)です。

m_rand_seed(0x9d14abd7) // 固定シード
save().save_item(NAME(m_rand_seed)); // シードもセーブ対象
u32 running_machine::rand() {
    // 古典的 LCG
    m_rand_seed = 1664525 * m_rand_seed + 1013904223;
    return (m_rand_seed >> 16) | (m_rand_seed << 16);
}

シードは 0x9d14abd7 に固定され、しかもシード自体がセーブステートに含まれます。だから乱数列はリプレイ間で完全に再現でき、セーブとロードをまたいでも途切れません。「エミュレーターはホストのエントロピーや時刻を挙動に混ぜてはならない」という決定論の鉄則が、この数行に凝縮されています。

同じ思想は未初期化メモリにも及びます。実機では電源投入時の RAM の内容は不定ですが、MAME の RAM デバイスは既定で全体を固定値 0xFF で埋めます。「不定」を決定論的な既定値に写像しているのです。

そして重要なのは、ゲーム内部の乱数についてです。実機の乱数生成器がそもそも決定論的なら、それをそのまま実行すれば再現性は自動的に得られます。第5部で見るパックマンがその好例で、パワーエサ中のゴーストの「ランダムな」動きすら、レベル開始ごとに同じシードでリセットされる疑似乱数でした。真の乱数はゲームの中に存在しない。だからこそパターン攻略が成立します。ここに決定論の本質があります——決定論はエミュレーターが足す機能ではなく、元のハードウェアが決定論的な機械だったことの帰結なのです。エミュレーターの仕事は、新たな非決定性を持ち込まないこと、ホスト由来の偶然を系統的に引き算することに尽きます。

時間を一本に畳む

第2部でスケジューラーの実装を見ましたが、その根底にある困難を原理として捉え直しておきましょう。実機では CPU も音源もビデオも MCU も、物理的に同時に動いています。ところがホストの CPU は逐次実行しかできません。並行する複数の時間軸を、1 本の逐次実行に直列化しなければならない——これがエミュレーションの本質的な難しさです。

Aaron Giles(第4代コーディネーター)は、MAME 初期の重要な改良として、60Hz 固定のタイミングから離れた精密な時間機構の導入を挙げています。かつては全イベントがゲームの 60Hz のリフレッシュに縛られていたが、複数の CPU やデバイスが絡むとはるかに高い精度が必要だと明らかになった、と。ここに直列化の粒度の問題があります。粒度が粗いと(たとえば 1 フレームずつまとめて進めると)、フレーム内で相互作用するチップ同士が「相手の最新状態」を見られず、密結合したチップほど破綻します。かといって粒度を細かくすれば、チップ間の切り替え回数が増えて遅くなる。時間解像度は、忠実度と性能のあいだのダイヤルなのです。どこに合わせるべきかは、チップ同士の結合の強さで決まります。

賢いのは、そのダイヤルを動的に回す発想です。一定量ずつ全デバイスを進める「固定ステップ」に対し、「次に何かが起きる時刻」までジャンプする「イベント駆動」は、相互作用が起きる瞬間にだけ解像度を上げます。MAME のタイマーとスケジューラーはこの発想に立っており、ネットリストの内部もイベント駆動のキューで解かれます。何も起きない区間を無駄に刻まず、必要なときだけ精密になる——忠実度を保ちつつ無駄を省く、洗練された折り合いです。

検証問題 — 「正しくできた」をどう示すか

「正確にエミュレートできた」ことを、どう証明すればよいのでしょうか。手段は大きく三つあります。

ひとつはテスト ROM です。MAME には Z80 の命令網羅テスト「ZEXALL」のドライバーが同梱されています。各命令を多数のマシン状態で実行し、結果の状態列に 32 ビットの CRC をかけて、実機の Z80 で経験的に得られた期待値と照合する——未文書の未定義フラグまで検査します。ただし、ここに検証の限界も現れます。先に見たとおり Z80 は NMOS と CMOS で挙動が違うので、期待値は「基準とした個体」に依存します。テストが通ることは「基準機と一致する」ことの証明であって、「あらゆる Z80 と一致する」ことの証明ではありません。

ふたつめは実機・基板との突き合わせです。2D のドット絵には「正解画像」がありますが、3D にはそれがありません。ナムコ System 22 のドライバーには BTANB(bug that’s also on the real board——実機にもあるバグ)という節があり、リッジレーサーの橋のケーブルが柵を貫通する現象は「実機でも起きる」と記録されています。ここで検証は、「エミュレーターのバグ」と「実機の仕様、あるいは実機のバグ」を切り分ける作業になります。実機で起きるバグを”直して”しまうのは、むしろ不正確なのです。正解の定義そのものが調査対象になる——これが検証を一筋縄でいかせない理由です。

みっつめはデキャップ、ダイ写真による内部の直接観測です。QSound の内蔵 ROM やセガ Model 1 のジオメトリ ROM のように、チップを物理的に開封して内部を読み出し、LLE の入力にする。これは HLE の当て推量を、反証可能な事実に置き換える営みです。

三つの手段に共通するのは、いずれも「外部の真理」に照らして仮説を検証している点です。冒頭で述べた反証主義的な検証観——ソースコードは仮説、動くことは反証されていないことの証、テスト不一致や実機との差異やデキャップ知見は反証——が、ここで具体的な方法論として立ち現れます。Aaron Giles が、リリース版のビルドにもデバッガーを残した理由も同じ精神からでした。MAME の目的がハードウェアの内部動作をドキュメント化することにある以上、リリース版でも誰もが内部を覗ける——つまり誰もが反証できる——状態を保つのが理にかなっている、と。

近似は必然である

最後に、これらすべての底にある事実を確認しましょう。完全な再現は、原理的に不可能です。

実機は、数万から数百万個のトランジスタからなる連続時間のアナログ回路です。これをトランジスタ単位・連続時間で解くのは非現実的で、1 フレーム分の計算に膨大な時間がかかってしまいます。したがって、どこかで必ず抽象化の線を引くしかない。先に見た「動く/サイクル精度/回路レベル」という解像度の階層は、まさに「どこで離散化し、どこで抽象化するか」の選択肢だったのです。

抽象化の線を最も下へ引いた例が、第3部で見たネットリストです。各素子を「抵抗+電圧源+電流源」に正規化し、各ノードでキルヒホッフの電流則から行列方程式を立て、非線形素子はニュートン・ラフソン反復で解く——ほとんど回路シミュレーターの SPICE そのものです。しかし、これで扱えるのは CPU を持たない古いディスクリート回路のゲーム(ポンなど)と、一部の音声回路だけです。素子数が少ないからこそ現実的な時間で解ける。全ゲームをこの解像度でエミュレートするのは、まったく非現実的です。

この事実が、近似の本質を教えてくれます。MAME は全系を一律の解像度でエミュレートしません。疎結合で単純な部分は命令アトミックに(速く)、密結合でプログラム可能な部分はサイクル精度の LLE で、CPU を持たないアナログ回路はネットリストで(回路レベルで)。線をどこに引くかは、ゲームの結合構造と、得られている情報量(デキャップ済みか)と、性能予算の三者の交渉で決まるのです。

完全な再現は目標ではありません。目標は、反証されない範囲で最小コストの抽象度を選ぶこと。これこそが、本章の冒頭で述べた「正確さは反証可能な主張である」という命題の、実務的な帰結です。エミュレーションとは、完璧を目指す営みではなく、「どこまで正確なら十分か」を対象ごとに賢く見極め続ける、終わりのない判断の連続なのです。

第3部 デバイスを読む

アーケード基板は、CPU・映像・音・そして時に CPU すら持たない生の回路といった「部品」の組み合わせでできています。第3部では、フレームワークの上に載るこれらのデバイスエミュレーションを一つずつ読み解きます。命令を実行する CPU コア、タイルとスプライトを合成するビデオ、波形メモリからアナログ回路まで扱うサウンド——部品の再現技術を見ていきましょう。

CPU編 — 命令を実行する部品たち

第2部では、小さなコアの上に巨大な部品カタログが載るという MAME の構図を見ました。第3部ではそのカタログの中身——CPU、ビデオ、サウンド、そして CPU が存在しない回路——を覗いていきます。ここでも一貫して流れているのは、「動けばよい」から「実機の観測結果と一致させる」への進化です。本章ではまず、あらゆるゲームの心臓である CPU コアを扱います。

src/devices/cpu の下には 190 のディレクトリが並んでいます。Z80 や 68000 のような著名どころから、電卓用の 4 ビット MCU、DEC Alpha、各種 DSP まで。1 ディレクトリがほぼ 1 アーキテクチャファミリーに対応し、派生を数えれば数百のプロセッサーが、第2部で見た共通インフラ(サイクル予算の icount、割り込み線の抽象化)の上で動いています。

CPU は「書く」ものから「記述する」ものへ

興味深いのは、主要コアの実装方式です。現在の Z80 コアの実行ループ本体は、驚くべきことにこう書かれています。

void z80_device::execute_run()
{
    #include "cpu/z80/z80.hxx"
}

関数の中身がまるごと include。この z80.hxx は手書きではなく、命令記述 DSL のファイル z80.lst から Python スクリプトがビルド時に生成したコードです。.lst の 1 命令は、こんな簡潔な記述です。

0036    # LD   (HL),n
    @arg
    @wm HL

@arg で即値を取り、@wm で HL の指す先へ書く——命令の「意味」だけを書けば、生成器がサイクル正確な C++ に展開してくれます。Z80 だけではありません。6502 も 68000 も、mame0288 の主要な 8/16 ビットコアはいずれも「DSL+生成器」方式に移行しています。エミュレーターを「書く」時代から、CPU を「記述する」時代への進化です。

命令の途中で止まれる CPU

この生成方式には、単に記述が楽になる以上の狙いがあります。命令の途中で時間切れになっても、続きから再開できる構造を、機械的に作り出すことです。

第2部で見たとおり、スケジューラーは各 CPU にサイクル予算を与えて実行させます。もし CPU が命令境界でしか止まれないと、共有バスをめぐる複数チップの協調(ウェイトステートや DMA によるバス調停)を精密に再現できません。そこで生成コードは、命令を「ステップ」に分割し、どのステップまで進んだかを m_ref という変数(プレフィックス・オペコード・ステップを 24 ビットに詰めたもの)に保存します。予算を使い切ったら、その m_ref を残して return。次に呼ばれたときは、保存したステップの case に飛び込んで続きを実行します。さらにメモリアクセスの地点では access_to_be_redone() をチェックし、アクセスがウェイトで保留された場合は、同じステップからやり直します。

この「中断・再開できる CPU(interruptible CPU)」の仕組みは、実は 6502 の生成方式が先行し、Z80 と新しい 68000 が後から同じ路線に合流したものです。共通の土台は cpu_device 側にあります。

virtual bool cpu_is_interruptible() const;
bool access_to_be_redone() noexcept
{
    return std::exchange(m_access_to_be_redone, false);
}

命令をブラックボックスとして「合計サイクル数だけ時計を進める」古い方式から、「命令内部のバスサイクル列を、途中で止まれる形で展開する」方式へ——サイクル精度への要求が、コアの実装方式そのものを変えたのです。

未定義動作まで再現する

正確さへの執念は、命令セットの「裏側」にまで及びます。Z80 の例を見てみましょう。

Z80 には、Zilog の公式ドキュメントに載っていない内部レジスタ WZ(通称 MEMPTR)があります。MAME はこれを正式なレジスタとして実装し、デバッガーにも Z80_WZ として表示します。なぜ、ドキュメントにないレジスタを実装するのか。それが、特定の命令で観測可能になるからです。たとえば BIT n,(HL) 命令を実行すると、フラグレジスタの未定義ビット(通称 X/Y フラグ、ビット 5 と 3)が、この WZ レジスタの上位バイトから漏れ出してきます。

void z80_device::bit_hl(int bit, u8 value)
{
    QT = 0;
    m_f.s_val = m_f.z_val = m_f.pv_val = value & (1 << bit);
    m_f.h_val = HF;
    m_f.n = 0;
    m_f.yx_val = WZ_H; // 未定義フラグは WZ の上位バイトから
}

さらに徹底しているのが SCF/CCF 命令(キャリーフラグの操作)です。これらの命令の未定義フラグは、近年の研究で「直前の命令がフラグレジスタに触れたかどうか」に依存することが判明しました。MAME はこの文脈依存の挙動——各命令のフェッチ時に「直前がフラグに触れたか」を記録する、いわゆる Q レジスタモデル——まで実装しています。ただし、こうした未定義挙動は Z80 の製造プロセスによって異なります。OUT (C),0 が 0 を出すか 0xFF を出すか、割り込み時の P/V フラグのグリッチなど、NMOS 版と CMOS 版で違うのです。MAME のコアはヘッダーで「本コアは ZiLOG NMOS を仮定する」と明言しています。第2部で論じた「正確さは基準となる個体を決めて初めて定義できる」という原理が、ここに具体化されています。

割り込みの扱いも精密です。Z80 の 3 つの割り込みモード(IM0/1/2)はすべて実装され、IM2 のベクター合成には「Zilog のデータシートは最下位ビットを 0 にせよと言うが、実験の結果、奇数ベクターも全 8 ビットが使われる」という実測に基づくコメントまで添えられています。データシートより実機の観測が優先される——ドキュメンテーションプロジェクトの面目躍如です。

6502 はトランジスタまで遡って検証されている

6502 コアの記述ファイルには「visual6502 で検証済み」というコメントがあります。visual6502 とは、実チップのダイ写真から起こしたトランジスタレベルのシミュレーターです。MAME の 6502 はこれと突き合わせて、実機の 1 サイクルごとの挙動を書き下しています。

#   visual6502 で検証済み
adc_abx
    m_TMP = read_pc();
    m_PC++;
    ...
    if(page_changing(m_TMP, m_X)) {
        # ページ跨ぎ時のダミーリード
        read(set_l(m_TMP, m_TMP+m_X));
    }

注目は最後の if です。インデックス付きアドレッシングでページ境界を跨ぐとき、6502 は「まだ桁上がりが反映されていない、誤ったアドレス」に対して 1 回無駄な読み出しを行います。この実機のクセ——ゲームが依存していることもある——まで、コードの形として書き下されているのです。サイクル数を表で持つのではなく、コードの構造そのものがサイクル数と実機挙動を決めています。

68000 は「マイクロコードを実行する」

極めつけは新しい 68000 コアです。生成器 m68000gen.py の冒頭には、数百個の 16 進数からなる 2 つの表があります——microcode_base_tablenanocode_base_table。これは実機 68000 の内部を制御していたマイクロコードとナノコードの制御ストアそのものです。

生成器はこの表から、マイクロコードアドレスごとの遷移グラフを構築し、実機の内部制御装置の動きを 1 ステップずつ辿る C++ を吐き出します。生成コードには // %03x trac1 のように、実機のマイクロ命令のニーモニックがコメントとして残ります。その結果、実機同様の 2 段プリフェッチパイプラインが自然に再現されます。命令パイプラインレジスタを見てください。

u16 m_irc, m_ir, m_ird, ...;
// IRC:先読み済み次ワード
// IR:デコード中, IRD:実行中

IRC(先読み済み)→ IR(デコード中)→ IRD(実行中)という 68000 の 2 段先読みが、マイクロコードの動きとして正確に流れます。「命令ごとの合計サイクル数を足す」近似とは根本的に異なる、バス動作単位の実装です。ソフトウェアで CPU を再実装する営みは、ついに「実機の内部 ROM(制御ストア)を実行する」地点に到達したのです。

一方、68010 以降の上位 68000 は、Karl Stenerud の Musashi 由来の別コアが担当します。こちらは 64K エントリーのオペコードジャンプテーブルと命令別のサイクル表による、命令単位の加算方式です(プリフェッチのバスレベル再現はしません)。プレーンな 68000 には精密な新コア、上位派生には実績ある Musashi——目的に応じて 2 系統を使い分けています。

なお、逆アセンブラーは CPU コアから完全に分離された独立のクラス群になっています。コアは「逆アセンブラーを 1 個作って返す」だけ。この分離の副産物が unidasm という単体ツールで、MAME が対応する全アーキテクチャの逆アセンブラーを 1 本のコマンドに束ねたものです。190 種の CPU の逆アセンブラーを常備した環境は、リバースエンジニアリングの道具箱としてそれだけで価値があります。

DRC — 3D世代のための動的再コンパイル

一方、1990 年代後半の 3D 基板には 100MHz 級の RISC プロセッサーが載っています。R5000(Midway の Seattle/Vegas 基板)、SH-2/SH-4(セガ ST-V、NAOMI)、PowerPC(セガ Model 3)。これらを 1 命令ずつ解釈するインタープリターで回すのは苦しく、MAME は DRC(動的再コンパイル)を使います。ゲストの機械語を、実行時にホストの機械語へ翻訳するのです。

MAME の DRC は 3 層構造です。ソースのコメントが設計を要約しています——「動的再コンパイルコアは一般に、コードを解析するプラットフォーム非依存の『フロントエンド』と、再コンパイルした機械語を生成するプラットフォーム固有の『バックエンド』に分かれる」。その 2 つの間に立つのが、UML(Universal Machine Language)という中間言語です。フロントエンドがゲスト命令を UML に翻訳し、バックエンドが UML をホストの機械語に落とす。この二段構えのおかげで、CPU の種類とホストの種類が掛け算で増えても、実装は足し算で済みます。

バックエンドの選択は、コンパイル時のアーキテクチャ判定で決まります。

#if !defined(MAME_NOASM) && \
    (defined(__x86_64__) || defined(_M_X64))
#define NATIVE_DRC drcbe_x64
#elif !defined(MAME_NOASM) && \
    (defined(__aarch64__) || defined(_M_ARM64))
#define NATIVE_DRC drcbe_arm64
#else
#define NATIVE_DRC drcbe_c
#endif

x86-64 用、ARM64 用、そしてどちらでもない環境のための「UML をそのまま解釈実行する C バックエンド」。JIT が使えないプラットフォームでも、この C バックエンドで必ず動く——移植性のドグマは DRC でも守られています。逃げ道は 3 段階に用意されていて、ドライバー側で特定機種の DRC を禁止することも、ユーザーが -nodrc でインタープリターに切り替えることも、-drc_use_c で C バックエンドを強制することもできます。

DRC を持つのは MIPS III、SH 系、PowerPC、Hyperstone E1-32 のみで、8/16 ビット世代はすべてインタープリター(生成コード含む)です。MIPS III と SH はインタープリターと DRC の両実装を持ち、実行時に切り替えます。PowerPC に至っては DRC 実装しかありません(非対応ホストでは C バックエンドで動かす、という割り切りです)。要するに DRC を持つのは「100MHz 級の RISC が載った 1990 年代後半の 3D 基板の CPU」だけ——「どこに高速化の投資をするか」の判断が、コア構成そのものに刻まれているのです。

プロテクションとの闘い

CPU にまつわるもう一つの主題が、メーカーが意図的に仕掛けた障壁——コピープロテクションです(具体的な攻防は第4部で詳述します)。

代表格が暗号化 CPU です。カプコンの「Kabuki」は暗号化されたコードを実行するカスタム Z80 モジュールで、復号鍵はバッテリーバックアップ RAM に保持されています。つまり電池が切れると鍵が消え、基板は二度と動かなくなる。第1部で触れたスーサイドバッテリーです。ソースコード kabuki.cpp の冒頭コメントは、ビットスワップと回転と XOR を組み合わせた暗号アルゴリズム、約 2.7×10¹⁶ の鍵空間、そして「0x00 と 0xff は置換の影響を受けない」という暗号学的弱点から既知平文攻撃で破られた経緯までを記した、そのまま読み物になる解読レポートです。

エミュレーター側の対応が美しいのは、コアを一切改造していない点です。第2部で見たメモリシステムには AS_OPCODES という第 4 のアドレス空間があり、「命令フェッチとデータ読み出しで異なる内容を見せる」ことができます。Kabuki 系ゲームは起動時に ROM をオペコード用とデータ用の 2 バッファに復号し、AS_OPCODES に復号済みオペコードを張るだけ。暗号化 CPU 対応が、メモリマップの問題に還元されるのです。セガの暗号化 68000 である FD1094 も、コアの派生クラス(class fd1094_device : public m68000_device)として実装されており、CPU コア本体は暗号のことを何も知りません。

もう一つの類型が保護 MCU です。基板上の i8751 などがメイン CPU と共有 RAM で対話し、正しい応答を返さないとゲームが進まない。内部 ROM が読み出せなかった時代、MAME は挙動の観察から応答を推測する HLE(シミュレーション)で凌ぎました。アイレム M72 のドライバーには、開発者の告白が今も残っています——「以下の保護ルーチンは完全なでっち上げである(entirely made up)。ゲームは動くが、本物の 8751 のコードから導出したものではない」。そして同じファイルの中に、後年デキャップで実チップから吸い出された内部 ROM を第 2 の CPU として実行する LLE 実装が共存しています。でっち上げから実チップの逐語実行へ。1 つのソースファイルの中に、保存プロジェクトの進行形がそのまま見えるのです(この HLE と LLE の理論的な位置づけは、第2部で論じたとおりです)。

CPU は命令を実行する部品でした。次章では、その CPU が生み出したデータを画面に変える部品——ビデオのエミュレーションを見ていきます。

ビデオ編 — タイルマップ、スプライト、ラスター

CPU が生み出したデータを、どうやって画面の絵に変えるか。本章では MAME のビデオエミュレーションを扱います。1980 年代の基板の大半が採った「タイルマップ+スプライト」という構成から、絵柄データの ROM 配置、スプライトの前後関係、フレーム途中で設定が変わるラスター効果、そして回路図がそのままコードになるパレット生成まで——ビデオ回路をコードとして読んでいきましょう。

タイルマップエンジンと遅延評価

1980 年代のアーケード基板の大半は「タイルマップ+スプライト」構成であり、MAME はこれを共通エンジン(src/emu/tilemap.cpp)として提供します。このエンジンの設計思想は、tilemap.h の冒頭に 300 行におよぶ解説コメントとして書かれていて、それ自体がタイルマップの教科書になっています。

まず用語です。タイルマップとはタイルの 2 次元配列で、各タイルは独立した描画特性を持ちます。ここで MAME は「論理インデックス」と「メモリインデックス」を区別します。コメントの定義を引きましょう。

論理インデックス = タイルマップ内のタイルの番号。
    行優先で隙間なく詰めた場合の番号で、常に
    (rownum * tilemap_columns + colnum) に等しい。

メモリインデックス = メモリ上のタイルの番号。
    論理インデックスと 1:1 対応しないことが多い
    (対応する場合もある)。新規タイルマップ作成時
    に (column,row) からメモリインデックスへ変換
    するマッパー関数を渡す。

論理インデックスは「画面上の位置を行優先で数えた番号」、メモリインデックスは「VRAM 上の実際の格納位置」です。この二つが一致しないことがある——それが、実基板の VRAM 配線の癖を吸収する鍵になります。

ドライバーがタイルマップを使うために書くのは、本質的に 2 つのコールバックだけです。タイルマップの生成はこう書きます(パックマンの例)。

m_bg_tilemap = &machine().tilemap().create(
        *m_gfxdecode,
        tilemap_get_info_delegate(*this,
            FUNC(pacman_state::pacman_get_tile_info)),
        tilemap_mapper_delegate(*this,
            FUNC(pacman_state::pacman_scan_rows)),
        8, 8, 36, 28);

第 1 のコールバック tile_get_info は、タイル番号から「どの絵柄・どの色で描くか」を返します。パックマンでは VRAM とカラー RAM を 1 バイトずつ読んで組み立てます。

TILE_GET_INFO_MEMBER(pacman_state::pacman_get_tile_info)
{
    int code = m_videoram[tile_index] | (m_charbank << 8);
    int attr = (m_colorram[tile_index] & 0x1f)
        | (m_colortablebank << 5)
        | (m_palettebank << 6);
    tileinfo.set(0, code, attr, 0);
}

第 2 のコールバック mapper は、先ほどの「論理インデックス ≠ メモリインデックス」を橋渡しします。パックマンの VRAM は画面レイアウトと素直に対応せず、上下 2 行分が別のアドレスに折り返されています。その歪みを、わずか 8 行の関数が吸収します。

TILEMAP_MAPPER_MEMBER(pacman_state::pacman_scan_rows)
{
    row += 2;
    col -= 2;
    if (col & 0x20)
        return row + ((col & 0x1f) << 5);
    else
        return col + (row << 5);
}

実基板のアドレス配線の奇妙さが、そのまま数行のコードに写し取られている——第2部で見た「ソースコードは基板の資料」という思想の、ビデオ側の現れです。

二重の遅延評価

エンジンの性能の要は、徹底した遅延評価です。しかもそれは二段構えになっています。

第一段。CPU が VRAM に書き込んでも、その場では何も描かれません。VRAM のライトハンドラーがすることは、該当タイルに dirty フラグを立てるだけです。

void pacman_state::pacman_videoram_w(offs_t offset,
        uint8_t data)
{
    m_videoram[offset] = data;
    m_bg_tilemap->mark_tile_dirty(offset);
}

mark_tile_dirty() はメモリインデックスを論理インデックスに変換してフラグを立てるだけで、再描画は一切行いません。

第二段。実際の描画時ですら、全タイルを描き直すわけではありません。描画中に走査しているタイルが dirty だったときに初めて、そのタイルの再デコードが走ります。

// 現在のタイルが dirty なら更新する
if (m_tileflags[logindex] == TILE_FLAG_DIRTY)
    tile_update(logindex, column, row);

つまり tile_get_info コールバックは、「dirty なタイルが実際に画面へ描画されるとき」にしか呼ばれません。書き込まれたが画面外のタイル、書き込まれていないタイルは、まったく処理されない。60 分の 1 秒ごとに画面全体が書き換わることは稀だ、という 8 ビット時代のゲームの特性を突いた最適化です。さらにその下、絵柄 ROM をデコードする gfx_element の側でも、実際に使われる絵柄だけが遅延デコードされます。書き込み→描画→デコードの三層すべてで「必要になるまでやらない」が貫かれているのです。

スクロールと絵柄の ROM 配置

スクロールは行・列単位で独立に指定できます。全体スクロールも部分スクロールも、同じ API の別の呼び方にすぎません。

// which 番目の行の横スクロール
void set_scrollx(int which, int value);
// 独立スクロールする行数を宣言
void set_scroll_rows(u32 scroll_rows);
// 独立スクロールする列数を宣言
void set_scroll_cols(u32 scroll_cols);

既定では行数・列数とも 1(画面全体が一体でスクロール)ですが、set_scroll_cols(36) と宣言すれば 36 本の列が別々の縦スクロール値を持てます。縦スクロールする迷路を実装した『ジュニアパックマン』がまさにこれで、列 2〜33 に同じスクロール値を設定して迷路を流します。多くの基板が持つ「行単位の横スクロール」による疑似ラスター効果も、この同じ仕組みです(描画側には、全行が同じスクロール値なら処理を省く最適化まで入っています)。

タイルやスプライトの絵柄が ROM 上でどう並んでいるかは、gfx_layout 構造体で宣言します。ピクセル (x,y) のビットが ROM のどこにあるかを、ビットオフセットの表として書き下すのです。ここにも実基板の都合がそのまま現れます。パックマンのタイルレイアウトを見てみましょう。

static const gfx_layout tilelayout =
{
    8,8, // 8×8 のキャラクター
    RGN_FRAC(1,2), // 256 個
    2, // 2 ビット/ピクセル
    { 0, 4 }, // 2 プレーンが 4 ピクセルずつ 1 バイトに同居
    // 後半4→前半4 の変則配置
    { 8*8+0, 8*8+1, 8*8+2, 8*8+3, 0, 1, 2, 3 },
    { 0*8, 1*8, 2*8, 3*8, 4*8, 5*8, 6*8, 7*8 },
    16*8 // 1 キャラ 16 バイト
};

パックマンは「別々の ROM に各プレーン」ではなく、1 バイトの中に 2 ビットプレーンが 4 ピクセルずつ同居し、しかも後半 4 ピクセルが先に並ぶ、という変則配置です。これをデコードする処理を書く必要はありません。ビットオフセットの表を宣言するだけで、gfx_element が汎用のデコーダーで展開してくれます。

RGN_FRAC(1,2) という記法にも工夫があります。これは「ROM 領域全体の 2 分の 1」を意味し、絶対バイト数ではなく分数でオフセットを書く仕組みです。実体はビット演算のトリックで、0x80000000 のフラグと分子・分母を詰め込んだ値にすぎません。この分数指定のおかげで、後年に容量違いの ROM を積んだ互換基板が見つかっても、レイアウト定義を書き直す必要がないのです。

スプライトと優先度

スプライトの描画は gfx_element のメソッド群が担い、透明処理の種類ごとにメソッドが分かれています——透過なしの opaque()、1 個のペンを透明にする transpen()、透明ペンの集合をビットマスクで指定する transmask()、影用の transtable()、アルファブレンドの alpha()。それぞれに拡縮版と優先度版があり、宛先ビットマップの型ごとにオーバーロードされています。

ここで面白いのが透明色の扱いです。パックマンでは「ペン番号 0 が透明」という単純な話ではありません。「カラー PROM を引いた結果が色 0 になるペン」が透明になるのです。だから transpen() ではなく transmask() を使い、透明にすべきペンの集合をパレットデバイスから取得します。

m_gfxdecode->gfx(1)->transmask(bitmap, spriteclip,
        (spriteram[offs] >> 2) | (m_spritebank << 6),
        color, fx, fy, sx, sy,
        m_palette->transpen_mask(
            *m_gfxdecode->gfx(1), color & 0x3f, 0));

transpen_mask() は「カラールックアップテーブルを引いた結果が透明色に一致するペンのビットマスク」を返します。カラーコードによってどのペンが透明になるかが変わる——実機のカラールックアップ回路の構造が、そのまま API の形になっているのです。

priority bitmap — ねじれた優先度も表現する

背景とスプライトの前後関係は、priority bitmap で解決します。画面と同サイズの 8 ビットバッファに、各タイルマップが「このピクセルは自分が不透明だ」という印を書き込み、スプライトは「どのレイヤーに隠されたいか」のマスクを持って描画されます。drawgfx.h の冒頭には、この仕組みの詳しい解説があります。

screen.priority().fill(0, cliprect);
m_tilemap1->draw(screen, bitmap, cliprect, tmap1flags, 1);
m_tilemap2->draw(screen, bitmap, cliprect, tmap2flags, 2);
...
u32 pmask = GFX_PMASK_2 | GFX_PMASK_4 | GFX_PMASK_8;
gfx->prio_transpen(bitmap, cliprect, code, color,
        flipx, flipy,
        sx, sy, screen.priority(), pmask, trans_pen);

各タイルマップを優先度値 1・2・4・8 で描き、スプライトは「隠されたいレイヤーのマスクの OR」を渡す。この方式の妙は、優先度に推移律を要求しないことです。「レイヤー A より下だが、A より下にあるレイヤー C よりは上」という、論理的にはねじれた優先関係も表現できます。実機のビデオ回路にはそういう「順序では説明できない」前後関係が実在し、それをそのまま扱えるわけです(レイヤーが 5 枚以上になると、この方式は使えず、推移的な優先度に限られる別方式に切り替わります)。

スプライト同士の描画には、一見逆説的な規約があります。「手前から奥へ描き、先に描いたピクセルは後のスプライトで上書きしない」というものです。普通は奥から手前へ描くものですが、これは「スプライト間の優先度」と「スプライト対背景の優先度」が実機では独立している、という性質の翻訳なのです。実際パックマンのスプライト描画は、スプライト RAM を末尾から先頭へ逆順に走査していて、コメントには「正しい優先度のためにこの順序で描くことが重要」と明記されています。

ハードウェアの制限も再現する

ハードウェアの制約すら、忠実に再現の対象になります。MSX やコレコビジョンで使われた VDP、TMS9928A のエミュレーションが好例です。この VDP はスキャンラインごとに表示できるスプライトが 4 枚までで、5 枚目に出会うと打ち切ります。MAME はその挙動をそのまま実装しています。

num_sprites++;

/* 5 番目のスプライトに遭遇? */
if ( num_sprites == 5 )
{
    fifth_encountered = true;
    break;
}

打ち切るだけでなく、ステータスレジスタに「5 番目のスプライトの番号」とオーバーフロービットを立てます。なぜここまで正確に再現するのか。当時のゲームはこの制限を仕様として積極的に利用していたからです。スプライトを高速で点滅させて 4 枚制限を回避して多重表示したり、5 番目のスプライト番号をプログラムから読み取ってロジックに使ったり。制限を「正確に」再現しなければ、ゲームが意図どおりに動かないのです。ちなみにこの VDP はフレーム一括描画ではなく、スキャンラインごとにタイマーで描画する方式で、ゲームがフレーム途中にレジスタを書き換えても正しく反映されます。

ラスター効果 — 「ここまで確定」モデル

多くの基板は、1 フレームの描画中にスクロール値やパレットを書き換えます。画面の上半分と下半分でスクロール量を変える、走査線ごとに色を変える——いわゆるラスター効果です。フレームを一括で描く方式では、これをどう扱うのでしょうか。

答えが update_partial() です。ビデオレジスタを書き換える CPU のハンドラーの中で、「現在の走査線位置までを、古い設定のまま描画確定」させてから新しい値を書きます。

bool update_partial(int scanline);
bool update_partial(int vpos, int hpos);
bool update_now() { return update_partial(vpos(), hpos()); }

実装は「前回の部分更新位置から、指定された走査線まで」を切り取って描画します。これにより、フレーム一括描画の枠組みを保ったまま、走査線単位の効果が再現できます。より細かく、ライン途中のピクセル単位で切り替わる効果には update_now() が使えます。実際『ミズ・パックマン・ツイン』は、タイトル画面のために VRAM 書き込みのたびに update_now() を呼んでいます。

void mspactwin_state::mspactwin_videoram_w(
        offs_t offset, uint8_t data)
{
    m_screen->update_now(); // タイトル画面
    pacman_videoram_w(offset, data);
}

もっと徹底したい基板のためには、可視ライン全部で自動的に部分更新をかける VIDEO_UPDATE_SCANLINE という属性もあり、走査線ごとに描画関数が呼ばれます。「フレーム単位」から「走査線単位」まで、必要な粒度を選べるようになっているのです。

パレット — 回路図がコードになる

色の扱いには、MAME のドキュメンテーション思想が最も鮮やかに現れます。まず、パレットは 3 層に抽象化されています。emupal.h のコメントを引きましょう。

P1) ゲームの仮想パレット(「色」)
P2) MAME の MAX_PENS 色パレット(「ペン」)
P3) OS 固有のハードウェア色レジスタ(「OS 固有のペン」)

ゲームが扱う仮想的な色、MAME 内部のペン、そして OS のハードウェア色レジスタ——この 3 層です。さらに、PROM ベースの古い基板の多くは「少数の色を、より多くのカラーコードから間接的に選ぶ」構造(間接パレット)を持ちます。パックマンは 16 色のパレットを 256 のカラーコードから引くので、ルックアップテーブルの方がパレットより大きいのです。

そして色の生成そのものが、回路図の転写です。パックマンのビデオコードのコメントには、色 PROM の 8 ビットがどの抵抗を通して RGB 出力につながっているかの配線図が書かれています。

bit 7 -- 220 Ω 抵抗 -- 青
      -- 470 Ω 抵抗 -- 青
      -- 220 Ω 抵抗 -- 緑
      ...
bit 0 -- 1 kΩ 抵抗 -- 赤

そしてコードは、この抵抗値から各ビットの重みを計算して色を合成します。

static constexpr int resistances[3] = { 1000, 470, 220 };
double rweights[3], gweights[3], bweights[2];
compute_resistor_weights(0, 255, -1.0,
        3, &resistances[0], rweights, 0, 0,
        3, &resistances[0], gweights, 0, 0,
        2, &resistances[1], bweights, 0, 0);

「この色は薄い黄色」と決め打つのではなく、「この抵抗ラダーにこのビットパターンを流すと、この電圧が出る」と物理から計算する。回路図がコードになる瞬間です。念の入ったことに、同じファイルには R と B の配線が入れ替わった海賊版基板のための別のパレット関数まで用意されています。配線の違いすら、データとして正確に記録されているのです。

ベクターとレンダリング層

ここまではラスター(ピクセルを敷き詰める)方式でしたが、アステロイドに代表されるベクターゲームは、まったく別の経路を通ります。vector_device は、点列(ディスプレイリスト)を貯めて、描画時に線分プリミティブとして描画層へ流し込むだけの薄いデバイスで、ビットマップを一切作りません。輝度に応じてビーム幅を変え、フリッカー(ちらつき)の乱数ゆらぎまで加えて、ブラウン管の質感を演出します。正確さの追求と「体験の再現」が同居しているのです(ベクタージェネレーターの詳しい仕組みは第4部で扱います)。

ラスターもベクターも、最終的には render 層という共通の仲介層に集約されます。この層は、エミュレーションコアと OSD(プラットフォーム描画)の間に立ちます。ラスタースクリーンの場合、描画関数が作ったビットマップは render texture に包まれ、1 枚のクアッド(矩形)としてコンテナに積まれます。

m_container->add_quad(0.0f, 0.0f, 1.0f, 1.0f,
        color, m_texture[m_curtexture],
        PRIMFLAG_BLENDMODE(BLENDMODE_NONE)
        | PRIMFLAG_SCREENTEX(1));

つまりエミュレーション側は「低解像度のビットマップを作る」だけで、拡大・回転・アートワーク合成・CRT シェーダーの適用は、すべて render 層より先(OSD)の仕事です。ベクターゲームはビットマップを経由せず線分を直接コンテナに積むので、4K の画面でも滑らかに描けます。座標はコンテナ内で 0.0〜1.0 に正規化され、実際の解像度から切り離される——この分業が、同じエミュレーション出力を CRT 風にも高精細にも表示できる柔軟さを生んでいるのです。

次章では、画面と並ぶもう一つの感覚——音のエミュレーションを見ていきます。

サウンドとネットリスト編 — 波形メモリからアナログ回路まで

第3部の最後は、音です。デジタルの波形メモリ音源から、FM 音源をめぐるライセンスの物語、そして CPU を持たない回路そのものをシミュレートするネットリストまで——「音を出す」という一点にも、エミュレーションの多彩な階層が詰まっています。

ストリームのグラフ

MAME のサウンドは「ストリームのグラフ」です。各音源チップはストリームを持ち、スピーカー側から毎秒 50 回のペースで「ここまでのサンプルをくれ」とプル駆動されます。CPU がチップのレジスタを叩く瞬間には、デバイスがまず「現在時刻までのサンプルを生成してから」状態を変えるため、サンプル単位のタイミングが保たれます。サンプルは 32 ビット浮動小数点で、チップごとに異なるネイティブレート(実クロック由来)の間をリサンプラーが取り持ちます。ネイティブレートは実機どおりクロックから導出されるもので、たとえば YM2151 は 3.58MHz のクロックから約 55.9kHz、QSound は DSP のマシンサイクルから約 24.04kHz という中途半端な値になります。高品質側のリサンプラーは多相の窓関数付き sinc FIR で、その実装ファイルには標本化定理からの導出を解説する百数十行のコメントが付いており、ここでもソースコードが教材を兼ねています。

この基盤は 2020年の浮動小数点化(0.226)と 2025年の全面刷新(0.278)という 2 段階の近代化を経た、コアの中でも若い部分です。現行版ではスピーカーが 3 次元の位置情報を持ち、最終段にはフィルター・コンプレッサー・リバーブ・イコライザーのエフェクトチェーンまで備わっています。

音源チップの実装は、単純なものなら教科書的に美しいコードになります。パックマンの 3 声波形音源(ナムコ WSG)の 1 ボイス分は、位相アキュムレータで 32 サンプルの波形 PROM を引くループが 7 行あるだけです。一方で単純に見えるチップにも底があり、PSG の定番 AY-3-8910 の実装コメントには、出力段の抵抗網の回路図と「データシートはカウントダウンと書いているが、実チップの観測ではカウントアップ」といった実測由来の知見が延々と記録されています。

音声合成の分業も時代を映します。ADPCM チップの MSM5205 は ROM もシーケンサーも持たない「垂れ流し」型で、チップが刻むクロックに合わせて CPU が 4 ビットの符号を 1 個ずつ供給し続けなければなりません。数年後の OKI MSM6295 は ROM 内のサンプルテーブルを自律再生する 4 声のプレーヤーで、CPU はコマンドを 2 バイト投げるだけ。どちらのモデルかによってドライバー側の書き方(CPU 側の割り込み駆動か、コマンド送信か)まで変わります。

FM 音源 — ライセンスが書き直させた名機

FM 音源の歴史は、MAME の歴史そのものの縮図です。YM2151(OPM)や YM2612(OPN2)の旧コアは 1990 年代末から使われてきた名実装でしたが、ライセンスが GPL-2.0+ でした。MAME 全体が「コアは BSD-3」を志向する中で、これは他プロジェクトへの再利用の障害になります。そこで Aaron Giles は 2020〜21年、FM コア群を ymfm としてゼロから書き直しました。README に明記された第一の動機は、精度でも速度でもなく「BSD ライセンスのコアを提供すること」です。

書き直しの土台になったのは、コミュニティの解析成果でした。Exodus エミュレーター作者 Nemesis による YM2612 の徹底解析、そしてシリコンダイ写真のリバースエンジニアリングに基づく Nuked 系実装との突き合わせ検証。ymfm の README は精度の哲学について率直で、「聴覚上区別不能な精度・妥当な性能・読めるコード」を目標に掲げ、ビット単位の完全一致が必要な読者にはダイ解析ベースの Nuked を薦める、とまで書いています。「どこまで正確なら十分か」という問いに、複数の実装が異なる答えを出して共存しているのです。

ソースツリーには旧世代の遺産も大切に残されています。旧 YM2151 コアの作者 Jarek Burczynski が実チップで計測した D/A クロックやオペレーターのパイプライン順序のタイミング図は、コードが置き換えられた後も ym2151.txt として保持されています。エミュレーター開発とは計測記録の蓄積である、という証拠のような 1 ファイルです。

その対極として、第1部で触れた QSound は「完全一致」の道を歩みました。聴感ベースの近似実装(HLE)が 20 年使われた後、2017年にダイ写真から DSP の内蔵 ROM が読み出され、翌年にはサイクル精度の DSP16A コアが実装されて、現在のデフォルトは内蔵 ROM を逐語実行する LLE です。1 個のチップの上に、エミュレーション技術の 3 世代が積み重なっています。

ネットリスト — CPU が無いなら回路を動かす

1970 年代のビデオゲームには、そもそも CPU がありません。ポンは TTL ロジック IC と抵抗とコンデンサーの塊であり、「実行すべきプログラム」が存在しないのです。これをどうエミュレートするか。

MAME には 2 世代の答えがあります。第 1 世代は discrete サウンドシステム(2000年〜)。回路図を人手で「機能ブロック」(555 タイマー、オペアンプ、ミキサー……)の有向グラフに変換し、固定サンプルレートで評価します。ドンキーコングのジャンプ音はその代表例で、ソースには 555 による電圧制御発振が部品値そのままで書かれています。

/* 555 電圧制御発振 */
DISCRETE_555_ASTABLE_CV(NODE_29, 1, RES_K(47),
    RES_K(27), CAP_N(47), NODE_28,
    &dkong_555_vco_desc)

ジャンプ音は ROM に入っていません。47kΩ と 27kΩ と 47nF が作る音なのです。

第 2 世代が、第1部でも触れた netlist サブシステム(2012年〜)です。こちらは機能ブロックではなく素子レベル——抵抗、コンデンサー、ダイオード、トランジスタ、74 シリーズ IC の単品——で回路を記述する、ミックスドシグナル回路シミュレーターです。アナログ部は全素子を「抵抗+電圧源+電流源」の統一モデルに正規化し、各節点にキルヒホッフの電流則を立てて行列方程式を解きます。非線形素子はニュートン・ラフソン反復、行列解法はガウス消去から GMRES まで選択式。要するに、ゲームエミュレーターの中に小さな SPICE が住んでいるのです(パラメーター名の VNTOL や RELTOL は SPICE 由来です)。

ポンのドライバーを見ると、この帰結として味わい深い 1 行があります。

NETLIST_CPU(config, "maincpu",
    netlist::config::DEFAULT_CLOCK())
    .set_source(NETLIST_NAME(pong));

CPU が存在しないので、回路全体が maincpu として登録されるのです。ネットリスト記述の中では回路図上の IC 名(C9 パッケージの f ゲートは c9f)がそのまま識別子になっており、Atari の回路図と 1 対 1 で突き合わせられます。映像はコンポジット信号としてモニターエミュレーションに流れ、パドル入力は回路内の可変抵抗の値を直接動かします。ソースコードがドキュメントである、という思想の最も純粋な形がここにあります。

netlist 記述は現在 50 本を超え、ブロック崩しなどの CPU レスゲーム本体から、マリオブラザーズのサウンド段の置き換え、システム16B の出力フィルター基板まで広がっています。ブロック崩し(『Breakout』)の記述には協業の痕跡もあります。これは DICE という独立したディスクリート回路エミュレーターのプロジェクトから移植されたもので、ファイル冒頭には「DICE 作者から書面で利用許諾を得た」経緯が記録されています。ゲーム保存が単独プロジェクトの営みではないことを示す好例です。

第3部を通じて、CPU・ビデオ・サウンド・回路という部品のエミュレーションを見てきました。ここまでが「部品」の話です。次章からの第4部では、これらの部品が実際の基板でどう組み合わさるのかを、時代もメーカーも異なる数々の事例で追いかけていきます。

第4部 ハードウェア事例研究

ここからは、部品が実際の基板でどう組み合わさったのかを、時代もメーカーも異なる数々の実例で追いかけます。第4部では黎明期の古典基板からベクター・初期3D、国産名作基板群、セガの体感ゲーム、ネオジオの共通ハード、そして自殺する暗号基板までを、CPU・ROM 構成・グラフィックス性能・サウンドといったスペックの変遷とともに読み解きます。スペックの違いこそがアーケード史の骨格だからです。

黎明期と古典基板 — 創意工夫の時代

ここまで見てきた部品と仕組みが、実際の基板でどう組み合わさるのか。第4部では、時代も設計思想も異なる基板を次々に取り上げ、MAME のドライバーコードに沿って読み解いていきます。スペックの数字が世代とともにどう変わり、それが表現に何をもたらしたか——アーケードハードウェアの技術史を、事例で辿る旅です。

まず本章では、アーケードの黎明期に立ち返ります。CPU もメモリも極端に乏しかった時代、開発者たちは限られたハードウェアを創意工夫で使い倒しました。スペースインベーダー、ギャラクシアン、パックマン——「回路そのものがゲームデザインだった」時代の基板です。

スペースインベーダー(1978) — 回路がゲームデザインだった時代

2MHz の CPU と 7KB のフレームバッファ

『スペースインベーダー』の基板は、Intel 8080 を 19.968MHz の水晶を 10 分周した約 2MHz で駆動します。ビデオは 1 ビット/ピクセルのフレームバッファ方式——ただし専用 VRAM チップがあるわけではなく、8KB のメイン RAM の一部(0x2400〜0x3FFF)を映像回路が直接シリアル読み出しする構成です。256×224 のモノクロ画面に必要なのは 7KB。RAM の残りと、画面に映らない領域がそのままワーク RAM を兼ねます。1 バイトも無駄にしない時代の設計です。

MAME のドライバー(src/mame/midw8080/mw8080bw.cpp)の冒頭には、この基板の水平・垂直同期カウンター回路の解説が延々と書かれており、画面はその実測値で宣言されます。ちなみにこのゲームのリフレッシュレートは俗に 60Hz と呼ばれますが、正確には 4992000 ÷ 320 ÷ 262 ≒ 59.54Hz です。MAME のマクロ名は MW8080BW_60HZ、値は 59.54——マクロ名と実値のずれ自体が、通称と実測の乖離を記録しています。

割り込みベクターは配線でできている

この基板の割り込み設計は、1978年という時代の精華です。CPU には毎フレーム 2 回、スキャンライン 96(画面の中間)と 224(VBLANK 開始)で割り込みが入ります。そして割り込み時に CPU が読み込むベクター(実体は 8080 の RST 命令)は、ソフトウェアではなく垂直カウンターのビットから配線で合成されます。ドライバーはその配線論理をそのまま 1 行のコードに写しています。

uint8_t vector = 0xc7
    | ((counter & 0x40) >> 2)
    | ((~counter & 0x40) >> 3);

カウンターのビット 6 の状態によって、この式は 0xCF(RST 1)か 0xD7(RST 2)を生成します。CPU に食わせる命令そのものをカウンター回路が作っているのです。

なぜ画面の中間で割り込むのか。これはダブルバッファの代用品です。ビームが画面上半分を描き終えた時点で割り込みを受ければ、通過済みの領域を安全に書き換えられます。VRAM は 1 面しかなくても、ビームと競争しながら描けばティアリングは起きない。「レース・ザ・ビーム」時代の発想です。MAME はこれを、画面位置に自己再スケジュールするタイマー(time_until_pos)で再現しています。

もう一つの有名な増設回路がハードウェアシフターです。この基板にはスプライト機能がなく、自機・エイリアン・砲弾といった図形はすべてフレームバッファへ直接描き込みます。ところが 8080 には 1 ビット単位のローテートしかなく、図形をピクセル単位で滑らかに動かすのに必要な「バイトをまたぐビットシフト」をソフトウェアだけで行うには遅すぎる。そこで基板には専用のシフトレジスタ IC(MB14241)が載っています。MAME でのエミュレーションは、わずか 63 行のデバイスです。

void mb14241_device::shift_data_w(u8 data)
{
    m_shift_data = (m_shift_data >> 8) | (u16(data) << 7);
}
u8 mb14241_device::shift_result_r()
{
    return u8((m_shift_data >> m_shift_count) & 0x00ff);
}

興味深いことに、このファミリーの多くの基板は実際には MB14241 を積んでおらず、74 シリーズの標準ロジック約 11 個で同じ機能を組んでいます。機能が等価なら同じデバイスでモデル化する——MAME の抽象化の判断がここに見えます。

敵の加速は、コードのどこにも書かれていない

インベーダーが減ると動きが速くなる、あの世界一有名な難易度カーブ。ROM の逆アセンブル解析(Computer Archeology)で確定している事実はこうです——ゲームは「1 回の割り込みにつき、インベーダーを 1 体だけ動かして描き直す」方式で動いており、ROM のどこにも速度テーブルは存在しません。開幕の 55 体では全体が 1 歩進むのに 55 回の割り込み(約 1 秒)かかりますが、残り 1 体になればその 1 体が毎フレーム動く。2MHz の 8080 が全スプライトをソフトウェア描画する処理予算から生まれた「1 フレーム 1 体」の設計が、そのまま加速として現れているのです。

これはエミュレーションの本質を語る絶好の題材です。MAME はこの挙動をどこにもプログラムしていません。CPU の速度と割り込みのタイミングを正確に再現しさえすれば、加速は自動的に現れる。挙動を実装するのではなく、挙動が生まれる条件を再現する——それがエミュレーションです。

サウンドもまた、CPU の外にあります。この基板に音源チップはなく、CPU が出力ポートに書いたビットがアナログ回路を直接トリガします。UFO の飛行音は SN76477 という発振チップ、爆発音や被弾音は個別部品で組まれた発振器やワンショット回路。MAME はこれを第3部で見た discrete サウンドシステムで再現しており、ドライバーには回路図の部品番号(R49、C18……)がコメントとして残されています。ここでも「音は ROM に入っていない、回路そのものが楽器」なのです。この discrete 化は 2007年になって行われたもので、それ以前はサンプル録音の再生で代用されていました。実際、タイトーのカラー版クローンは今もサンプル再生方式で、MACHINE_IMPERFECT_SOUND のフラグが付いています。同じゲームの中に、サウンド再現技術の世代差が同居しているわけです。

最後に色の話を。インベーダーの画面の緑や赤は、基板ともソフトウェアとも無関係です。白黒モニターに貼られたカラーセロファンの色なのです。MAME はこれをエミュレーションコアの外側、アートワークシステムのレイアウトファイルで再現します。自機周辺に緑、UFO 地帯に赤の半透明矩形を乗算合成する——実機の構造(ゲームの外側にある色)と、エミュレーターの構造(コアの外側にある色)が同型になっている好例です。

なお、この基板について MAME のソースには興味深い「歴史的なねじれ」も記録されています。スペースインベーダーはタイトーが設計したゲームですが、MAME ではミッドウェイの 8080 基板ドライバー(mw8080bw.cpp)がメインのセットを持っています。これは当時、タイトーが国内の供給不足を補うためにミッドウェイ製のライセンス版を逆輸入していた事情の名残です。ドライバーの TODO コメントには開発者自身の手で「invaders のセットをここから外すべきだ、本来ここにあるべきものではない」と書き残されており、ソースコードにプロジェクト四半世紀分の地層が見えます。

ギャラクシアンとパックマン — 定番構成の誕生

「多くのシステムの根」

翌 1979年のナムコ『ギャラクシアン』で、アーケード基板は次の段階に進みます。フレームバッファを捨て、タイルマップとスプライトによるハードウェア描画を採用したのです。MAME のドライバー冒頭コメントは、この基板をこう位置づけています——「ギャラクシアンは、1980〜1982年に開発された非常に多くのシステムの根(root hardware)である。この基本設計は複製され、改変され、海賊版化され、無数に使われた」。

Z80(3.072MHz)、32×32 のタイルマップ、そして 8 基のスプライト。スプライトはラインバッファ方式で、HBLANK 中の転送時間の都合により、実際に描けるのは「7.5 個分」です(8 個目は半分しか転送が間に合わない)。ハードウェアの制約がそのまま仕様になる、この時代らしい端数です。

この基板の白眉は、背景の星空です。星は CPU もタイルも使わず、17 ビットの LFSR(線形帰還シフトレジスタ)による専用回路が生成します。上位 8 ビットがすべて 1 のときだけ星が点灯するので疎らな星空になり、中間の 6 ビットが色を決める。そして星がゆっくり横に流れるのは、LFSR の周期が 2¹⁷−1 クロックなのに対し 1 フレームが 512×256 = 2¹⁷ クロックであるため、毎フレーム 1 クロックずつ位相がずれるからです。オフバイワンが演出になっている、と言ってもいい。さらにこの同じ LFSR は、サウンド回路のノイズ源(爆発音・発射音)も兼ねています。星空と効果音が同じ乱数から生まれる——回路の節約がゲームの美学を作った例です。

MAME はこの星空のために、内部画面を水平方向に 3 倍のスケールで持っています。星の生成クロックが「18MHz と 6MHz ピクセルクロックの AND」で、デューティ 2/3 の非対称な 2 パルスになるため、1 ピクセル未満の精度が要るのです。星 1 個のための執念ですが、ここを近似すると星の間隔と色の並びが実機と変わってしまいます。

スプライトの描画順にも実機由来の細部が宿ります。実機のラインバッファは、各ピクセルについて「まだ何も描かれていない(値が 0 の)位置」にしか書き込めない方式でした。このため先に処理された若い番号のスプライトがそのピクセルを取り、あとから来たスプライトは裏に回ります——若い番号が優先されるわけです。MAME はこの先着優先を、あえて番号の大きい順(逆順)に描くことで再現しています。

// ラインバッファは現在値が 0 の位置にしか書けない
// これを再現するため逆順に描画し、若い番号の
// スプライトを優先させる
for (int sprnum = 7; sprnum >= 0; sprnum--)

さらに先頭 3 基のスプライトは Y 座標が 1 ドットずれ、X も補正が要る——といった実基板検証に基づく癖まで実装されています。第3部で「スプライトは手前から奥へ描く」という規約を見ましたが、その規約が実際のハードウェアのどんな性質に対応するのか、ここに具体例があるわけです。

1 年でモダン化するアーキテクチャ

翌 1980年の『パックマン』(第2部で解剖した基板)は、ギャラクシアンと同じ 18.432MHz の水晶、同じ Z80、同じ抵抗ラダー構成の色 PROM を使いながら、設計が一段モダンになっています。色 PROM は 1 段(32 バイト直結)から 2 段(パレット PROM+ルックアップ PROM)になり、少ない容量でタイルごとの配色の組み合わせが増えました。割り込みは、ギャラクシアンがマスク不能な NMI を基板上のフリップフロップでゲートしていたのに対し、パックマンは Z80 の割り込みモード 2 を使い、CPU 自身が OUT 命令でベクターを設定します。1 年違いの 2 枚の基板に、割り込み設計の世代交代が刻まれているのです。

この「Z80+タイルマップ+スプライト+色 PROM」という構成は、以後 1980 年代前半の事実上の標準になります。その証拠が MAME のドライバー規模です。galaxian.cpp 1 本で 305 セット(親 56、クローン 249)が登録され、ファミリー全体では 400 セットを超えます。コナミの『フロッガー』や『スクランブル』さえ、このドライバーの中にいます——機械構成としては「ギャラクシアンのビデオ回路に、サウンド専用 Z80 と AY-3-8910 を載せたコナミ式サウンドボードを足したもの」だからです。フロッガー固有のビデオ差分は、実質フラグ 1 つで処理されています。

海賊版の博物館

そして、このファミリーのドライバーは海賊版経済圏の一次資料でもあります。galaxian.cpp の GAME 行 305 のうち 189 行に bootleg または hack の記載があり、ムーンクレスタのクローンは 41 セット。スペインの Petaco、Calfesa、FAR、ブラジルの Taito do Brasil——1980年代の海賊版業者の名前が、メーカー欄に国際色豊かに並びます。ドライバー冒頭には海賊版単基板の IC 配置図が ASCII アートで丸ごと保存され、「RGB の配線を入れ替えるワイヤーハーネス」を使った改造版のためには専用のパレット関数まで用意されています。正規品だけが歴史ではない。改造も複製も含めた全体が「起きたこと」であり、MAME はそれを区別しながらすべて記録しています。

このファミリーは、サウンド再現技術の見本市でもあります。ギャラクシアン本体のアナログ効果音は第3部で見た discrete サウンドシステムで実装され、17 ビット LFSR による爆発音・発射音が部品値そのままに記述されています(星空と同じ LFSR です)。一方、コナミ式サウンドボード(フロッガー/スクランブル系)は、より新しい netlist サブシステムで再現されています。AY-3-8910 を抵抗出力モードで動かし、CD4066 アナログスイッチで切り替わる RC フィルター網とアンプ IC を回路シミュレートするのです。そしてパックマンのサウンドは、そもそもアナログではなくデジタルの波形メモリ音源(ナムコ WSG)。一つの基板ファミリーの中に、アナログ回路のブロックシミュレーション、素子レベルのネットリスト、デジタル音源という 3 通りの手法が共存しています。第3部で見たサウンド再現の 3 段階が、実物として並んでいるわけです。

スペックで見る黎明期の3年

インベーダーからドンキーコングまで、わずか 3 年間のスペックを並べると、アーケード基板の基本形が固まっていく過程がそのまま見えます。

項目 インベーダー(1978) ギャラクシアン(1979) パックマン(1980) ドンキーコング(1981)
メーカー タイトー/ミッドウェイ ナムコ ナムコ 任天堂
メイン CPU Intel 8080 / 1.9968MHz Z80 / 3.072MHz Z80 / 3.072MHz Z80 / 3.072MHz
サウンド ディスクリート(専用CPUなし) ディスクリート Namco WSG(3声) ディスクリート + i8035系サウンドCPU
解像度・向き 260×224 / 縦 256×224 / 縦 288×224 / 縦 256×224 / 縦
発色数 モノクロ(カラーセロファン) 32色 32色 256色
描画方式 フレームバッファ タイル1層 + スプライト8 タイル1層 + スプライト8 タイル1層 + スプライト(1ライン16)
ROM 総量 8KB 約14KB 約24KB 約32KB

数字が語るのは、標準形の確立です。1978年のインベーダーは 8 ビットの 8080 を約 2MHz で回し、フレームバッファに直接描き、色すら基板の外(セロファン)にありました。ところが翌 1979年のギャラクシアン以降、3 作すべてが Z80 の 3.072MHz に収束します。面白いことに、この 3 作は起点となる水晶周波数がそれぞれ違うのに、分周した結果は偶然きっちり 3.072MHz で揃っている——8 ビット時代の事実上の標準クロックです。

描画方式も、インベーダーのフレームバッファから「タイルマップ 1 層+ハードウェアスプライト」へと一気に移行し、以後 10年の定番構成になりました。発色は白黒→32色→256色、ROM は 8KB→32KB へと 3 年で 4 倍。そしてサウンドの進化が最も段階的で、CPU すら持たないディスクリート回路(インベーダー、ギャラクシアン)から、専用の波形音源チップ(パックマン)、ついには専用のサウンド CPU(ドンキーコング)へと、音を出す仕組みが独立したサブシステムへ育っていきます。この 3 年で、80年代アーケードの骨格が出来上がったのです。

黎明期のわずか数年で、アーケード基板の基本形——CPU、タイルマップ、スプライト、色 PROM、そして独立したサウンドサブシステム——が固まりました。次章からは、この基本形の上に築かれた 80〜90 年代の多彩な基板を見ていきます。まずは、性能競争が生んだもうひとつの帰結——コピーとの戦いから始めましょう。

ベクターグラフィックスと初期の3D

前章まで見てきたアーケード基板は、方式こそ違えど、すべて「ピクセルを敷き詰めて絵を作る」ものでした。タイルマップで背景を、スプライトでキャラクターを並べ、専用のビデオ回路がそれをフレームバッファに合成し、ブラウン管がラスター走査する。スペースインベーダーのフレームバッファも、パックマンのタイルマップも、その延長にあります。

本章で扱うのは、それとはまったく別の系譜です。ビットマップを一切作らないゲーム機——ベクターグラフィックスの世界です。ベクター機にはフレームバッファもタイルもスプライトもありません。CPU は「点と点を結ぶ線分のリスト」を生成し、専用回路がブラウン管の電子ビームを線に沿って直接動かす。第3部で vector_device(線分リストを描くだけの薄いデバイス)を見たとき、その線分がどこから来るのかは保留にしました。本章の主役、ゲーム側のベクタージェネレーターが、その線分を生み出しているのです。

線を引くという発想 — XYモニター

通常のラスター用ブラウン管は、電子ビームが画面を上から下へ、決まった順序で舐めていきます。何を映すかにかかわらず、走査線の動きは固定です。

ベクターモニター(XYモニター)はまるで違います。X 軸と Y 軸それぞれの DAC でビームを任意の座標へ偏向し、2 点間を直線で結んで発光させる。何を描くかがビームの動きそのものを決めるのです。線を描く順序は、そのままディスプレイリスト(描画命令の列)の順序です。フレームバッファは存在しません。

この方式には固有の長所と短所があります。線が鋭く明るく、当時のラスター機では出せない解像感がありました。輝度も 1 本ごとに指定でき、明るい線・暗い線・非表示の移動を自在に混在できます。一方で、描く線が増えるほど 1 巡にかかる時間が延び、リフレッシュレートが落ちてちらつきます。MAME はこの「線が多いとちらつく」という物理的な弱点すら、vector.cpp のフリッカーオプションで再現します。ビームの太さ(beam_width)も調整でき、正確さの追求と「ブラウン管らしさ」の再現が同居しています。

MAME におけるベクター描画のデータフローは、次のように分業されています。ゲーム固有のベクタージェネレーター(avgdvg.cpp など)がディスプレイリストを解釈して点列を生成し、汎用の線分レンダラー(vector.cpp)がそれを線分プリミティブとして描く。前者が「このゲームはどう線を出すか」、後者が「線をどう画面に落とすか」を担当する構図です。

Atari の DVG と AVG — 回路をそのまま写す

ベクター機の代表格が Atari です。その心臓部は 2 系統ありました。DVG(Digital Vector Generator)AVG(Analog Vector Generator)です。MAME の実装は src/devices/video/avgdvg.cpp(1,504 行)にまとまっています。

DVG は 1979年のアステロイドやルナランダーといった初期の白黒機に使われました。座標は整数で、7497 というビットレートマルチプライヤ IC を 2 段重ねて直線を刻みます。これは DDA(デジタル微分解析)——2 点間を細かいステップで補間して直線を描くアルゴリズム——を、ディスクリートな TTL ロジックで実装したものです。AVG は 1980年のバトルゾーン以降に登場し、X/Y にアナログの積分器と DAC を使って「時間 × 傾き」で線を引きます。DVG より高精度・高速で、カラー化やクリッピング回路を持つ機種もありました。ソースのコメントは両者の関係をこう述べています。「AVG は多くの点で DVG と異なる。共通するのはステートマシンというアプローチだけだ」。

ここに MAME の設計思想が鮮やかに現れます。ベクターゲームのプログラマー向け資料には、VCTR(長いベクトルを描く)、SVEC(短縮版)、HALT(停止)、JSRL / RTSL(サブルーチン呼び出しと復帰——図形の再利用に使う)といった、いかにも命令セットらしい名前が並びます。ところが MAME の avgdvg.cpp には、これらの命令をデコードする処理が存在しません。なぜか。実機のベクタージェネレーターは、そもそも命令デコーダーを持っていないからです。その正体は「256×4ビットの PROM とラッチで構成されたステートマシン」であり、MAME はそれをそのまま模倣しています。

 * ステートマシンは、ラッチに接続された 256×4 ビットの
 * PROM である。次状態のアドレスは、ラッチされた前の
 * 状態・オペコード・停止フラグから生成される。オペ
 * コードはベクター RAM/ROM から来る。ステートマシンは
 * 1.5 MHz でクロックされる。

命令の意味は、PROM の内容と 8 個のハンドラー関数の組み合わせから立ち上がってくる。命令名という抽象を挟まず、回路の構造ごと写し取る——第2部から繰り返し見てきた「ソースコードがハードウェアのドキュメントである」という思想の、最も徹底した実例のひとつです。

余談ながら、この方式には面白い副作用があります。テンペストやクォンタムは AVG を無限ループで走らせっぱなしにするため、「HALT を待って一括描画する」という通常の方式が使えません。実機のベクターモニターには、そもそも「フレーム」という概念すらないのです。MAME はやむなくアドレス 0 へのジャンプを検出して、そこでフレームを区切ります。コメントが率直です。「これは実機とは何の関係もない。ベクターモニターにとっては、AVG が常時描き続けていて何の問題もないのだから。エミュレーションでは、ベクターの流れをなんとか『フレーム』に分割しなければならない」。実機とエミュレーターの世界観の差を、一文で示す名コメントです。

なお Atari とは独立した系譜として、Cinematronics のベクターハードウェアもあります(スペースウォー、Star Castle、Rip Off など)。こちらは 12 ビットのアキュムレータを持つ独自の Cinematronics CPU(CCPU)がベクター出力を直接駆動する方式で、Atari の DVG/AVG とは別物です。ベクターという同じ表現に、複数の技術的解が並立していたことがわかります。

バトルゾーン — 2Dモニターに3Dを描く

ベクター機の真価は、3D 表現で発揮されました。1980年のバトルゾーンは、ワイヤーフレームの戦車戦を一人称視点で描く、事実上初期の 3D ゲームのひとつです。しかし基板の CPU は 8 ビットの 6502。3D 座標を 2D 画面へ投影する計算——三角関数と行列演算の塊——を、この CPU だけで毎フレーム処理するのは不可能でした。

そこで Atari は、座標変換専用のカスタム演算回路 Math Box を積みました。CPU から見た Math Box は、メモリ上のいくつかの番地に値を書くと演算が起動し、別の番地から結果を読める、メモリマップド I/O デバイスです。MAME はこれを src/mame/atari/mathbox.cpp(305 行)として、ゲート単位ではなく高位のシミュレーション(HLE)で再現しています。16 本の 16 ビットレジスタを持ち、書き込むオフセットがコマンドを兼ねる巨大な switch 文になっています。

その中核が回転演算です。3D の視点変換とは、要するに回転行列と座標ベクトルの掛け算の集合です。Math Box のあるコマンドは、それをこう計算します。

mb_temp = ((int32_t) REG0) * ((int32_t) REG4); // cos * x
REGc = mb_temp >> 16;
mb_temp = ((int32_t) -REG1) * ((int32_t) REG5); // -sin * y
REG7 = mb_temp >> 16;
REG7 += REGc; // 回転後の座標成分

cos × x-sin × y を 32 ビットで掛け、上位 16 ビットを取り出して足す。回転行列の 1 要素の計算が、そのまま数行のコードになっています。透視投影に必要な除算も、復元法(逐次減算による割り算)で実装されています。GPU どころか浮動小数点演算器すらない時代、掛け算をハードウェアにオフロードすることで 3D を実現していた——その証拠が、このコードに残っています。

スターウォーズ — マイクロコードで動く行列プロセッサー

Atari のベクター 3D の到達点が、1983年のスターウォーズです。カラー化された AVG に加え、本格的な Matrix Processor(行列プロセッサー)を搭載しました。バトルゾーンの Math Box が「コマンド番号で固定の演算に分岐する」方式だったのに対し、スターウォーズの行列プロセッサーは命令 PROM を持つマイクロコード機です。MAME は演算コアを src/mame/atari/starwars_m.cpp(415 行)に実装しています。

3 枚の PROM を 16 ビットの命令語に再構成し、各ビットが実行すべき演算(アキュムレータのロード、乗数のロード、積和の実行など)を表します。3D 変換の本質は「行列 × ベクトル」、すなわち積和の繰り返しです。実機は直列乗算器とアキュムレータのパイプラインで、A・B・C をロードしてから ACC = ACC + (A − B) × C を実行します。MAME はこれを 1 行で表現します。

m_ACC += (((int32_t)(m_A - m_B) << 1) * m_C) << 1;

実機ではこの積和に 33 クロックかかります(1 ビットずつ加算するリング状の回路を 1 周する時間です)。MAME は消費サイクル数だけ加算し、値そのものは 1 命令で計算します。時間の流れは正確に、計算は効率的に——エミュレーションの巧みな割り切りです。

そして、この行列プロセッサーには印象的な細部があります。透視除算を行う専用のハードウェア除算器を、MAME は実機のアルゴリズムどおりに実装しているのですが、そのコメントにこうあります。「このアルゴリズムはハードウェアと同じ『誤った』結果を生成する」。前章のコラムで触れた BTANB(実機にもあるバグ)と、まったく同じ発想です。正確なエミュレーションとは、実機の欠陥までも再現することだ——1983年の基板にも、この原則が貫かれています。

Math Box と Matrix Processor は、CPU の外に置いた座標変換専用ハードウェアという点で共通しています。そしてこの思想こそ、後年のセガ Model 1 の TGP、ナムコ System 22 の DSP(前章のコラムで見た「ジオメトリプロセッサーー」)へと連なる、3D 演算ハードの祖先なのです。

ハードドライビン — 本格ポリゴン3Dの登場

ワイヤーフレームから塗りつぶしポリゴンへ。その転換点が、1989年の Atari『ハードドライビン』と『S.T.U.N. Runner』です。ここでベクターモニターは捨てられ、表示はラスターに戻ります(ポリゴンの塗りつぶしにはフレームバッファが要るからです)。しかし系譜としては、「専用の演算ハードで座標変換する」Atari ベクター機の直系の子孫です。

その構成は、当時としては破格でした。MAME のドライバー harddriv.cpp は 5,418 行という大作で、冒頭から「多数の CPU と多数のボードの多数の組み合わせ」と始まります。基本構成を挙げると——ゲーム全体を統括する 68010、ポリゴンをレンダリングする TMS34010 グラフィックスプロセッサーー、ピクセルを展開する TMS34012、そしてオプションでゲーム内計算用の TMS34010。1 台に 3D 描画用のプロセッサーーが複数積まれています。

そしてポリゴンの座標変換・ライティング・傾き計算を一手に担う心臓部が、ADSP-2100 系の DSP です。ソースはこう明記します。

    ... 単一の
    8MHz ADSP-2100(ADSP と ADSP II)または 12MHz ADSP-2101
    (DS III と DS IV)チップが、すべてのポリゴン変換・
    ライティング・傾き計算を担う。

つまり 1 台に、68010 が 1 個、TMS340x0 系が 3 個前後、そして ADSP-2100 という座標変換専用 DSP が 1 個(拡張ボード付きの機種ではさらに DSP32C や TMS32015 が加わる)。5 個から 7 個ものプロセッサーーが同居する、マルチプロセッサーー 3D パイプラインです。「ポリゴン変換専用の DSP を積む」という発想が 1989年に既にあり、これがそのままセガ Model 1 へ直結していきます。

興味深いのは、この難物ファミリーが MAME でおおむね動作することです。ハードドライビン、S.T.U.N. Runner、レースドライビンの主要リビジョンは動作フラグ 0(正常動作)。残る課題は複数 DSP 間のシリアル通信の細部や、通信対戦(スティールタロンズのリンク機能)といった周辺部です。前章で見た Model 2/3 が全滅に近かったのと対照的なのは、Atari が比較的よく文書化された標準的な DSP(ADSP-2100)を使い、内蔵 ROM を持つカスタムチップに頼らなかったことが大きいのでしょう。

スペックで辿る三世代

ベクターと初期3D の三作を並べると、10年足らずでハードウェアがどれほど跳躍したかが、数字ではっきり見えます。

項目 アステロイド(1979) スターウォーズ(1983) ハードドライビン(1988)
メーカー Atari Atari Atari Games
メイン CPU MOS 6502 / 1.512MHz MC6809E / 1.512MHz MC68010 / 8MHz
演算・補助ハード なし Matrix Processor(行列演算) TMS34010 GSP 48MHz、TMS34010 50MHz、ADSP-2100 8MHz
サウンド ディスクリート POKEY×4 + 音声合成 TMS5220 68000 + TMS32010(音声基板)
表示方式 白黒ベクター カラーベクター ラスター 508×384(ポリゴン描画)
ROM 総量 約8KB 約72KB 約1.07MB

数字を追うだけで、ベクター機の 9 年間の歩みが見て取れます。アステロイド(1979)は 8 ビットの 6502 を約 1.5MHz で回す単一 CPU、色は白黒、ROM はわずか 8KB——当時の家庭用 8 ビット機とさして変わらない規模で、あの緊張感あるゲームが成立していました。スターウォーズ(1983)では CPU が 2 個(メインとサウンドに 6809)になり、音源に POKEY を 4 個と音声合成チップを積んでカラー化。そして 3D 座標変換のために、CPU の外に行列プロセッサーという専用ハードを抱えます。ROM は 72KB へ、9 倍に増えました。

ハードドライビン(1988)で桁が変わります。メインの 68010 に加え、ポリゴン描画に TMS34010 系を複数、座標変換に ADSP-2100——プロセッサーの総数は 5〜7 個に達し、ROM は約 1MB。白黒の線画から塗りつぶしポリゴンの 3D へ、わずか 9 年での跳躍が、CPU の数(1→2→7個弱)と ROM 容量(8KB→72KB→1MB、実に 130 倍)という数字にそのまま刻まれています。ハードウェアのスペックを並べることは、技術史の年表を読むことなのです。

別系譜がひとつに合流する

本章で見てきたベクターと初期 3D は、タイルマップ+スプライトの主流とは根本的に異なる系譜でした。主流が「ピクセルを敷き詰める」のに対し、ベクター機は「線を引く」。座標変換の面でも、CPU の外に専用の演算ハードを置くという独自の道を歩みました。

しかしこの 2 つの系譜は、やがて 3D の時代にひとつへ合流します。バトルゾーンの Math Box、スターウォーズの Matrix Processor、ハードドライビンの ADSP ボード——これらの「ジオメトリ演算をハードにオフロードする」思想が、セガ Model 1/2/3 の TGP や SHARC、ナムコ System 22 の DSP へと受け継がれ、ポリゴン 3D の黄金時代を準備したのです。

象徴的なことに、Cinematronics CPU コアもハードドライビンのドライバーも、主要な書き手は Aaron Giles(第1部で見た第4代コーディネーター)でした。初期のベクター機から本格 3D まで、MAME における 3D 前史の多くが、ひとりの貢献者の仕事として辿れる。これもまた、30年続くプロジェクトならではの厚みです。

日本の名作基板を読む — コナミ・ナムコ・タイトー

1980年代後半、日本のアーケードメーカーは、それぞれに異なる技術的個性を花開かせました。コナミ、ナムコ、タイトー——同じ「68000 やその周辺の CPU にカスタムチップを組み合わせる」時代にありながら、その設計思想はくっきりと分かれています。そして面白いことに、その違いは MAME のソースコードの構造そのものに刻まれています。本章では 3 社の代表的な基板を、MAME のドライバーを通して読み解きます。

なお本章の記述では、調査の過程で通説の誤りがいくつか見つかりました(源平討魔伝の基板系統など)。MAME の GAME 定義という一次情報で裏を取り、正しい形で扱います。ソースコードで俗説を検証する——それ自体が MAME を資料として使うことの実例です。

コナミ — 起動しないゲーム機

コナミの『グラディウス』(海外名 Nemesis、1985)、『沙羅曼蛇』、『ツインビー』は、MAME では 1 本のドライバー src/mame/konami/nemesis.cpp にまとまっています。ハードウェアの総称は GX400。CPU はメインが 68000、サウンドが Z80、音源は AY-3-8910 を 2 個に波形音源 K005289、さらに音声合成 LSI の VLM5030 という構成です。ここまでは、この時代の標準的な基板といえます。

しかしこの基板には、エミュレーションにとって特異な変種があります。バブルシステムです。

バブルシステムは、ゲームデータの格納に磁気バブルメモリ(磁性体の中の微小な磁区「バブル」の有無で情報を記録する、当時の先進的な不揮発メモリ)を使いました。ところがバブルメモリには致命的な癖があります。温まるまで動作しないのです。電源を入れてから使えるようになるまで、数十秒の暖機が必要でした。

この間、基板は何をしているか。制御 IC「005297」が 68000 のリセット線とバス要求線を握ったまま離さず、CPU を止めています。プレイヤーが見るのは「WARMING UP NOW」の文字とカウントダウンタイマー、そして耳にするのは音声合成による「Getting ready… Fifty…」というカウントダウンと、この待ち時間のために流れる小曲——ファンの間で「モーニングミュージック」として知られる BGM です。バブルメモリが温まると、005297 は 68000 のブートプログラムを共有 RAM にコピーし(この転送に約 30.65 ミリ秒かかります)、ようやく CPU を解放してゲームが起動します。

MAME はこれをどう扱っているか。制御 IC 005297 は完全には解析されておらず、エミュレートされていません。そこでドライバーは、「68000 が解放される時点」から話を始め、本来 005297 がやるはずのブートプログラムのコピーを、初期化関数で手作業で肩代わりしています。

void bubsys_state::bubsys_init()
{
    /* ... 005297 はシステムのマスターであり、
       /RESET 線と /BS 線を制御する ... */
    const uint8_t *src = memregion("maincpu")->base();
    memcpy(m_bubsys_shared_ram, src, 0x1e0);

完全に解析できていないハードウェアを、それでも遊べる形にする——第3部で見た保護 MCU の HLE と同じ、現実的な回避策です。バブルシステム版のセットには MACHINE_UNEMULATED_PROTECTION(保護が未再現)のフラグが付いています。

ここに、本書を貫くひとつのテーマが顔を出します。実機では、暖機の待ち時間そのものが体験の一部でした。プレイヤーは数十秒、モーニングミュージックを聴きながらゲームの起動を待ったのです。MAME はこの暖機を温度モデルで再現していません。ソースの TODO にこう残っています。「バブルメモリが『温まる』のをシミュレートする(設定可能な)遅延を追加する」。つまり現状は、待ち時間ごと省略しているのです。「正確な保存」と「当時の体験の再現」は、必ずしも一致しない。コナミのバブルシステムは、その緊張関係を最も鮮やかに示す事例です。保存の難所が「映像」ではなく「起動プロセスそのもの」にある、という点でも独特です。

ナムコ — 1チップ、1デバイス

ナムコの設計思想を一言で表すなら、「番号付きのカスタム IC を大量に載せる」です。CUS117、CUS120、C140、C102……。基板の上には、機能ごとに専用設計されたカスタムチップがずらりと並びます。そして MAME は、この思想を「1 IC = 1 デバイス」という形で、ソースコードにそのまま反映しています。

System 1(1987)を見てみましょう。ドライバーは src/mame/namco/namcos1.cpp。CPU 構成が既に豪華です。

MC6809E(config, m_maincpu, XTAL(49'152'000)/32);  // メイン
MC6809E(config, m_subcpu, XTAL(49'152'000)/32);   // サブ
// サウンド
MC6809E(config, m_audiocpu, XTAL(49'152'000)/32);
// I/O・DAC 用 MCU
HD63701V0(config, m_mcu, XTAL(49'152'000)/8);

6809 が 3 個(メイン・サブ・サウンド)に、I/O 用の MCU が 1 個。そしてメイン/サブ間の複雑なメモリ管理を、専用のカスタムチップ CUS117 が全面的に捌きます。MAME ではこれが machine/c117 という独立したデバイスになっています。妖怪道中記、ドラゴンスピリット、パックマニア、スプラッターハウスといった名作が、この基板の上で動きました。

System 2(1988)ではさらにスケールアップします。ドライバー namcos2.cpp の CPU 構成は、68000 を 2 個(メインとスレーブ、2KB のデュアルポート RAM で連携)、6809、I/O マイコン、そして通信用のシリアル CPU——公称 5 CPU です(MAME は 4 個をエミュレートし、シリアル I/F の CPU は未対応)。音源は 24 チャンネルステレオ PCM の C140。アサルトの回転・拡大するステージは、ROZ と呼ばれるカスタムハードウェアが担います。

ここで通説をひとつ正しておきます。System 2 の回転・拡大を「DSP による」と説明する資料がありますが、これは誤りです。標準の System 2 に DSP は載っていません。回転・拡大は ROZ 専用カスタム(C102 など)の仕事です。DSP(TMS320C25、ナムコ名 C67)を積むのは、後継の System 21(『ウイニングラン』などのポリゴン 3D 機)です。MAME のカスタムチップ対応表を見れば、C67 が System 21 の欄にしか現れないことが確認できます。

そして、ナムコの真骨頂はチップの世代を超えた再利用にあります。System 86 のスプライトジェネレーターは System 1 にそのまま受け継がれ(ソースにも「スプライトジェネレーターは System 86 と同一」と明記されています)、System 2 のカスタム群は Final Lap や System 21 など後続の基板と横断的に共有されます。namcos2.cpp にある「どのチップがどの基板で使われるか」の対応表は、実チップの流用関係を示すと同時に、そのまま MAME のドライバー間のコード共有の設計図になっているのです。チップという単位でハードウェアを設計し、その単位でエミュレーターも構築する——ナムコの一貫性が、そのまま MAME のアーキテクチャの美しさになっています。

ちなみに、名作『源平討魔伝』(1986)を System 1 のゲームだと思っている方は多いかもしれませんが、正確には System 86 の作品で、MAME でも namcos86.cpp に収録されています。巨大なキャラクターが登場する横スクロール活劇として知られる一本です。

タイトー — 3枚のモニターと、チップ進化の証言

タイトーの個性は、映像回路を世代ごとに統合カスタムへまとめ上げていく執念と、そして何より「多画面」への挑戦に表れています。

その代表が『ダライアス』(基板表示は1986、日本での稼働は1987年)です。横に 3 枚のモニターを並べ、ハーフミラーで継ぎ目を消してシームレスな超横長画面を作る——シネマスコープを謳ったあの筐体です。MAME はこれをどう再現するか。1 枚の巨大なバッファを作るのではありません。3 つの独立した画面デバイス(左・中・右)として実装し、同じタイルマップを X 座標のオフセットを変えて 3 回描くのです。

u32 screen_update_left(...) {
    return update_screen(
        screen, bitmap, cliprect, 36 * 8 * 0);
}
u32 screen_update_middle(...) {
    return update_screen(
        screen, bitmap, cliprect, 36 * 8 * 1);
}
u32 screen_update_right(...) {
    return update_screen(
        screen, bitmap, cliprect, 36 * 8 * 2);
}

3 画面の物理的な配置は、第3部で触れたレイアウトファイルで定義されます。実機のタイルマップ回路 PC080SN を 3 個使う構成を、MAME では 1 個のデバイスを広幅化して共有する形で再現しています。

同じ 3 画面方式は『ニンジャウォーリアーズ』(1987)にも使われました。そのドライバーのヘッダーコメントには、多画面の物理原理そのものが技術資料として書き残されています——中央のモニターはプレイヤーに正対し、両脇の 2 枚は下向きに置かれて特殊なハーフミラーに反射させる、各画面で約 1 インチずつ映像を重ねる、そしてこのハーフミラーが「極めて高価だった」、と。エミュレーターのソースコードが、失われゆく筐体の構造を証言している好例です。

そしてタイトーのソースには、ハードウェア進化史そのものが刻まれています。ダライアスのヘッダーは、タイルマップ回路 PC080SN を 3 個も使う必要に迫られたことが、テキスト層を内蔵した次世代チップ TC0100SCN(ニンジャウォーリアーズで初登場)を生んだ、と語ります。そしてその流れは、F2 システムを経て、F3 システムの単一大型ディスプレイプロセッサー TC0630FDP へと収斂していきます。「1 個で足りずに 3 個載せた」経験が次のチップを生む——ハードウェアの進化が、MAME のコメントを通じて跡づけられるのです。

なお、ダライアスの筐体にはボディソニック(BGM に合わせて座席が振動する機構)がありましたが、これは物理的な筐体機能であり、MAME のソースには現れません。のちに見るセガ体感ゲームの可動筐体と同じく、画面の外の体験は、エミュレーションの外にあります。

タイトーの共通基板 F2 と F3 システムには、クローンや地域違いまで含めれば MAME にそれぞれ 71、100 ものセットが登録されています(重複を除いたユニークタイトルは、およそ 29、35)。まさに名作の宝庫です。世代ごとに映像チップを統合し、その上に多数のソフトを載せる——この設計は、次章で見るネオジオの「共通ハード」思想へとつながる、ひとつの潮流でもありました。

スペックで見る国産基板

3 社の代表基板のスペックを並べると、設計の個性と、80年代後半の性能競争の激しさが同時に見えてきます。

項目 コナミ グラディウス(1985) ナムコ System 1(1987) ナムコ System 2(1988) タイトー ダライアス(1986)
メイン CPU MC68000 / 9.216MHz MC6809E×3 / 各1.536MHz MC68000×2 / 各12.288MHz MC68000×2 / 各8MHz
サウンド CPU Z80 / 1.790MHz (メインの6809と共用) MC6809E / 2.048MHz Z80×2 / 各4MHz
音源 AY-3-8910×2 + K005289 + VLM5030 YM2151 + C30(8ch PSG) C140(24ch PCM) + YM2151 YM2203×2 + MSM5205
解像度 256×224 288×224 288×224 288×224 ×3画面(実質864×224)
発色数 2048色 カスタム C116 カスタム C116 2048色
背景レイヤー スクロール2枚 6プレーン 6プレーン + ROZ回転面 3画面ぶんのタイル層
ROM 総量 約0.8MB 約7MB 約17MB 約2.2MB

数字の対比が、各社の力点を物語ります。コナミのグラディウス(1985)は 68000 に音源チップを 3 種盛るオーソドックスな構成で、ROM も 1MB 弱と手堅い。ナムコの System 1(1987)は 6809 を 3 個並べる分散処理型で、ROM は早くも 7MB に達し、System 2(1988)では 68000 を 2 個に増やしたうえ ROM を 17MB まで積み、回転・拡大の ROZ 面まで加えます。ナムコの「物量」志向がスペックに表れています。

そしてタイトーのダライアス(1986)の特異さは解像度欄に集約されています。288×224 の画面を横に 3 枚——実質 864×224 という、他のどの基板とも異なる超横長の表示を実現しました。ちなみに 1992年の F3 システムになると、メイン CPU が 68EC020、発色数は 8192色、音源は ES5505 という PCM 音源へと進化し、80年代の基板からもう一段跳躍します。同じ「日本メーカーの 2D 基板」でも、7 年で世代がまるごと入れ替わっているのです。

三者三様の設計が、コードに宿る

同じ時代の日本メーカー 3 社を並べると、その個性がはっきりと浮かび上がります。

コナミの GX400 世代は、映像は CPU が直接叩く比較的ディスクリートな構成で、特異性はむしろ「バブルメモリ+制御 IC」という周辺のギミックにありました。MAME でもそれは「未エミュレートの IC を回避して起動させる初期化関数」として現れます。ナムコは「番号付きカスタム IC を大量に、そして世代を超えて再利用する」思想を、MAME の徹底した 1 チップ 1 デバイスのコード分割として結実させました。タイトーは「映像機能を世代ごとに統合カスタムへまとめる」道を歩み、その進化史がソースのコメントに証言として残っています。

三者三様の設計思想が、30年後のエミュレーターのコード構造に、それぞれの形で写し取られている。ハードウェアを読むとは、それを作った人々の考え方を読むことでもある——日本の名作基板は、そのことを教えてくれます。

セガ体感ゲームとスーパースケーラー

ポリゴン 3D が実用化される前、セガは別の方法で「奥行き」と「疾走感」を作り出していました。巨大なスプライトを、ハードウェアで滑らかに拡大・縮小する——サイズの変化だけで遠近を表現する技術です。セガ AM2 研究開発部の鈴木裕が主導したこの手法は「スーパースケーラー」と呼ばれ、ハングオン(1985)に始まり、スペースハリアー、アウトラン、アフターバーナー、パワードリフトと、80年代セガの看板タイトル群を支えました。そしてこれらの多くは、筐体そのものが動く「体感ゲーム」でもありました。本章では、この疑似 3D の仕組みと、体感筐体を MAME がどう扱うかを見ていきます。

スーパースケーラーの正体は10行で読める

「スーパースケーラー」という華々しい名前とは裏腹に、その中身は驚くほど素朴です。ポリゴンでも DSP でもなく、スプライトを間引いたり複製したりして拡大縮小しているだけ——そのことが、MAME のソースを読むと一目でわかります。

まず基板の系統を整理しておきましょう。MAME では体感ゲームの基板が世代別に分かれています。

基板 MAME ドライバー 代表タイトル
Hang-On 系 segahang.cpp ハングオン、スペースハリアー(1985)
Out Run 系 segaorun.cpp アウトラン(1986)、ターボアウトラン
X Board segaxbd.cpp アフターバーナー(1987)、スーパーモナコGP
Y Board segaybd.cpp ギャラクシーフォース2、パワードリフト(1988)

ここで通説をひとつ訂正しておきます。パワードリフトは X Board と混同されがちですが、MAME のドライバー配置がはっきり答えを出しています——パワードリフトは Y Board(segaybd.cpp)のタイトルです。基板の帰属を一次情報で確定させる、良い実例です。

スケーリングの仕組みは、最も初期の Hang-On 系が最も読みやすい形をしています。スプライトの描画はスキャンライン単位で行われ、上端から下端まで 1 行ずつ走査していきます。このとき、ソースデータのアドレスを 1 行ごとにどう進めるかが、縦方向のスケーリングの肝です。

for (int y = top; y < bottom; y++)
{
    // 1行進める
    addr += pitch;
    // ズームビットが立てば pitch をもう一度足す
    if (zoom[zaddr++] & zmask)
        addr += pitch;

1 行進むごとに、まず pitch を足してソースの次の行へ移ります。そして「ズームテーブル ROM」を参照し、そのビットが立っていれば pitchもう一度足す。つまりソースの 1 行を飛ばす——これが縮小です。飛ばさなければ等倍。ズーム係数に応じてテーブルを引き、間引き量を変えることで、任意の縮小率を実現しています。

横方向はもっとシンプルで、固定小数点のアキュムレータを使います。各ピクセルごとにズーム値を足していき、桁上がり(0x100)しない間だけ書き込む。これで間引き(縮小)と引き伸ばし(拡大)が表現できます。

疑似 3D の「奥行き」の正体は、これだけです。遠くにあるものは小さいスプライト、近くにあるものは大きいスプライト——その大小を、ソース行の間引きと複製で作っている。ポリゴンで空間を計算しているわけでも、DSP で座標変換しているわけでもありません。「スプライトを賢く拡大縮小する」という一点に、当時のセガの技術が集中していたのです。

世代を追うと、このズームの持ち方が進化していくのも興味深い点です。Hang-On は外部のズーム ROM を引く方式でしたが、Out Run と X Board 世代では、スプライトの属性そのものに 10 ビットの分数ズーム係数を持つようになります(0x200 が等倍、0x100 が 1/2、0x300 が 2倍)。ハードウェアの成熟が、スプライト属性フォーマットのコメントだけで追えるのです。そして Y Board 世代になると、11 ビットのズームに加えて画面全体の回転まで扱えるようになります。ギャラクシーフォースの「画面全体が傾く」あの表現は、Y Board の回転機能によるものです。

68000を3個積む — 演算力を物量で

スーパースケーラーの拡大縮小は、大量の座標・スケール計算を要します。セガはこれを、高度な演算チップではなく、CPU の物量で解きました。基本構成は「メイン 68000 +サブ 68000(道路やオブジェクトの描画用)+ Z80(サウンド)」。そして上位基板ほど 68000 を増やしていきます。

その極みが Y Board です。68000 を 3 個(メイン、subx、suby)搭載しています。

M68000MUSASHI(config, m_maincpu, MASTER_CLOCK/4);
M68000MUSASHI(config, m_subx, MASTER_CLOCK/4);
M68000MUSASHI(config, m_suby, MASTER_CLOCK/4);
Z80(config, m_soundcpu, SOUND_CLOCK/8);

ここには、エミュレーションの現場ならではの生々しい事情も見えます。標準の 68000 コアではなく、あえて古い Musashi コア(第3部で触れた、68010 以降が使う旧世代のコア)を使っているのです。ドライバーのコメントに理由が書かれています——68000 コアを書き直したところ、パワードリフトがハングアップするようになった。TAS 命令の扱いが原因かもしれない、と。正確さを追求する書き直しが、あるゲームでは回帰バグを生む。それを避けるために、あえて古いコアを残す。エミュレーションの完璧さが、時に別のエミュレーションと衝突する——そのリアルがここにあります。上位のデラックス筐体版になると、これに加えてモーター制御用の Z80、通信対戦用の Z80 まで足され、CPU の数はさらに増えていきます。

アウトランの道路 — スキャンラインが描くカーブ

体感ドライブゲームの主役は、路面です。アウトランのあの滑らかにうねる道路を、MAME は独立したデバイス(道路ジェネレーター)で再現します。この道路描画は、スキャンライン単位の巧妙な仕掛けでできています。

各スキャンラインについて、道路 RAM から 2 つの情報を引きます。ひとつは「道路 ROM のどのラインを使うか」——これが丘や遠近(路面が地平線に向かって狭まっていく見え方)を作ります。もうひとつが「そのラインの水平スクロール値」——これをライン別にずらすことで、カーブを表現するのです。手前のラインと奥のラインで横位置を少しずつずらせば、道が左右に曲がって見える。単純ながら効果的な手法です。

道路は 2 レイヤー持っていて、制御レジスタで「道路 0 のみ/両方(0 優先)/両方(1 優先)/道路 1 のみ」を切り替えられます。これは分岐路(アウトランの、あの区間ごとに道が二又に分かれる演出)やクロスフェードに使われました。この道路デバイスは、のちに見るシステム16 の文脈にも登場した同じ系統の発展形です。

動かない筐体を、それでも記録する

体感ゲームの「体感」の部分——油圧やモーターで筐体が動く機構を、MAME はどう扱うのでしょうか。

答えは「動きは再現しないが、駆動値は出力する」です。MAME は筐体を物理的に動かすことはできません(そもそもソフトウェアですから)。しかし、ソフトがモーターに送る駆動値を、出力ポートとしてそのまま公開します。Y Board のパワードリフトやギャラクシーフォースのデラックス版では、その出力名は実に細かいものです。

output_finder<> m_right_motor_position;
output_finder<> m_right_motor_speed;
output_finder<> m_left_motor_position;
output_finder<> m_vibration_motor;
output_finder<> m_bank_motor_position;

右モーターの位置と速度、左モーター、振動モーター、車体を傾けるバンクモーターの位置……。これらの値は MAME の出力システムを通じて外部へ送り出せるので、実物の可動筐体や LED 基板を駆動するドライバーへ渡すことができます。エミュレーションの中では椅子は動きませんが、その「動くべき量」は正確に計算され、外の世界へ出力されるのです。

ここで、MAME の再現度フラグをめぐる興味深い設計判断があります。ピンボールや景品機のような「機械部分がないとゲームとして成立しない」機種には MACHINE_MECHANICAL というフラグが付きます。ところが体感ゲームには、このフラグがあえて付けられていません。なぜか。体感ゲームは画面の中でプレイが完結し、筐体の動きはあくまで体感的なフィードバックにすぎないからです。「可動部は再現しないが、それはゲームの本質機能ではないから欠陥扱いにはしない。代わりに駆動値は出力する」——この線引きに、何を「保存すべき本質」とみなすかについての、MAME の一貫した判断が表れています。

スペックで見る体感4世代

セガの体感ゲームは、ほぼ 1 年ごとに基板を更新していきました。4 世代のスペックを並べると、その物量の増強ぶりが一目瞭然です。

項目 Hang-On(1985) Out Run(1986) X Board(1987) Y Board(1988)
代表作 ハングオン アウトラン アフターバーナーII ギャラクシーフォース2
メイン系 68000 ×2 / 各6.294MHz ×2 / 各10MHz ×2 / 各12.5MHz ×3 / 各12.5MHz
サウンド CPU Z80 / 4MHz Z80 / 4MHz Z80 / 4MHz Z80 / 約4MHz
音源 YM2203 + SegaPCM YM2151 + SegaPCM YM2151 + SegaPCM YM2151 + SegaPCM
発色数 6144色 8192色 16384色 16384色
解像度 320×224 320×224 320×224 320×224
ROM 総量 約1.26MB 約2.56MB 約3.81MB 約7.59MB

わずか 3 年の間に、メインの 68000 は 6.3MHz から 12.5MHz へ倍増し、ついに Y Board で 3 個に。発色数は 6144色から 16384色へ、ROM 容量は 1.26MB から 7.59MB へと 6 倍に膨らんでいます。解像度こそ 320×224 で据え置きですが、その同じ画面に、より速い CPU とより多くの色、より大きなグラフィック ROM を注ぎ込むことで、拡大縮小するスプライトの数と滑らかさを上げていったのです。「同じ画面サイズのまま中身を強化し続ける」——スーパースケーラーという一点突破の技術を、ハードウェアの物量で磨き上げた 3 年間が、この表に凝縮されています。ちなみに Y Board で突出している ROM 容量(7.59MB)の大半は、拡大縮小されるスプライトの絵柄データ(約4MB)です。前章のベクター機で見たのと同じく、増えた容量の行き先はやはりグラフィックでした。

2Dの疑似3Dは、ほぼ完動する

前章のコラムで見たとおり、セガの本物のポリゴン 3D 機(Model 2/3)は、MAME でいまだ多くが動作困難です。ところがスーパースケーラーの 2D 疑似 3D 世代は、対照的にほぼ完璧に動きます。ハングオンもアウトランも動作フラグ 0、Y Board のタイトルはすべてセーブステート対応で正常動作。この差は、「スプライトの拡大縮小による疑似 3D」と「本物のポリゴン 3D」の間にある、エミュレーション難易度の断層をはっきりと示しています。前者は仕組みが理解しやすく、内蔵 ROM を持つ謎のカスタムチップにも依存していなかった。だからこそ、完全な再現に到達できたのです。

そしてここにも、実機のバグごと保存する MAME の流儀が現れます。アウトランのある ROM は、実機の段階でデータの一部のビットが固着した不良品でした。MAME はこれを「直さず」、実機にあるバグ(BTANB)としてそのまま残しています。正確なエミュレーションとは、名作の輝きだけでなく、その基板が抱えていた欠陥までも、ありのままに記録することなのです。

ネオジオ — 共通ハードという発明

これまで本部で見てきた基板は、時代も設計もばらばらでしたが、ひとつだけ共通点がありました。1 タイトル 1 基板——1 つのゲームのために 1 つの専用基板が作られる、という前提です。パックマン基板はパックマンのためのもの、システム16 は差し替えで別ゲームにできたとはいえ、ROM ボードと保護チップの交換を伴う「基板の改造」でした。

1990年、SNK のネオジオは、この前提を根本から覆します。本章では、この「共通ハードウェア+ROM カートリッジ」という発明が、アーケードの経済と、そしてゲーム保存にとって何を意味したのかを見ていきます。

フロアスペースの経済学が生んだ設計

ネオジオの設計思想を、MAME のドライバー冒頭コメントほど端的に語るものはありません。

    ... アーケード運営者は最大6種類のタイトルを
    1つの筐体に収められる。これはフロアスペースの
    限られた運営者にとって重要な経済的配慮だ
    (ネオジオ用ゲームはカートリッジ式で、
    容易に交換できる)。

MVS(Multi Video System、業務用)は、1 つの筐体に最大 6 タイトルを入れられる。フロアスペースが限られた店舗運営者にとっての、経済的な合理性——それがこの設計を生んだ、と明記されています。技術的な野心ではなく、運営コストの都合がアーキテクチャを決めたのです。

考えてみれば、これは画期的な転換でした。従来、人気の落ちたゲームの基板は死蔵されるか、コストをかけて別ゲームに改造されるしかありませんでした。ネオジオは高価な本体(マザーボード)と安価な ROM カートリッジを分離することで、この関係を逆転させます。オペレーターは本体を一度買えば、以後は安いカートリッジを買い替えるだけでラインナップを更新でき、稼働率の低いタイトルはカートリッジを抜くだけで撤去できる。MVS には 1 / 2 / 4 / 6 スロットの筐体があり、複数のカートリッジを差してプレイヤーに選ばせることもできました。

さらにネオジオが独特なのは、業務用の MVS と家庭用の AES(Advanced Entertainment System)が、ほぼ同一のアーキテクチャだったことです。MAME は AES を「MVS の home version」と位置づけています。カートリッジの物理形状こそ MVS 用と AES 用で異なり相互に挿さりませんが、中身の ROM データは共通です。これは「アーケードと寸分違わぬ家庭用移植」を原理的に保証しました。移植コストはゼロ——MVS の成功が、そのまま AES の商品になったのです。

背景すらスプライトで描く

ネオジオのハードウェア構成は、この時代の定番——マスタークロック 24MHz から分周した 68000(メイン、12MHz)、Z80(サウンド、4MHz)、そして第3部で扱った OPN 系の音源 YM2610(FM+SSG+ADPCM を統合)——です。しかし映像の作り方に、際立った特徴があります。

ネオジオはタイルマップ背景を持ちません。 可動キャラクターも、背景も、すべてを大量のスプライトで構成するのです。描画レイヤーは実質 2 つ——スプライトと、前景の固定テキストレイヤーだけです。

これを可能にするのが、スプライトの物量と機能です。各スキャンラインで最大 96 スプライトまで描画でき(奇数ラインと偶数ラインで別のリストを使います)、しかもハードウェアで縦横に縮小できます。横方向のズームは 16 段階の固定ビットテーブルで、実機で検証されたパターンがそのまま定数として書かれています。

/* 横方向ズームテーブル。実機で検証済み */
static const u16 zoom_x_tables[16] =
{ 0x0080, 0x0880, 0x0888, 0x2888, ... 0xffff };

縦方向のズームは、本体側のシステム ROM に含まれる専用のルックアップ ROM(000-lo.lo)を引いて、スプライト内のどのラインを表示するかを決めます。そして「連結(chaining)」——あるスプライトの隣に次のスプライトを繋げて、大きなオブジェクトや横に長い背景ストリップを作る機能。この縮小と連結を組み合わせることで、タイルマップを持たないまま、疑似的にスクロールする背景をスプライトだけで構成しているのです。ハードウェアのコストを ROM(ズームテーブルや大容量のグラフィック ROM)に逃がす——潔い割り切りです。

カートリッジの中身も規格化されていました。2 枚の基板からなり、片方にグラフィックの C-ROM・テキストの S-ROM・サウンドドライバーの M-ROM、もう片方に ADPCM サンプルの V-ROM とプログラムの P-ROM。この「役割ごとに命名された ROM 群」という規格化が、後述する保存の容易さにつながります。

BIOSがカートリッジを起動する

共通ハードウェアである以上、本体側には「どのカートリッジが挿されても、それを認識して起動する」共通のシステム ROM——BIOS が必要です。MAME でネオジオのゲームを動かすには neogeo.zip(BIOS を含む親セット)が必須ですが、これは面倒な制約ではなく、共通ハード方式そのものの必然的な帰結です。

MAME はこの BIOS を、共通の親セット neogeo にまとめ、数百のゲームセットが親として参照する構造にしています。この親セットには BIOS ルートのフラグが付き、単体では遊べず、あくまで共通基盤としてのみ機能します。CPS-2 やシステム16 でも見た MAME の親子(parent/clone)構造ですが、ネオジオはそれを最も素直な形で——「1 つの共通ハード+多数のソフト」として——体現しています。

BIOS には多くのバリエーションがあります。地域別(ヨーロッパ、US、日本、アジア)、世代別、そして業務用 MVS と家庭用 AES では別の BIOS。さらに興味深いのが、第三者が作った非公式のハック BIOS「ユニバイオス」への対応です。MAME はこれについて「実際のアーケードで十分に使われているため対応する」と明記しています。保存プロジェクトが、公式・非公式の線引きをどう引くか——現実の使用実態を基準にするという、ひとつの見識です。

面白い設計の転用もあります。本来はハングアップ検出のためのウォッチドッグタイマーを、ネオジオはコピープロテクトに流用しました。バックアップ RAM に正当なデータが無いと初期化ループに入り、一定時間ウォッチドッグがリセットをかけなければ「プロテクションチェック失敗」のフラグが立つ、という仕掛けです。ハードウェア機能の目的外利用の好例です。

また MVS には、スコアやゲーム進行を記録して持ち帰れるメモリカード(JEIDA 規格の SRAM カード)がありました。1990年の業務用機で、家や別の店へセーブデータを持ち運べた——家庭用 AES と同一ハードだからこそ成立した、「アーケードのセーブ文化」です。

保存にとっての意味、そして暗号との闘い

共通ハード+カートリッジという設計は、ゲーム保存にとって幸運でした。本体(BIOS・システム ROM)とゲーム(カートリッジ ROM)が物理的に分離しているため、両者を別々にダンプ・保存できます。カートリッジは規格化された ROM の集合なので、吸い出しの手順が定型化しやすい。そして家庭用 AES とのアーキテクチャ共通性により、同一のデータが業務用・家庭用という 2 つの流通経路で残りました。専用基板のロジックに埋め込まれた ROM を苦労して剥がすのに比べれば、はるかに保存に適した構造だったのです。

ただし、後期のネオジオは平穏ではありませんでした。1999年の『KOF99』以降、カスタム IC によるグラフィックの暗号化が導入されます。この C-ROM 暗号を、MAME のコメントは「これまで見た中で最も厄介な XOR 方式のひとつ」と評しています——9 個の一見無相関な 256 バイトのテーブルを使う、入り組んだ仕組みでした。

しかし、この暗号もまた破られます。皮肉なことに、その原因は暗号自身の弱点にありました。ソースのコメントが解読の経緯を記録しています。第一に、XOR の値が復号後のアドレスではなく暗号 ROM 内のアドレスに依存し、しかも復号後データの 60% 以上が 0x00 か 0xFF に偏っていたため、統計的なパターンが露見した。第二に、アドレスのスクランブルが 32 ビット語単位で、ROM 中に一度しか現れない 32 ビット値が多数あったため、暗号と平文のアドレス対応表を構築できた。こうして既知平文攻撃で陥落したのです。「最も厄介な暗号」が、データの統計的な偏りという足元から崩れる——暗号設計の教訓話として、実に示唆に富んでいます(この解読には第1部で触れた Nicola Salmoria も関わっていますが、該当ソースファイル自体のクレジットには別の開発者名が記されています)。

スペックで見るネオジオ

項目 仕様
発売年・メーカー 1990年 / SNK
メイン CPU MC68000 / 12MHz(24MHz マスターの1/2)
サウンド CPU Z80 / 4MHz
音源 YM2610 / 8MHz(FM 4ch + SSG 3ch + ADPCM 7ch)
解像度 320×224(実設計は幅304)/ 約59.2Hz
発色数 同時 4096色(256パレット×16色、パレット総域は16384エントリ)
背景構成 専用タイル背景なし。全てスプライトで構成 + 固定テキスト層
スプライト 1ライン最大96個、1画面最大381個、縦横ズーム対応
ROM 本体 BIOS 512KB + カートリッジ(C/V-ROM 等、タイトルにより数〜数百Mbit)

このスペック表には、ネオジオの設計思想がそのまま表れています。まず「背景構成」欄——専用のタイルマップ背景を持たず、すべてをスプライトで描く、という他基板にない割り切り。それを支えるのが「1ライン最大96個・1画面最大381個」という、当時としては破格のスプライト表示能力です。前章のセガ体感ゲームや国産基板が複数のタイルレイヤーを重ねて画面を作っていたのに対し、ネオジオは大量のスプライトという単一の武器に集中しました。

そして最大の特徴は「ROM」欄にあります。本体は BIOS の 512KB のみで固定され、ゲームのデータ量はカートリッジ側で自由に決まる——だからこそ、初期の数 Mbit のタイトルから、後期の数百 Mbit の大作まで、同じ本体で動かせたのです。CPU やサウンドのスペック(68000 12MHz + Z80 + YM2610)は当時のアーケードとして標準的で、むしろ平凡とすら言えます。ネオジオの革新は個々のチップ性能ではなく、「本体を固定し、ソフトの規模をカートリッジで伸ばす」というアーキテクチャそのものにあった——スペック表は、そのことを静かに語っています。

コード構造が設計思想を映す

ネオジオは、MAME における「共通ハード+多数ソフト」方式の代表例です。CPS-1/2 やシステム16 も親子構造を持ちますが、ネオジオが際立つのは、業務用と家庭用がアーキテクチャを完全に共有し、1990年から2004年という長期にわたって、同一の基板でソフト供給を続けた点です。

そして、その設計思想は MAME のコード構造そのものに映し出されています。MAME はネオジオの本体を src/mame/neogeo/ に、カートリッジとプロテクションを src/devices/bus/neogeo/ のスロットデバイス群として、明確に分離しています。これは実機の「共通本体+交換式カートリッジ」という物理構造を、そのままソフトウェアの構造へ写したものです。「ハードとソフトを分ける」という 1990年の設計判断が、30年後のエミュレーターのコードの分け方にまで、一貫して貫かれている。

ハードウェアの設計思想は、それをドキュメント化するソースコードの構造にまで及ぶ——ネオジオは、そのことを最もきれいに示す事例なのです。

暗号とプロテクション — 自殺する基板たち

基板の性能が上がり、グラフィック ROM が巨大化するにつれ、1 タイトルに込められた開発資産は跳ね上がりました。それは同時に、海賊版に丸ごとコピーされたときの痛手も大きくなることを意味します。メーカーはコピー防止に本気を出し始め、やがて「電池が切れると自ら死ぬ基板」まで生み出しました。本章では、カプコン CPS-2 とセガ・システム16 の暗号、そしてそれを解き明かしたコミュニティの、長い攻防を追います。

CPS-2 — 暗号と鍵の 15 年戦争

自殺する基板

1993年、カプコンが投入した CPS-2 は、それまでとは次元の違うコピー対策を持っていました。B ボード(ゲームカートリッジ)に載るカスタム IC「DL-1525」は 68000 CPU そのものをゲートアレイに封入したもので、プログラム ROM の復号をチップ内部で行います。バスをプローブしても平文は流れません。そして復号鍵はチップ内の SRAM に保持され、基板上の電池で維持されます。電池が切れれば鍵は消え、基板は起動時に青一色の画面を映すだけになる——スーサイドバッテリーです。

CPS-2 の暗号は、前身の CPS-1 から大きく強化されています。CPS-1 で暗号化されていたのはサウンド用の Z80(第3部で触れた Kabuki)だけでしたが、CPS-2 は保護対象をメイン CPU である 68000 のプログラム全体に移し、しかも復号回路を CPU ごとカスタム IC に封じ込めました。基板は 2 枚組で、共通のマザーボード(A ボード)にゲームごとのカートリッジ(B ボード)を挿す構成です。カートリッジのケースは地域別に色分けされ、緑が日本、青が北米・欧州、橙が南米。生産期間を通じてユニークタイトルは約 50、地域違いを含めれば 170 以上のセットが存在します。

効果は絶大でした。MAME のドライバーコメントによれば、商用稼働期に CPS-2 の海賊版基板は 1 つも知られていません。しかしこの仕組みは、ゲームの寿命を電池の寿命に縛り付けるものでもありました。ここから 15 年に及ぶ、コミュニティとの攻防が始まります。

3 幕の解読史

第 1 幕は力技です。 2001年1月、Razoola らの「CPS-2 Shock」チームが、実機ハックによって非暗号化のプログラムデータを取得することに成功します。暗号アルゴリズムは未解明のまま、「暗号化 ROM と平文の XOR 差分テーブル」として配布され、翌月の MAME 0.37b12 で最初の CPS-2 ゲーム群が動き出しました。ちなみにこのとき生まれた、電池切れ基板でも動くようパッチされた「Phoenix セット」(不死鳥の名は蘇生から)には皮肉な後日談があります。保存目的で作られたこのデータが、2000年代の CPS-2 海賊版ビジネス——「スーサイドフリー」を謳う改造基板や多合一基板——の母体になってしまったのです。MAME は現在も Phoenix 系セットを「カプコンのデータの無断改変」として bootleg 扱いで収録しています。

第 2 幕は数学です。 2006年末、Charles MacDonald と Razoola が実機から完全な暗号テーブルを抽出し、Nicola Salmoria がブログで統計解析を始めます。年明けの 2007年1月、Andreas Naive の洞察を得て解析は一気に進み、1月8日、ブログに「CPS2 Fundamental Breakthrough」の記事が上がります。正体はアドレス下位 16 ビットを鍵に混ぜ込む、4 段 Feistel ネットワーク 2 つの直列構成、鍵長 64 ビット。まずアドレスを第 1 の Feistel 網に通してシードを作り、それでマスターキーを変調して第 2 の Feistel 網を構成し、命令語を復号する。同じ命令でも番地が違えば暗号文が変わる、という XOR テーブル時代の悪夢の正体がこれでした。

わずか 2 日後の 1月10日、MAME 0.111u3 に復号関数が実装されます。「Feistel ではないかもしれない」という記事から実装まで 1 週間強。オープンな場でリアルタイム中継された暗号解読でした。各ゲームの鍵は既知平文との中間一致攻撃で総当たりされ、1 ゲームあたり 20〜40 分で復元できたといいます。同年 2月には XOR テーブル方式が MAME から完全に撤去され、6 年間続いた「アルゴリズムを知らないまま動かす」時代が終わりました。解読された鍵は人間くさいものでした——ビットパターンには 16 進数字列の置換らしき規則性があり、ウォッチドッグ設定値には誕生日らしき日付が見つかっています。

MAME の実装は、実機とは逆のアプローチを取ります。実機のカスタム IC は命令を 1 個フェッチするたびにハードウェアでオンザフライ復号しますが、MAME は起動時にプログラム ROM 全体を一括で復号します。起動時に「Decrypting 42%」と進捗が表示されるのはこのループです。復号の心臓部である S-box テーブルの選び方にも小さな工夫があります。MAME の S-box は「電池切れ基板の鍵がちょうど全ビット 1(オール FF)になる」ように正規化されており、死んだ基板も「鍵が FF の暗号化基板」として同じコードパスで扱えるのです。

余談ですが、この暗号には「暗号化ウォッチドッグ」という先進的な対解析機構もありました。数秒ごとに特定の 68000 命令(ゲームごとに異なる)をフェッチしないと復号自体が停止するのです。1993年の業務用基板としては驚くべき周到さです。

第 3 幕はハードウェアへの還流です。 2016年、Eduardo Cruz(Arcade Hacker)らが CPS-2 のセキュリティプログラミング手順——カスタム IC に鍵を書き込むプロトコル——のリバースエンジニアリングに成功します。Arduino ベースの治具で、電池切れ基板に鍵を書き戻し、無改造のまま蘇生させられるようになりました。同年の MAME 0.178 は、これを受けて鍵データの形式を刷新します。以後の MAME の key ファイル(20 バイト)は、64 ビットの暗号鍵、ウォッチドッグ命令、暗号化範囲の設定を含む「実基板にそのまま書き込めるビット列」です。エミュレーターの ROM セットと実機の修理ツールが、同じデータを共有している——保存のための解析が一周して、実機を救う道具になったのです。

システム16 と FD1094 — 鍵に刻まれた開発日誌

電池を抱いた 68000

セガが 1985年から展開したシステム16 は、68000(10MHz)+ Z80 という CPU 構成に、カスタムチップによる多層タイルマップとスプライトを組み合わせた、同社 16 ビット世代の主力基板です。『ファンタジーゾーン』『シノビ』『ゴールデンアックス』、そして 2 個目の 68000 と道路描画レイヤーを足した発展形の『アウトラン』。MAME ではタイルマップが segaic16_video_device、スプライトが世代別のデバイス群、315-5195 メモリマッパーや 315-5248 乗算器といった補助チップまで、部品番号単位でデバイス化されています。

セガはこの基板に、部品番号 315-5xxx のカスタムチップ群を惜しみなく投入しました。MAME はこれらを機能単位でデバイス化しています。タイルマップ生成は単一の segaic16_video_device に集約され、ハングオン系・16A・16B の各方式を切り替えられるようになっています。スプライトは世代ごとに別デバイスで、sega_sys16a_sprite_devicesega_sys16b_sprite_device、そして後継基板用の sega_xboard_sprite_devicesega_yboard_sprite_device が並びます。ハードウェア乗算器(315-5248)や比較・タイマー IC(315-5250)まで独立したデバイスです。第2部で見た「基板上のチップをそのままデバイスツリーに写す」設計思想が、カスタムチップの塊であるセガ基板で最も濃く現れています。

その多くのタイトルで、メイン CPU のソケットに刺さっていたのは素の 68000 ではなく、日立製のカスタム CPU FD1094 でした。黒いエポキシのブロックで、金属蓋の下には電池。復号鍵は 8KB のバッテリーバックアップ RAM に保持され、蓋の裏には電池線がハンダ付けされているため、乱暴に蓋を開けた瞬間に鍵が消えます。CPS-2 より 8 年早い、自殺する CPU です。

同系にはもう一つ、FD1089 という暗号化 CPU もあります。こちらはオペコードとデータの両方を復号する一方、16 ビット語のうち 8 ビットしか変換せず、アドレスもわずか 4 ビットしか暗号化に関与しません。MAME の fd1089.cpp のコメントは、この設計を辛辣に批評しています——内蔵 RAM が 16KB あるのに、ゲーム全体の復号に必要なのは 128 バイト未満。「領域とセキュリティの無駄遣いだ(A waste of space and security)」。リバースエンジニア側から見た暗号設計の巧拙が、そのまま一次資料として残っている珍しい箇所です。

仕組みは執拗です。復号されるのは命令フェッチのみ(データ読みは素通し)。しかも FD1094 は 256 個の内部状態を持ち、プログラムが CMPI.L #$00xxFFFF, D0 という一見無意味な比較命令を実行すると状態が切り替わり、以後の復号鍵の変調が変わります。割り込みを受ければ専用状態へ、RTE で復帰。つまり実行の流れそのものが鍵の一部なのです。

さらに設計者は、エミュレーターや解析者の存在を明確に想定していました。復号結果が PC 相対アドレッシング命令になり得る値は、すべて不正命令 $FFFF に置換されるのです。PC 相対命令が使えると「プログラム自身にプログラム空間を読ませて、復号結果をデータとして書き出す」トロイの木馬が組めてしまう。それを命令セットのレベルで潰した、対ダンプ設計です。

MAME の実装 — 256 枚の復号面

MAME の fd1094_device は、第3部で見た仕組みの美しい応用例です。実機は 1 フェッチごとにオンザフライで復号しますが、MAME は逆のトレードオフを選びました。256 状態それぞれについて ROM 全体を復号した「面」を遅延生成してキャッシュし、状態遷移のたびに AS_OPCODES(命令フェッチ専用アドレス空間)のバンクを切り替えるのです。デバイス自体は m68000_device の派生クラスで、CPU コアには CMPI.L・RTE・割り込み受理のコールバックを生やすだけ。コア本体は暗号のことを何も知りません。

鍵は日時から作られていた

FD1094 の鍵抽出は CPS-2 より早く、2004年に Charles MacDonald と Nicola Salmoria がエミュレーションを実現し(MAME 0.88)、鍵は .key ファイルとして ROM セットに収められるようになりました。平文がバスに出ない設計ゆえ、抽出には電池の生きた動作品 CPU が必須です。今日でも新しい鍵の吸い出しは続いており、対応から漏れていた版が突然追加されることがあります。

そして 2007年、Aaron Giles による解析が驚くべきことを明らかにします。鍵データの下位ビットは乗数 0x29 の線形合同法(LCG)で生成されており、シード 1 個から全体が再現できるのです。さらに 2014年、Andreas Naive がそのシードの正体を突き止めました——鍵を書き込んだ日時そのものだったのです。MAME のソースには部品番号ごとの一覧表があります。『テトリス』の鍵のシードは 881129、つまり 1988年11月29日。『ゴールデンアックス』は 89/03/29 の 16 時台。連日鍵を生成していた様子や、入力ミスがそのまま全鍵に引き継がれたビットまで、25 年越しにセガ社内の作業日誌が透けて見えます。エミュレーションのための解析が、意図せずソフトウェア考古学になった瞬間です。

FD1094 はシステム16 だけのものではありませんでした。MAME のドライバーを横断すると、この暗号化 CPU はアウトラン系(ターボアウトラン)、スプライト拡張された X ボード(サンダーブレード、スーパーモナコ GP)、システム18(『マイケル・ジャクソンズ・ムーンウォーカー』ほか)、さらにはフロッピーディスクでゲームを供給するシステム24 まで、1987年から 1992年頃までのセガ基板の広範囲で使われています。fd1094.cpp が抱える鍵は約 80 種。逆に、より後期のYボードやシステムC以降には FD1094 は使われず、セガの保護戦略が次の世代へ移っていったことが、対応チップの分布から読み取れます。

システム16 にも救済の物語があります。2018年、CPS-2 と同じ Arcade Hacker チームが FD1089/FD1094 への鍵書き込み手順を公開し、MAME に蓄積された .key ファイル群は、そのまま実機修復のデータベースになりました。MAME のソースにも、Guru のコメントとしてこう残っています——非暗号化版のドキュメント化は「死んだ FD1089/1094 基板を蘇生させるのに使える」からだ、と。CPS-2 とシステム16、8 年を隔てて生まれた 2 つの「自殺する基板」が、同じチームの手で、同じように蘇生可能になった。暗号との 15 年戦争は、こうしてほぼ完全な勝利で幕を閉じたのです。

スペックで見る68000世代と、保護の必然

システム16 から CPS-2 まで、68000 を中核とする基板のスペックを並べると、なぜこの世代でコピープロテクトが激化したのかが見えてきます。

項目 システム16B(1987) CPS-1(1991) CPS-2(1993)
メーカー セガ カプコン カプコン
メイン CPU MC68000 / 10MHz MC68000 / 10MHz MC68000 / 16MHz
サウンド Z80 + YM2151 + μPD7759 Z80 + YM2151 + MSM6295 Z80 + QSound
解像度 320×224 384×224 384×224
発色数 2048色(+影/強調で4096) 3072色 3072色
背景レイヤー 2スクロール + テキスト 3スクロール(8/16/32px) 3スクロール(8/16/32px)
スプライト同時数 128 256 1024
ROM 総量 約1.0MB 約7.3MB 約18.6MB

前節までの「暗号との攻防」を、スペックの側から見直すと腑に落ちます。68000 world の 6 年間で、メイン CPU は 10MHz から 16MHz へ、スプライト同時数は 128→256→1024 と 8 倍に、そして ROM 容量は 1MB から 18.6MB へと約 18 倍に膨れ上がりました。この ROM の大半は、増え続けるスプライトとレイヤーのためのグラフィックデータです。

ここに、保護の必然があります。基板に載るグラフィック資産が巨大化し、1 タイトルの開発費が跳ね上がるほど、その ROM を丸ごとコピーされる痛手は大きくなります。システム16 の FD1094(電池でバックアップした鍵で命令を復号)も、CPS-2 のスーサイドバッテリー(電池が切れれば基板が死ぬ)も、まさにこの「守るべき資産が 18 倍になった時代」への回答でした。スペックの膨張と暗号の高度化は、同じコインの裏表だったのです。数字を並べて初めて、これらの「自殺する基板」たちの切実さが立体的に見えてきます。

3D世代の壁

第4部でここまで見てきた事例は、ベクター機を除けば、いずれも 2D 世代の基板でした。1990年代半ばのポリゴン 3D 基板——セガ Model 2/3、ナムコ System 22——になると、エミュレーションの風景は一変します。本章では、なぜ 3D 世代が今なお難関なのかを見ていきます。

MAME 0.288 の実態を数字で見ると、Model 2 は 90 セット中 62 が NOT_WORKING(『デイトナ USA』『バーチャファイター2』などは動作)、Model 3 に至っては全 62 セットが公式には NOT_WORKING です。興味深いのは、ソース内の動作メモには「works」と書かれたゲームが多数あることで、フラグと実態のこのギャップ自体が 3D 世代の検証の難しさを物語っています。2D なら「正解のドット絵」と比較できますが、3D には正解画像がありません。ナムコ System 22 のドライバーには BTANB(bug that’s also on the real board——実機にもあるバグ)という節があり、リッジレーサーの橋のケーブルが柵を貫通する現象は「実機でも起きる」と記録されています。バグか仕様かの切り分けそのものが調査対象なのです。

難しさの核心はジオメトリプロセッサーにあります。Model 2 は世代によって富士通 TGP や SHARC といった DSP がジオメトリ演算を担い、そのプログラムが DSP の内蔵 ROM に焼かれていることがある。Model 1 の TGP 内蔵 ROM が Caps0ff のデキャップでダンプされたのは 2017年のことで、それまでは挙動を推測した HLE で凌いでいました。資料の欠如も深刻で、MAME の DSP コアのコメントには「『マニュアル』と呼ぶのもはばかられる資料のおかげで拡張できた」という自嘲が残っています。

前章までの 2D 基板と、この 3D 世代のスペックを対比すると、跳躍の大きさがはっきりします。System 22(1993)はメインに 68020 を 24.576MHz で積み、その脇に座標変換専用の DSP を 2 個、さらに 640×480 という高解像度、32768 色、実搭載 20MB 級の ROM——1980年代の基板とは桁が違います。Model 3(1996)に至っては PowerPC 603e を 100MHz で回し、ROM は実搭載約 99MB。第4部の冒頭で見たインベーダー(8KB)から、実に 1 万倍を超える容量です。この物量が、そのままエミュレーションの困難さに直結しています。

それでも前進は続いています。2025年には Model 2 のトライリニアフィルタリングとマイクロテクスチャが実装され、2026年には SHARC の動的再コンパイラーが有効化されて『バーチャコップ』が working に昇格しました。30 年前の基板が、今も毎月のリリースで改善されている。また Model 3 の Supermodel、System 22 の Vivanonno(日本発)といった専用エミュレーターが先行して実用化し、その解析成果が MAME のドライバーのクレジットに刻まれているのも、この世代の特徴です。保存は一つのプロジェクトの仕事ではないのです。


第4部では、1978年のインベーダーから 1990年代のポリゴン 3D まで、アーケードハードウェアの歩みをスペックとともに辿ってきました。CPU は 8080 の 2MHz から PowerPC の 100MHz へ、ROM は 8KB から 100MB へ、描画はフレームバッファからタイル+スプライトを経てリアルタイムポリゴンへ。ここまでは一貫して「基板を再現する」話でした。

次の第5部では、視点を裏返します。正確に再現された基板の上で走るゲームそのもののコードを、MAME を顕微鏡にして覗くのです。

第5部 名作の設計を読み解く

正確なエミュレーションは、ゲームの内部をそのまま観察できる顕微鏡になります。第5部では、MAME を通して見えてくる名作の設計思想を読み解きます。限られた CPU で知能を演じる敵 AI、手触りを決める乱数と当たり判定、正確さゆえに甦る有名なバグとキルスクリーン、そして製品に残された見せるはずのなかった隠しコード——ソースとメモリの中に設計者の意図を探します。

敵AIとアルゴリズム — 限られたCPUで「知能」を演じる

第4部までは「基板を再現する」話でした。第5部では視点を裏返します。正確に再現された基板の上で走るゲームそのもののコードを、MAME を顕微鏡にして覗くのです。

MAME が単なるプレイ環境ではなく「ドキュメンテーションプロジェクト」であることの、最も鮮やかな帰結がここにあります。基板が命令サイクル単位で正確にエミュレートされているなら、その上で動くゲームのロジック——ゴーストの思考、当たり判定、乱数、そして 40 年語り継がれてきたバグの正体——も、命令単位で観察できるのです。都市伝説の時代は終わり、逆アセンブルとデバッガーの時代が来ました。

本章ではまず、敵の「思考」を扱います。数キロバイトの ROM と、毎秒数百万命令しか実行できない 8 ビット CPU で、開発者たちはどうやって「知能があるように見える敵」を作ったのか。その答えは、高度なアルゴリズムではなく、驚くほど巧妙な演出にありました。

パックマンのゴースト — ターゲット関数ひとつで生まれた「性格」

パックマンの 4 匹のゴーストには、はっきりした性格があります。赤(ブリンキー)は執拗に追いかけ、ピンク(ピンキー)は先回りし、青(インキー)は気まぐれで、オレンジ(クライド)は近づくと逃げ腰になる。この個性はどう実装されているのか。答えは驚くほどエレガントです——4 匹は完全に同一の移動エンジンを共有し、違うのは「ターゲットタイルの計算式」ただ一つなのです。1980年の 16KB ROM に実装された、ストラテジーパターンと言ってもいいでしょう。

共通エンジンの規則はこうです。ゴーストは迷路を 8×8 ピクセルのタイル格子として認識し、新しいタイルに入るたびに次の分岐で曲がる方向を先読みで決めます。180度の反転は自発的にはできません。交差点では、逆方向を除く各出口について「その先のタイルからターゲットタイルまでの直線距離」を計算し、最短の方向を選びます(距離は平方和 dx²+dy² で比較され、同着なら上・左・下・右の優先順位で決まります)。これは A* のような大域的経路探索ではなく、ターゲットへ貪欲に近づくだけの局所的な規則です。ターゲットは迷路の外の到達不能な地点でも構いません——単に「引力の源」として働くのです。

そして 4 匹のターゲットの決め方が、性格そのものです。

さらに全ゴーストは、グローバルなタイマーで「散開(スキャッター)」と「追跡(チェイス)」を波状に切り替えます。この波は 7 エントリのテーブルで表現されており、プレイヤーを追い詰めすぎないための「難易度の呼吸」になっています。面白いのはレベル 2 以降の実装で、状態機械の構造は変えずにテーブルの値だけをいじり、チェイスを 1033 秒、スキャッターをわずか 1 フレーム(1/60秒)にすることで、実質的に「ほぼ永久に追跡」を実現しています。そしてモードが切り替わる瞬間、ゴーストは全員一斉に反転します。これは内部状態の変化を、見えない形ではなく視覚情報としてプレイヤーに漏らす——意図はともかく、結果的に読み合いを成立させる仕掛けになっています。

スキャッター/チェイスとは別に、ゴーストが巣から出撃する条件も精妙に設計されています。各ゴーストは個別のドットカウンターを持ち、プレイヤーがドットを食べるたびに「巣の中で最優先のゴースト」のカウンターだけが増えて、しきい値に達すると出撃します(レベル 1 ではピンキーは即時、インキーは 30 個、クライドは 60 個)。加えて赤のブリンキーには「クルーズ・エルロイ」という特殊状態があり、残りドット数が少なくなると加速し、散開モード中でも巣に帰らずパックマンを追い続けるようになります。終盤の張り付くような赤ゴーストの怖さは、このモードによるものです。

MAME で観察するなら、デバッガーでメモリウィンドウを 0x4D00 番地に向けてみてください。4 匹のゴーストとパックマンの座標、向き、モード変数が、およそ 256 バイトの中にすべて収まっています。ゲームの「魂」がそこに丸ごと見えるのです。たとえば 0x4DC1 がスキャッター/チェイスのフェーズ番号、0x4D39 がパックマンのタイル座標(この値をピンキーのルーチンが参照します)です。

顕微鏡を覗く手順

ここで、MAME を解析の顕微鏡として使う典型的な手順を、パックマンを題材に具体化しておきましょう。MAME 内蔵のデバッガーを使う形になりますが、これまで見てきた「なぜ分かったのか」の裏側です。

まず mame pacman -debug -window で起動します。デバッガーが有効なときは起動直後に停止するので、g で走らせてゲームを進め、~ キーでいつでもブレークインできます。目当ての変数——たとえばゴーストの座標——のアドレスが分からない場合は、チートサーチ機能を使います。cheatinit ub(符号なしバイトで RAM 全域のスナップショットを取る)を実行し、ゴーストを動かして値が変わった場所を cheatnext で絞る。これを数回繰り返せば、目的のワーク RAM に行き着きます。

アドレスが分かれば、そこに書き込む命令を特定できます。wp でウォッチポイントを張り、値が変わる瞬間にブレークすれば、書き込んだ命令の PC が分かります。あるいは trackmem を有効にしてから pcatmem <アドレス> を実行すれば、「そのアドレスに最後に書いた命令の PC」が 1 コマンドで返ります。あとはその周辺を逆アセンブルして読むだけです。次章で見るピンキーのバグもキルスクリーンも、まさにこの道具立てで解明されました。パックマンの 4 匹の個性も、この手順で 0x4D00 番台のメモリを追えば、ターゲット計算のコードにたどり着けます。

スペースインベーダー — AIなきAIの砲撃

パックマンが「知能の演出」の到達点だとすれば、その原型は 1978年のスペースインベーダーにあります。第4部で見たとおり、インベーダーの「敵が減ると速くなる」挙動は、速度を上げるコードが存在せず、1 フレームに 1 体だけ動かす処理量の副産物でした。AI らしい AI はどこにもありません。しかし、その砲撃には、後の敵 AI につながる二つの手法が既に同居しています。

インベーダーの弾は 3 種類です。ひとつは「ローリングショット」——発射する瞬間にプレイヤーがいる列を狙って落とす、乱数を使わない決定論的な狙い撃ち弾です(飛び出したあとは追尾しません)。残りの 2 種類(プランジャーとスクイグリー)は、どの列から撃つかをあらかじめ用意した列テーブルから順に選びます。スクイグリーの発射列は [11, 1, 6, 3] のような固定順で巡回するのです。「自機の位置を狙う」と「データテーブルによる固定パターン」——この二つは、以後の敵 AI にも繰り返し現れる、基本的な手法です。

そして砲撃の頻度もまた、乱数ではありません。発射の間隔(リロードレート)は、プレイヤーのスコアに連動して段階的に短くなります。スコアが $0000〜$01FF のときは 48 フレーム間隔、$3000 以上では 7 フレーム間隔。スコアが上がるほど、つまり上手いプレイヤーほど激しく撃たれる。難易度カーブが、AI ではなくスコア連動のテーブル値で作られているのです。群れの速度も弾の頻度も完全に決定論的——インベーダーは「乱数もAIも使わずに、難しく感じさせる」実装の教科書です。

ギャラガ — 編隊とトラクタービーム

1981年のギャラガになると、敵は生き生きと動き出します。編隊を組み、個別に離脱し、弧を描いてダイブし、そして自機を捕獲しに来る。この複雑な振る舞いは、Computer Archeology による Z80 の解析で、その骨格が明らかになっています。

ギャラガの敵の挙動は、コマンドとサブコマンドによるテーブル駆動のステートマシンで駆動されています。あるコマンドはベクターテーブルを持ち、17 個のサブコマンドに分岐する。各サブコマンドが「編隊からの離脱」「終盤の総攻撃」「分裂」といった状態を担います。たとえば画面上の敵が 6 体未満になると発動するサブコマンドは、残った敵に「広いスイープ状の弧を描いてダイブし、画面外へ抜けていく」動きをさせます。挙動そのものをコードで書き分けるのではなく、状態をデータで管理し、汎用のインタープリターが実行する——少ないコードで多彩な敵を作る、この時代の定石です。

砲撃ロジックも解読されています。敵は自機に向けて弾を撃ちますが、そこには明確なガード条件があります。画面下端に近すぎるときは撃たない。爆発直後のディレイ中も撃たない。そして興味深いことに、自機の捕獲シーケンス中は撃たない——捕獲フラグが立っている間、砲撃が抑止されるのです。弾速は「発射時点の自機位置へ向かうのに必要な速度」を計算して決まる、いわゆるリード撃ちです。

そのトラクタービーム(自機捕獲)は、4 つの専用コマンドからなる状態機械で実装されています。ビーム開始、ビーム継続、自機の捕獲、そして解放された自機の調整。捕獲中に立つフラグが、先ほどの砲撃抑止と連動している——演出と AI がコードレベルで相互にロックされているのです。そして捕獲された自機は、ダイブしてくる捕獲ボスを撃てば救出され、横に合体して「デュアルファイター」になります。火力は 2 倍、しかし当たり判定も 2 倍。このリスクとリターンの設計は、ギャラガの戦略性の核心でした。

ゼビウス — 「なぜ敵は整列して待つのか?」

1982年のゼビウスは、敵 AI の思想そのものを転換させました。作者の遠藤雅伸は、その動機をこう語っています——「当時人気のシューティングは、敵が出てくると画面上部に整列した。でも僕は『なぜ敵はわざわざ整列して待っていてくれるのか?』と思った。生き残りたいなら動くはずだ。だからゼビウスの敵はプレイヤーから逃げようとする。無人機を除いて。無人機は体当たりしに来るように作った」。

この「逃げる敵」と「突っ込む無人機」の区別が、敵タイプごとの思考ルーチンの分岐になっています。そして、ゼビウスのもう一つの特徴が出現の決定論です。地上物も空中物も、乱数で湧くのではなく、スクロール位置に紐づいた事前配置のマップデータで出現します。だからパターンを覚えられる——ゼビウスは「暗記でクリアできる」初期の代表作になりました。地上を移動する敵は、マップ上に置かれたウェイポイントを目標に、背景と一緒に流れるローカル座標で動きます。

ただし、「ゼビウスに乱数がない」と言い切るのは不正確です。隠しフラッグ(特殊なボーナス)の水平位置はランダムとされ、「配置は覚えられても、収集には運が絡む」のです。ほぼ決定論、しかし一部に乱数——このさじ加減が、暗記による上達を報いつつ、完全な作業化を防いでいます。乱数を極力使わず、プレイヤーの習熟を報いる設計。これはアーケード名作に共通する哲学です。

ラリーX — あえて単純にするという判断

敵 AI は、高度であればあるほど良いわけではありません。それを教えてくれるのが、パックマンと同じナムコ・ほぼ同時期の『ラリーX』(1980)です。

ラリーX の敵車(レッドカー)の AI は、パックマンのゴーストより意図的に単純です。全車が同一の挙動で、スキャッター/チェイスのようなモード切り替えも持ちません。基本は「プレイヤーを追ってくる」だけ。なぜパックマンであれほど緻密な AI を作った同じ会社が、ここでは手を抜いたのか。

解析コミュニティの考察が示唆的です。ラリーX は視界が狭く、プレイヤーは迷路全体を見渡せず、レーダーで敵の位置を知る方式です。つまり、パックマン級の多モード AI を作っても、その賢さがプレイヤーに見えないのです。見えない知能は、ないのと同じ。だから「距離を保ち、突然現れる敵に反応する」というゲーム性に合わせて、単純な追跡で十分——という設計判断でした。AI の高度さと面白さは別物であり、重要なのはプレイヤーの知覚である。パックマンとラリーX という同社の対照的な 2 作が、そのことを雄弁に語っています。

見えない知能を、整数演算で

これらの事例から、アーケード期の敵 AI に共通する技術が浮かび上がります。読者(とりわけアセンブラを知る方)に刺さるのは、その「制約との戦い」でしょう。

当時の 8 ビット CPU には、高速な乗除算も三角関数もありません。にもかかわらず、敵は弧を描き、自機に狙いを定め、追いかけてきます。その秘密は、すべてを整数演算とテーブル参照に還元する技術にあります。弧や旋回は、サインテーブル・コサインテーブルを ROM に置いて角度パラメーターで引く。自機への追尾は、dx と dy の符号と大小を比較するだけで方向を 8 方向や 16 方向に量子化し、方向ごとの速度テーブルを引く(コナミの『タイムパイロット』の全方位ホーミングがこの典型です)。除算も三角関数も使わずに「狙う」「追う」を表現するのです。

手法を整理すると、四つの柱が見えてきます。第一にテーブル駆動——動きをコードではなくデータ(座標列、角度列、コマンド列)で持ち、汎用インタープリターが実行する。第二にステートマシン——「待機/離脱/ダイブ/帰還」のような状態を遷移させる。第三にプレイヤー追跡——距離最小化(パックマン)や角度指向(タイムパイロット)、リード撃ち(ギャラガ)。そして第四に疑似乱数の抑制——多くの名作は乱数を最小限にとどめ、決定論とテーブルで難易度を作りました。暗記でき、上達できるようにするためです。

そして最も重要な教訓は、技術そのものではありません。スペースインベーダーはハードウェアの制約を難易度カーブに転用し、ギャラクシアンやゼビウスは敵に独立性や逃走本能を与えて「意思がある」ように見せ、ラリーX は見えない賢さをあえて作りませんでした。「知能の演出」は、アルゴリズムの高度さではなく、プレイヤーの知覚に最適化される。限られた CPU が、かえって開発者の創意工夫を引き出したのです。

次章では、同じ顕微鏡を「当たり判定」と「乱数」に向けます。格闘ゲームのヒットボックスはどこに格納され、ダメージのばらつきはどんな式で決まるのか——ゲームの手触りを決める数字の世界を覗いていきましょう。

乱数と当たり判定 — ゲームの手触りを決める数字

前章では敵の「思考」を覗きました。本章で扱うのは、ゲームの「手触り」——当たり判定と乱数です。技が当たるか外れるか、ダメージがどれだけ入るか。プレイヤーが肌で感じるゲームの感触は、突き詰めれば ROM の中の数字とテーブルで決まっています。その数字を、MAME を通して読んでいきましょう。

ストリートファイターII — ヒットボックスは RAM にない

対戦格闘ゲームの心臓は当たり判定です。ストリートファイターII(1991、カプコン、CPS-1 基板の 68000)の内部を、解析コミュニティの成果から覗いてみましょう。

格闘ゲームのキャラクターには、複数の不可視の矩形(ボックス)が重ねられています。相手の攻撃を食らう「食らい判定」(慣例的に青)、技を出している間だけ出現する「攻撃判定」(赤)、キャラ同士が重ならないための「押し合い判定」(緑)、そして投げの成立範囲。これらの重なりで勝敗が決まります。ストII のボックスデータは、1 個あたり符号付き 1 バイト × 4(X オフセット、Y オフセット、X 半径、Y 半径)で表現されます。1 バイトなので ±128 ピクセルが限界で、ダルシムの伸びる手足のためだけに「特定の値なら別バイトで上書きする」という例外処理が仕込まれているほどです。

ここで重要なのは、このボックスデータが RAM ではなく ROM のテーブルに格納されていることです。アニメーションのフレームごとにボックス ID が振られ、キャラクターごとのテーブルをその ID で引く、という二段のポインタ参照になっています。この事実が、解析手法を決定づけます。ROM は変化しないので、チートツールのようなメモリ検索では絶対に見つからないのです。

ではどうやって解析するのか。ここに MAME デバッガーの王道ワークフローがあります。解析者 dammit の解説が本質を突いています——既知の RAM アドレス(体力やアニメ番号)にウォッチポイントを張り、数フレーム分のトレースを取り、ログを逆順に読んで「ROM からボックスを引いている命令」までさかのぼる。経験則や推測と違い、この方法は完全な確実性でゲームの動作を突き止められる、と。

こうして特定されたアドレスは、Lua スクリプトに落とし込まれ、画面にボックスをリアルタイム描画する可視化ツールになりました。MAME の Lua では、フレーム描画のたびに呼ばれるコールバックを登録し、メモリから座標を読んで画面に矩形を重ねられます。おおよそ次のような形です(実際のアドレスはゲームごとにデバッガーで特定したものに置き換えます)。

local mem =
    manager.machine.devices[':maincpu'].spaces['program']
local scr = manager.machine.screens[':screen']
local sub = emu.add_machine_frame_notifier(function ()
    -- キャラ X 座標
    -- (番地は仮。デバッガーで特定して置き換える)
    local x = mem:read_u8(0x9000)
    -- キャラ Y 座標(同上)
    local y = mem:read_u8(0x9001)
    scr:draw_box(x-8, y-8, x+8, y+8, 0xffff0000, 0x40ff0000)
end)

draw_box の色は 0xAARRGGBB のアルファ付き 32 ビットで、食らい判定を青、攻撃判定を赤、と塗り分ければ、静止画では決して分からない「発生の何フレーム目に攻撃判定が出るか」が一目で見えるようになります。ヴァンパイアセイヴァーとストZERO を最初に解析した felineki と mz、それを各作品に拡張した dammit——ツールと知見が受け継がれる系譜そのものが、MAME が「遊ぶ道具」から「研究する顕微鏡」へ変わった歴史です。今日の格闘ゲームのフレームデータ表や、FightCade によるロールバック対戦のオンラインコミュニティも、この解析文化の延長線上にあります。

ダメージ乱数とコンボ — 数式と、公認された「バグ」

同じくデバッガー解析から、ストII のダメージ計算式も明らかになっています。dammit が『スーパーストII X』を -debug で起動し、被弾処理にブレークポイントを張って導いた式はこうです。

最終ダメージ = ceil(
  (基本ダメージ + 乱数テーブル[乱数]) × 防御係数 )

乱数テーブルは 32 個の符号付きバイト値の表で、疑似乱数でどれか一つが選ばれます。防御係数はキャラクターごとに 22/32 から 27/32(ザンギエフやT.ホークは打たれ強い)。浮動小数点を持たない 68000 らしい、分数による固定小数点設計です。そして felineki が見つけた細部が味わい深い——各技は乱数テーブルを 2 組持ち、攻撃側の残り体力が 60 以上か 59 以下かで切り替わります。追い詰められた側のダメージはばらつきが大きくなる。「KO 間際ほどドラマチックに」という設計意図が、コードから透けて見えるのです。ちなみに初代『ストII』には、通常のボタン入力が 1/512 の確率で必殺技に化けるという隠し乱数もありました(『ダッシュ』で削除されています)。

投げの設計にも、格闘ゲームの試行錯誤の跡が残っています。ストII 系の通常投げは「相手に近く、レバー横+ボタン」で発生 0 フレーム(即時成立)、外せば通常技が出るためスカりモーションがありません。そして初代から『ハイパーファイティング』までは、投げを抜ける手段が一切ありませんでした。この非対称性が「投げハメ」を生みます。開発者の西谷亮氏は後に、完成後いちばん気がかりだったのがこの投げハメで、直す時間がなく「不発弾を抱えているような気分だった」と語っています。この宿題は『スーパーストII X』で投げ抜け(食らった直後 13 フレーム以内にコマンド入力するとダメージ半減)が導入され、ようやく解決されました。仕様の欠陥を認識し、続編でパッチする——ソフトウェア開発そのものの営みが、格闘ゲームの進化史に刻まれています。

そして、格闘ゲームの歴史を変えた「コンボ(連続技)」。これが意図されざる副産物から生まれたことは、企画者の西谷亮氏本人が複数の場で証言しています。1991年の開発者インタビュー、2013年の発言、カプコン公式のコラム——いずれも一致するのは、必殺技コマンドの入力受付を寛容にした結果、通常技のモーション中に完成したコマンドがそのモーションを打ち切って必殺技を発動できてしまった、という説明です。西谷氏はこう述べています。予期していなかったが、開発中にゲームに取り込むと決めたのだから、バグとは呼ばない、と。偶然の発見を仕様に昇格させる——ゲーム開発文化を象徴するエピソードです。

乱数は、乱数ではない

ここで一歩引いて、「乱数」そのものを考えてみましょう。第2部(エミュレーションの理論)で見たとおり、MAME はエミュレーター自身の乱数を固定シードの線形合同法で決定論的にしています。では、ゲームの中の乱数はどうでしょうか。

驚くべきことに、アーケード名作の多くは、そもそも真の乱数をほとんど使っていません。前章で見たパックマンがその極致です。ゴーストの動きは完全に決定的で、パワーエサで青くなったゴーストの「ランダムな」動きすら、疑似乱数生成器が ROM のアドレスを生成してその内容の下位ビットを方向に使うという実装で、しかもレベル開始ごとに同じシードでリセットされます。つまり同じ入力列に対しては、常に同じ結果が返る。だからこそパターン攻略が成立します。パックマンで唯一の非決定的要素はフルーツの表示時間(9〜10秒で変動)だけで、パターンプレイヤーがフルーツを急いで取りに行くのは、このためなのです。

なぜ乱数を避けるのか。それは上達を報いるためです。乱数だらけのゲームは、どれだけ練習しても運に左右され、習熟が報われません。逆に決定論的なゲームは、パターンを覚え、精度を上げるほど確実に上手くなれる。ゼビウスが「暗記でクリアできる」ことを誇りにしたのも、スペースインベーダーが難易度をスコア連動のテーブルで決めたのも、同じ思想です。乱数は「毎回違う」新鮮さを与える一方、「努力が報われる」感覚を奪う諸刃の剣。名作たちは、その使いどころを慎重に絞っていました。

そして実機の乱数が決定論的である以上、それをそのまま実行する MAME の中でも、ゲームの乱数は完全に再現されます。ここに、本書を貫く一つの事実が改めて現れます——決定論はエミュレーターが足すものではなく、元のハードウェアが決定論的な機械だったことの帰結なのです。ダメージのばらつきも、ゴーストの動きも、乱数に見えて実は計算可能。MAME はその計算を、命令サイクル単位で忠実になぞっているにすぎません。ゲームの「手触り」は、突き詰めれば決定論的な数式とテーブルの産物なのです。

次章では、その決定論的な計算が「意図せぬ結果」を生む瞬間——有名なバグとキルスクリーンを見ていきます。正しく再現された計算だからこそ、当時のバグまでもが寸分たがわず甦るのです。

有名なバグとキルスクリーン — 正確さは欠陥も甦らせる

正確なエミュレーションには、ひとつの必然的な帰結があります。それは、名作の輝きだけでなく、当時のバグまでも寸分たがわず甦らせるということです。CPU の計算を命令サイクル単位で忠実になぞる以上、実機で起きていた計算ミスや桁あふれも、そっくりそのまま再現される。本章では、逆アセンブルによって機序が完全に解明された、有名なバグとキルスクリーンを見ていきます。これらはもはやゲーム攻略の噂話ではなく、命令レベルで説明できるコードの挙動です。

ピンキーのバグ — 16ビット加算という古典的な罠

パックマンには、あまりに有名なバグがあります。ピンキーは「パックマンの 4 タイル先」を狙うはずなのに、パックマンが上を向いているときだけ、ターゲットが「4 タイル上」ではなく「4 タイル上かつ 4 タイル左」になるのです。攻略パターンはこの癖を織り込んで組み立てられてきました。原因は、Don Hodges による Z80 逆アセンブル解析で命令レベルまで特定されています。

パックマンでは方向がベクトルとしてもエンコードされており、ROM には各方向の (Y成分, X成分) が格納されています。右は (0xFF, 0)、下は (0, 1)、左は (1, 0)、そして上は (0, 0xFF) です(0xFF は 8ビットの −1)。隣接 1 タイルの計算には、Y と X を別々に 8ビット加算する正しいルーチンがあります。ところがピンキーのターゲット計算ルーチンは、ベクトルの 4 倍を作るのに、Y と X をパックした 16ビットレジスタペア HL を一括で加算してしまうのです。

2792 2A1C4D    LD HL,(#4D1C)  ; HL ← 方向ベクトル(X<<8|Y)
2795 29        ADD HL,HL      ; 2倍
2796 29        ADD HL,HL      ; 4倍
2797 19        ADD HL,DE      ; 座標に加算 → ターゲット

ADD HL,HL は 16ビット加算なので、下位バイト(Y)からのキャリーが上位バイト(X)に伝播します。上向きのベクトル (X=0, Y=0xFF) を 2 倍すると、Y の 0xFF×2 = 0x1FE のあふれが X 側に +1 として混入し、4 倍すればそれが「4 タイル左」として現れる。右・下・左のベクトルではたまたまキャリーが出ないか無害なため、上向きのときだけ症状が出るのです。同じコードが 4 方向すべてに使われており、上だけ結果が違う——だからこれは意図ではなくバグだと判断できます。おそらくプログラマー自身も気づいていませんでした。

これは MAME の顕微鏡としての性能を示す絶好の題材です。デバッガーで bp 278e(このルーチンにブレークポイント)を張り、パックマンを上に向けた状態で止めれば、ADD HL,HL の実行で HL のキャリーが X バイトに漏れていく様子をレジスタ表示で目視できます。「負数を含む 2 成分ベクトルを、パック表現のままスカラー倍してはいけない」という古典的な教訓が、40年前の名作の中に、そして今動いているエミュレーターの中に、生きた形で見えるのです。

パックマン 256面のキルスクリーン — 3つの偶然の合成

パックマンをレベル 256 までプレイすると、画面の右半分が意味不明なタイルで埋め尽くされ、進行不能になります。「キルスクリーン」として知られるこの現象も、逆アセンブルで完全に解明されています。原因は 3 つの偶然の合成です。

第一に、レベル番号は 0 始まりの 1 バイト変数(0x4E13)で管理されています。256面ではこの値が 0xFF です。第二に、画面右下のフルーツ表示ルーチンは、この値に 1 を足して「表示すべきフルーツ数」を求めます。

2BF0 3A134E  LD A,(#4E13) ; A ← レベル番号(255)
2BF3 3C      INC A        ; +1 → 8ビットで桁あふれし A=0
2BF4 FE08    CP #08       ; 8未満か? → 少数フルーツパスへ
2BFC 47      LD B,A       ; B ← 0 をループカウンターに

INC A で 0xFF+1 = 0x100 となりますが、8ビットに切り詰められて A=0 になります。キャリーフラグは立ちますが、チェックされていません。そして第三の偶然——ループカウンター B に 0 が入り、Z80 の DJNZ(デクリメントして 0 でなければループ)命令に渡されます。DJNZ は先にデクリメントするので、B=0 は 0→255→254…と回り、256 回ループするのです。

ループは 1 回ごとにフルーツ表のポインタと VRAM の書き込み先を 2 バイトずつ進めます。本来 20 個しかないフルーツ表を突き抜けて、後続の無関係な ROM データを「フルーツ」として画面に描き続ける。しかもパックマンの VRAM は「下 2 行 → 右上から縦方向のストリップ → 上 2 行」という特殊な並びなので、書き込みが進むにつれ描画位置が画面の右半分を縦に走査し、あの特徴的な「右半分だけ化ける」見た目になるのです。化けたタイルの中に含まれるドットを全部食べても 244 個には届かないため、面クリアは永遠に不可能——これがキルスクリーンです。

3 つの偶然のどれか一つでも欠ければ起きません。そして Hodges が示した修正は、わずか 9 バイトの命令並べ替えで済みます。MAME でこれを再現したいなら、256面まで到達する必要はありません。デバッガーで 0x4E13 に 0xFF を書き込んで面クリアさせれば、キルスクリーンを「召喚」できます。読者が必ず試したくなる実験でしょう。

ドンキーコング — レベル22で即死するボーナスタイマー

パックマンのキルスクリーンが「表示の破綻」だったのに対し、ドンキーコングのキルスクリーン(レベル22)は「即死」です。22面を開始すると、プレイヤーは何もできないまま、開始直後にボーナスタイマーが尽きて死んでしまう。これも Don Hodges の Z80 逆アセンブル解析で、原因が命令レベルまで特定されています。

ボーナスタイマーの初期値は、レベル番号から (レベル×10)+40 という式で計算されます。ところが、この計算が 8 ビットの A レジスタで行われるのです。レベル22 では 22×10+40 = 260。しかし 260 は 8 ビット(0〜255)には収まりません。260 を 256 で割った余り、すなわち 4 になってしまうのです。本来 400 あるべきボーナスタイマーが、わずか 40(表示上)相当の値に化ける。プレイヤーが操作を始める間もなく時間切れになり、即死する——これがドンキーコングのキルスクリーンの正体です。

原因は、パックマンのキルスクリーンと同じ 8 ビットの桁あふれ(オーバーフロー)であり、しかも桁上がり(キャリー)を検査していない点まで共通しています。Hodges が示した修正も同系統で、入力値を上限で飽和させる(4 に化ける前に頭打ちにする)わずかな命令の追加で済みます。8 ビット CPU の時代、(レベル×10)+40 という何気ない計算が、なぜ 26 面ではなく 22 面で破綻するのか——それは 22×10+40 が 256 を超える最初のレベルだからです。整数の器の大きさが、ゲームの寿命を決めていたのです。

桁あふれが作る「意図せぬ上限」

パックマンとドンキーコングのキルスクリーンは、いずれも整数オーバーフローという同じ根を持っていました。この種のバグは、当時のゲームに広く潜んでいます。8 ビットや 16 ビットのカウンターが一周する、BCD(2進化10進)演算で桁があふれる、レベルカウンターの扱いを誤る——限られたビット幅の器に、開発者が想定した以上の値が入ったときに、「意図せぬ上限」が生まれるのです。

象徴的なのが、スコアの「カウンターストップ(カンスト)」です。多くのゲームは、スコアを決まった桁数で表示・保持します。その桁数を超えるスコアを稼ぐと、表示が一周して 0 に戻ったり、内部の値が破綻したりする。プレイヤーにとっては「究極の目標」ですが、開発者にとっては「そこまで誰も到達しないだろう」と踏んだ設計上の割り切りでした。アーケードゲームが数十分で終わることを前提に、スコア変数の器を切り詰めた結果です。

こうしたバグを「本物のバグ(逆アセンブルで機序が判明したもの)」と「都市伝説(実際には起きない、あるいは条件が違うもの)」に切り分けることも、解析の重要な仕事です。たとえば、あるゲームの特定面が「クリア不能なキルスクリーン」と噂されても、実際には(難しいが)突破可能で真のキルスクリーンではない、という例もあります。噂と事実を分けるのは、結局のところコードを読むことなのです。

逆アセンブルは都市伝説を終わらせる

MAME を顕微鏡として使う文化は、長年の噂話に決着をつけてもきました。

『ゼビウス』の「バキュラは 256 発撃ち込むと壊せる」という有名な伝説。これは作者の遠藤雅伸氏本人が明確に否定しています。開発当初は氏自身も耐久カウンターが 8 ビットで一周する可能性を考えたものの、後にプログラムを見直したところ破壊不能にする例外処理が入っていたことを確認した、と。そもそもザッパーの連射制限のため、1 枚のバキュラに 256 発当てること自体が物理的に不可能でした。皮肉なことに、この「256 発」という噂の背後には、まさに本章で見てきた 8 ビットの桁あふれへの直感があったのでしょう——だが、実際のコードはその桁あふれを防いでいたのです。

一方で、逆アセンブルが「本物のバグ」を完全に解明した例もあります。『ギャラガ』の「敵が一定時間撃たなくなる」トリック——特定のパターンで敵をやり過ごすと、以後まったく弾を撃たなくなる、という有名な裏技です。Computer Archeology による Z80 ×3 の全コード解析で、原因はこう特定されています。特定条件下で敵弾が X 座標 0 で発射されると、弾の移動ルーチンは X=0 のスプライトを「無効」とみなして無視し、一方で弾を消去するルーチンは Y 座標しか見ない。この食い違いのため、その弾スロットが「使用中だが動きも消えもしない」宙ぶらりんの状態に陥り、永久に解放されなくなります。敵の弾スロットは 8 個。それがすべてこの状態になると、敵は物理的に新しい弾を発射できなくなるのです。

これは現代のプログラマーにも馴染み深い、リソースリークの古典です。確保したハンドルが特定の経路で解放されずに溜まり、プールが枯渇する——40 年前のアーケードゲームで起きていたのは、まさにそれでした。Computer Archeology は原因の特定にとどまらず、修正パッチまで示しています。逆アセンブルの精度がここまで来ると、それはもはやゲーム攻略ではなく、コードレビューです。

都市伝説を作るのも壊すのも、結局はコードを読むことです。そして MAME は、そのコードが動く様を命令サイクル単位で観察できる、唯一の顕微鏡なのです。ここまで第5部では、敵の思考、当たり判定と乱数、そしてバグと、正確に再現された基板の上でゲームのロジックを覗いてきました。次章では、開発者が意図的に残した秘密——隠し要素とプロテクトへと話を進めます。

隠し要素とプロテクト — 製品に残る、見せるはずのなかったコード

第5部ではここまで、正確に再現された基板の上でゲームのロジックを読んできました。本章で扱うのは、そのロジックの周縁——製品 ROM の中に残された、本来ユーザーに見せるつもりのなかったコードやデータです。開発者が仕込んだ裏技やイースターエッグ、消し忘れたデバッグ機能、そしてコピーを防ぐために組み込まれ、結果的にゲーム体験そのものを左右したプロテクト。これらはいずれも「製品には現れないはずのもの」ですが、バイト単位で正確なエミュレーションと、MAME のデバッガー・グラフィックビューアによって、今日では系統的に発掘・検証できます。第4部で見たハードウェア暗号とは切り口を変え、ソフトウェアに刻まれた秘密を読んでいきましょう。

消し忘れられた裏口 — コナミコマンド

ここまではアーケード基板の話を続けてきましたが、「消し忘れられたコード」という本章の主題を最も象徴するのは、実は家庭用に起源を持つひとつのコマンドです。隠しコマンドの代名詞、コナミコマンド(↑↑↓↓←→←→BA)——その出自には、まさに本章の主題に直結する、意外な事実がふたつあります。

第一に、本籍地はアーケードではありません。考案したのはコナミの橋本和久で、1985年のアーケード版『グラディウス』を翌年ファミコンへ移植する作業中に、デバッグ用として仕込んだものです。テストプレイで自分ではクリアできず、「ポーズ中に入力すると一式のパワーアップが付く」ショートカットを用意した——それがコナミコマンドの始まりでした。第二に、それは消し忘れられたのです。製品版から削除するつもりが忘れられ、そのまま出荷された。アーケードの『グラディウス』基板にはこのコマンドは存在しません(そもそもアーケード版にポーズはありません)。攻略情報で「グラディウスでコナミコマンド」とあるものの多くは、家庭用移植版を指しています。北米でこれを決定的に有名にしたのは『魂斗羅』の残機30コマンドで、以後コナミ作品の定番となり、やがてゲームの外——Web サイトのイースターエッグにまで波及していきました。

技術的には、コナミコマンドは「直近 N 回のボタン入力を循環バッファ(リングバッファ)に保存し、規定パターンと突き合わせる」という実装です。骨子はこうなります。

static const uint8_t KONAMI[10] = {
    UP,UP,DOWN,DOWN,LEFT,
    RIGHT,LEFT,RIGHT,B,A };
uint8_t history[10]; int idx = 0; // リングバッファ
// 「押した瞬間」だけ記録(エッジ検出)
on_new_button_edge(uint8_t input) {
    history[idx] = input;
    idx = (idx + 1) % 10;
    // idx を起点に history[] を KONAMI[] と
    // 順に比較 → 全一致で発動
}

ポイントは 2 つ。ひとつはエッジ検出——前フレームの入力との XOR を取り、「今まさに押された」ビットだけを記録します(押しっぱなしを 1 回と数えるため)。もうひとつは末尾一致——最後に入力された 10 個が規定列と一致すれば発動する方式です。6502 のようにモジュロ命令を持たない CPU では、リングバッファのポインタのラップアラウンドを手作業で処理します。なお、ファミコン版グラディウスや魂斗羅の実 ROM を逆アセンブルしてバッファ長や照合ルーチンの正確な番地を特定した一次資料は、筆者が調べた範囲では見当たりませんでした。上記は一般的な実装パターンに基づく説明です。

裏技はコナミの専売特許ではありません。タイトーの『バブルボブル』(1986)は、特定面をノーミスで到達すると隠し部屋が開き、そこに暗号化されたヒント文が表示される——ゲーム内のヒントそのものが謎解きになっている、凝った作りでした。

最古のイースターエッグは、無料クレジットだった

開発者が製品にこっそり署名やメッセージを潜ませる「イースターエッグ」。その最古級の例が、アーケードの黎明期に見つかっています。

Atari の『Starship 1』(1977)です。元 Microsoft の Ed Fries が 2017年に発掘し、判明している最古のアーケード・イースターエッグとしてギネス世界記録に登録されました。特定のボタンを押しながらコインを投入し、素早く操作すると、画面に “HI RON!”(共同設計者 Ron Milner へのメッセージ)が表示され、しかも 10 クレジットの無料ゲームが付きます。技術的に興味深いのは、このメッセージを出す処理が、ソースコード上一見して実行されないように偽装されたサブルーチン(hir という名で、コメントもなし)として潜ませてあったことです。40年後、本人ですら手順を思い出せず、別の元開発者たちがコードとトリガ条件を突き止めて復元しました。

開発者の署名はその後も受け継がれます。セガ AM2 の鈴木裕は、『スペースハリアー』や『アウトラン』に自分のクレジットとビルド日付を仕込みました。アウトランでは特定操作で “PROGRAM YU SUZUKI 1986 SEP” が表示されます。この「ビルド日付が ROM に焼かれている」という事実は、次に見るデバッグ残存と技術的に同根で、開発当時のスナップショットが製品にそのまま凍結されていることを意味します。ナムコの『スーパーパックマン』(1982)に至っては、サービスモードでの隠しメッセージに加え、MAME のグラフィックビューアで ROM をのぞくと開発者のクレジットが絵として埋め込まれているのが確認できます。

デバッグ機能は消えずに残る

イースターエッグと地続きなのが、消し忘れられたデバッグ機能です。開発中に使ったテストモード、ステージセレクト、無敵——これらが製品 ROM に残っている例は、枚挙にいとまがありません。鈴木裕の隠しクレジットもスーパーパックマンのサービスモードも、要するに「開発中の検査機構が製品に残った」ものです。The Cutting Room Floor のようなアーカイブは、各タイトルの残存デバッグ機能を系統的に記録しています。

ここで MAME が「発掘装置」として力を発揮します。多くの基板は DIP スイッチにテストモードやフリープレイ設定を持ち、MAME ではメニューから切り替えられます。開発用の隠し設定が未使用ビットに残る例もあります。そして MAME のチート機構——コミュニティが解析した「無敵」「ステージセレクト」などのメモリ書き換えを XML で記述したもの——は、「そのゲームのワーク RAM のどの番地が何を意味するか」を解析した成果物です。MAME 内蔵デバッガーの watchpoint と組み合わせれば、裏技の発動時に変化する RAM を突き止め、その正体を暴くことができます。隠し要素を探すこと自体が、前章まで見てきた解析ワークフローの実践なのです。

プロテクトがゲームを握るとき

さて、本章の核心に入ります。コピープロテクトは、単に「起動を止める」だけの仕組みではありませんでした。時にそれはゲームのロジックそのものを握り、結果としてコピー版の体験を劣化させる設計へと進化しました。

最も鮮やかな実例が、タイトーの『オペレーションウルフ』(1987)です。この基板には「C-chip」と呼ばれる保護用のカスタム MCU が載っていました。MAME の解析ドキュメント(attic/opwolf_cchip.txt)が明らかにしたところによれば、この C-chip は単なるコピー防止装置ではなく、各レベル固有のロジック——敵の湧き方、難易度、演出——そのものを担っていたのです。海賊版基板は C-chip を再現できなかったため、全レベル共通の一般的な処理だけを実装し、レベル固有の部分を省くしかありませんでした。

その差は歴然です。ドキュメントにはこうあります——「『敵に発見された』カットシーンは、オリジナルではレベル間にランダムに出現するが、ブートレグでは意図的に無効化されている(deliberately disabled in the bootleg)」。レベル 5 での敵全滅時のスクロール、レベル 4 クリア時の特殊な爆発演出、特定ラウンド後の難易度リセット——こうしたレベル固有の演出が、コピー版ではことごとく欠落するのです。プロテクトがゲームロジックを握っていたからこそ、コピーは「動くが、別物」になる。そして MAME は、その差分を一行一行ドキュメント化しています。保存プロジェクトとしての MAME の価値が、ここに凝縮されています。

プロテクトには、逆アセンブル自体を妨げる巧妙な手口もありました。Atari の SLAPSTIC(1984〜1990)は、バンク切り替えを兼ねたプログラムフロー撹乱型の保護 IC です。MAME のソース(slapstic.cpp)の解説によれば、これは「1984年から1990年にかけて複数のアーケードゲームで、バンク切り替えとセキュリティに使われた」チップで、特定のアドレス列(必ず $0000 へのアクセスで始まる、呪文のような順序)を踏むとバンクが切り替わります。制御フローがコードそのものではなく「メモリアクセスの順序」に隠されているため、静的な逆アセンブルでは追えません。その目的は、店舗オペレーターが勝手に EPROM を焼いて別ゲームへ「アップグレード」するのを防ぎ、正規キットの購入を強制することでした。MAME はこの厄介な仕掛けを、ステートマシンとして完全に再現しています。難読化する側と、それを解いて再現する側の攻防が、対になって残されているのです。

もっとも印象的なのは、プロテクトの発想の転換を示す逸話です。あるアーケード基板は、通常どおり起動しますが、100 ゲームを経過すると、画面にコピーライト表記が実際に描画されているかを別のルーチンで検査したといいます。海賊版業者は法的リスクを避けるためコピーライト文字列を消していたので、消えている個体では「Kick me for a free game(蹴れば無料ゲーム)」と表示され、キックスイッチで無料クレジットが入るようになる——つまり、その基板を「稼がない」個体に変えてしまうのです。プロテクトの目的を「起動を止める」から「経済的に成立させない」へずらした、鮮やかな発想でした(ただしこれは開発者の伝聞として伝わる逸話で、具体的なタイトル名は特定されていません)。

秘密を掘り起こす道具として

本章で見てきた隠し要素とプロテクトは、いずれも「製品には現れないはずのもの」でした。開発者の署名、消し忘れたデバッグ機能、そしてゲームロジックそのものを握るプロテクト。これらが今日、系統的に発掘・検証できるのは、MAME がバイト単位で正確なエミュレーションを実現し、かつグラフィックビューア・デバッガー・チート機構という発掘の道具を備えているからです。

第4部の暗号との攻防が「正規の個体だけが正しくゲームを提供できる」という体験設計だったとすれば、本章の隠し要素は「開発者が製品に残した、もうひとつの物語」です。どちらも、ソースやバイト列を注意深く読むことでしか見えてこない。そして、それを読めるようにするのがエミュレーターの役割なのです。ここまで第5部では、正確に再現された基板の上で走るゲームのコードを——敵の思考から、当たり判定と乱数、有名なバグ、そして製品に残された秘密まで——MAME を顕微鏡にして読み解いてきました。

おわりに

本書は、MAME を「遊ぶ道具」ではなく「読む対象」として扱ってきました。最後に、その営みが何を意味するのかを考えて筆を置きます。

私たちが見てきたのは、一貫して「正確さへの執念」でした。水晶発振子の周波数を裸の数値ではなく型で書かせる仕組み。実基板でデコードされないアドレス線を .mirror() として記録する律儀さ。8 ビットのラッチ書き込み 1 回のたびにタイマーを 1 個確保してでも守る、CPU 間同期の厳密さ。「音は ROM に入っていない、回路そのものが楽器だ」と言わんばかりに、555 タイマーの抵抗値まで書き写すディスクリートサウンドの実装。そして、電池が切れて自殺した基板の暗号鍵を、15 年かけて数学とハードウェアの両面から取り戻した執念。

これらはすべて、「ゲームが遊べればよい」という目的からは説明できません。説明できるのは、ただ一つの動機——このハードウェアが存在したという事実を、後世に正確に残すという保存の意志だけです。MAME の公式ドキュメントが「ソフトウェアが動くのは、ドキュメントの正確さを検証するためだ」と言い切るとき、それは詭弁でも謙遜でもなく、コードのすみずみに刻まれた事実の要約なのです。

この視点に立つと、いくつかのことが違って見えてきます。

MAME のソースコードは、失われゆくシリコンの技術資料です。基板が錆び、電池が切れ、チップのマスク ROM が読めなくなっても、そこに何が起きていたかは C++ のコードとして残ります。デキャップされた QSound チップの内蔵 ROM、日時から生成されていたと判明したセガの暗号鍵、「完全なでっち上げ」と告白された保護 MCU のシミュレーション——それらはハードウェアが物理的に消えた後も参照できる、人類の電子娯楽の一次資料です。

そして MAME は、ゲームそのものを理解する顕微鏡でもあります。パックマンのゴーストがなぜあの動きをするのか、ピンキーがなぜ上向きのときだけ左にずれるのか、スペースインベーダーがなぜ加速するのか、ギャラガの敵がなぜ撃たなくなるのか。これらの問いに、憶測ではなく命令サイクル単位の答えを与えられるようになったのは、正確なエミュレーションがあってこそです。逆アセンブルは都市伝説を終わらせ、代わりに検証可能な知識を残しました。

保存は、一人の、あるいは一つのプロジェクトの仕事ではありません。クリスマスイブに一人の開発者が書き始めたコードは、世界中の数百人の貢献者、赤字を承知で基板を買い続けるダンピングコミュニティ、レーザーでチップを開封する専門家、そして FPGA で別の形の再現を目指す人々を巻き込む、巨大な協働へと育ちました。その成果は、法制度による保存の追認を引き出し、ときには権利者自身による公式復刻の呼び水にすらなっています。

30 年前に稼働を終えた基板が、今も毎月のリリースで少しずつ正確になっていく。それは進歩というより、記憶を確かなものにし続ける作業に近いものです。動かなくなった機械の中で何が起きていたのかを、私たちはまだ完全には知りません。だからこそ MAME の開発は終わりません。そしてそのソースコードは、これからも読まれるのを待っています——遊ぶためではなく、知るために。

付録

A. 参考資料集

本書の記述は、可能なかぎり一次資料で裏を取っています。さらに深く知りたい読者のために、主要な情報源を整理しておきます。

公式資料

ソースコードの読み方

MAME のソースを自分で読むなら、本書がたどった順路が一つの入口になります。

特定バージョンのソースを固定して読みたい場合は、GitHub のタグ(本書は mame0288 を基準にしました)を指定すると、行番号までこの本と一致します。

歴史・保存活動

ゲーム解析

関連プロジェクト

B. 本書の成り立ちとライセンス

制作について

本書は、企画・監修を Keijiro Takahashi が、一次資料の調査と本文の執筆を Claude(Anthropic の AI)が担う形で制作されました。各章は、執筆に先立って MAME の公式サイト、GitHub 上のソースコード(タグ mame0288)、開発者の公開文書などの一次資料を調査し、確認できた事実のみを記述しています。歴史的な逸話のうち一次資料で裏付けられなかったものは、魅力的であっても採用を見送りました。バージョン番号や行番号は執筆時点(2026年)の MAME 0.288 系列に基づいており、将来のバージョンでは変わりうることをお含みおきください。

原稿は Markdown で書き、pandoc でリフロー型 EPUB に変換しています。固定レイアウトではないため、読者の端末でフォントサイズや行間を自由に変更できます。

ライセンス

本書の文章は CC0 1.0 Universal(パブリックドメイン献呈) の下に置かれています。制作者は、本書の文章に関する著作権その他の権利を、法律上可能なかぎり放棄します。誰でも、許諾も帰属表示もなしに、複製・改変・再配布・翻訳・商用利用ができます。

ただし、本文中に引用した MAME のソースコード断片は、この限りではありません。それらの著作権は MAME の各開発者に帰属し、BSD-3-Clause または GPL-2.0+ ライセンスの下にあります。本書がこれらを引用できるのは、MAME 自身が「ソースコードはドキュメントであり、精査し理解するために使うものだ」と述べているからであり、本書はまさにその実践です。なお「MAME」は登録商標であり、本書は MAME 開発チームによる公認・提携を受けたものではありません。

この方針は、MAME のライセンス思想——知識をできるだけ自由に保存し、参照し、再利用できるようにするという方向性——に敬意を表したものです。権利を握って囲い込むのではなく、手放して広く役立てる。本書自身のあり方で、それを実践したいと考えました。

最後に

MAME は生きたプロジェクトです。この本を読み終えたら、ぜひ実際にソースコードを開き、デバッガーを起動し、動いている基板の中を覗いてみてください。そこには、まだ誰も完全には書き終えていない、ハードウェアの物語が待っています。