ハクソク

世界を動かす技術を、日本語で。

F#でゲームボーイエミュレーターを作成しました

概要

  • Game Boyエミュレータ「Fame Boy」開発の経緯と学び
  • F#によるドメインモデリングと型システム活用
  • 実ハードウェアに近い構成とシンプルなインターフェース設計
  • テスト駆動開発やAI活用による効率的な実装
  • PPU(ピクセル処理)実装の難しさと学び

Game Boyエミュレータ開発のきっかけ

  • 8年以上ソフトウェアエンジニアとして働くも、コンピュータの仕組みを深く理解していなかった課題意識
  • 子供の頃にPokémonに熱中した経験から、Game Boyをエミュレート対象に選定
  • いきなり実装せず、まずFrom NAND to Tetrisで基礎を学習
  • エミュレータ開発の練習として**CHIP-8エミュレータ(Fip-8)**をF#で作成
  • 数ヶ月間の夜更かしの末、Fame Boyが完成
    • サウンド対応
    • デスクトップ・Web両対応

エミュレータの設計と構成

  • デスクトップとWeb両対応を目指し、コアとフロントエンド間のインターフェースを極力シンプルに設計
    • framebuffer:160x144の色調配列
    • audiobuffer:32768Hzのリングバッファ
    • stepEmulator():1命令実行し、消費サイクル数を返却
    • getJoypadState(state):フロントエンドからジョイパッド状態をコールバック
  • 実機ハードウェアに近い構成を意識
    • CPUはSharp LR35902を模し、ハードウェア知識はメモリマップのみ
    • Memory.fsがRAMやバスの役割を担い、VRAM/OAM RAMはPPUと共有
    • IoController.fsでI/Oレジスタを一元管理し、安全性向上
  • stepper関数で各コンポーネントの処理を逐次実行し、同期を実現
    • 実機は並列だが、エミュレータはシングルスレッドで逐次処理
  • 正しい動作速度(1フレーム約17500CPUサイクル)を維持するため、サウンド有効時はオーディオサンプリングレートで、ミュート時はフレームレートで駆動

F#によるCPUエミュレーションとドメインモデリング

  • F#の型システムを活用し、CPU命令を明確にモデリング
    • 例:LoadInstrArithmeticInstrなど命令種別を区別
    • From/To型でオペランドの位置を抽象化し、不正状態を型で防止
  • 命令数削減:512個のオペコードを58命令まで一般化
  • F#のパターンマッチOption型の使いやすさを実感
  • 副作用の許容:CHIP-8エミュレータは純粋関数型だったが、Fame Boyではパフォーマンス重視で可変性を利用

フラグ操作とシンプルな設計

  • setFlags関数の設計改善
    • 配列+型で冗長だったものを、純粋関数にリファクタ
    • inline関数でヒープ割当を回避し、FPSが約10%向上
    • シンプルかつ合成可能な実装に満足感

テストとAI活用

  • 最初はTetris ROMで動作確認しながら命令実装
  • 課題:テストケース漏れ・命令群ごとの集中実装が難しい
  • 解決策:AIに技術仕様からテストケース生成を依頼し、テスト駆動開発を実践
    • バグ発見や学習効率向上に寄与
  • 学習重視のため、AIはテスト生成に限定し、実装は自身で担当

PPU(ピクセル処理ユニット)の難しさ

  • Game BoyにはGPUではなくPPU(ピクセル処理ユニット)が搭載
  • 多くのブログがCPU中心で、PPUは数段落のみ
  • PPU実装は創造的というより「手順をなぞる」機械的作業が多い
  • From NAND to TetrisCHIP-8エミュレータ経験でCPUは理解しやすかったが、PPUは習得に時間を要した
  • ピクセル単位での処理に苦労しつつも、実装を通じて理解が深まった

このように、Fame Boy開発を通じてコンピュータの仕組みF#の強みテスト駆動開発の有用性、PPU実装の難しさなど、多くの学びと発見を得ることができた。

Hackerたちの意見

ついに誰かが実際に人間の努力をして何かを学んでるね。「LLMが助けてXをY分で作った」ってのじゃなくて。人類にもまだ希望があるのかな。
いつまでも存在し続けるよ。2026年になっても手工具で何かを作る人がいるしね。これを「アーティザナルコーディング」って呼ぼう。
長年のF#開発者で、STEMの学問的いじめを受けてきた者として、ChatGPT-3.5がF#のGitHubリポジトリからのコピペがひどくて明らかだったから、LLMを使うのは拒否してる。AGIを感じたことはなくて、ただ装飾が剥がれた盗作マシンを見ただけだった。結局、Microsoftの誰かが気づいてRLHFのアラームを鳴らしたから、GPTはかなり改善されたみたい。F#にはかなり使える感じだね。今は無原則なF#erがエージェントでうまくやってるんだろうけど、「やった、盗作問題が解決された、さあ適当なものを生成しよう!」とは思わなかった。「ああ、これでChatGPTが盗作しても明らかじゃなくなるんだ」と思った。自分のコアバリューを妥協して生産性の利益を得るためにd100やd1000を振りたくない。遅くて無職のままでいいよ、ありがとう。これは真剣な話で、今はソーラーインストールやゴミ運びを始めようとしてる。 [1] 「学生は考えたくない」という問題はLLMよりずっと前からある。2007年に上級のPDEのクラスを取ったとき、ほとんどの人が私の宿題をコピーしてた。私はPDEを勉強する意欲があったから、怠け者の数学専攻に抵抗できなかったんだ。それが数学の大学院でも再び起こった!本当に信じられない。なんでプログラムにいるの?
F#はめっちゃ楽しい!素晴らしい仕事だね!
それめっちゃクール!F#大好きだけど、小さなSmalltalkインタープリタを書いたことがあって、あれを使うときはあんまり速くはないってことが分かったよ(笑)。
F#を使うと、ちょっとアホな命令型のことをやるとパフォーマンスが良くなることに気づいたよ。でも副作用は関数内に収めるようにしてる。そのおかげで、関数はほぼ「純粋」になって、そこそこ速くなるんだ。例えば、私はいつも`Map`データ構造を使うのが好きで、あれは結構いい不変の構造なんだけど、パフォーマンスが重要になると、普通のハッシュマップでつまらない命令型ループに入るのも簡単なんだ。全部を一つの関数にまとめておけば、あんまり汚い感じはしないかな。
どの機能をいつ使うかに気をつければ、F#はすごく速くなるよ。便利だよね、必要なときは機能的なパラダイムを使ったり、ホットループでは低レベルの命令型コードを使ったりできるし。でも、リンクリストやシーケンス、不変データ型をあちこちで使うと、確かにRustにはならないね。
ちょっと気になったんだけど、そのインタープリターはいつ書いたの?dotnetエコシステムは年々大幅に速度改善されてるし、特にフレームワーク時代に最後に試した人にはね。実際、C#コンパイラが活用してないテールコールの改善にも取り組んでるし(dotnet 9か10の頃に、F#が再帰呼び出しがテールコールじゃない場合にコンパイラーエラーを出す属性を追加したんだよね。だから、うっかりそれを間違えることもない)。
機能言語で書かれたエミュレーターを見るといつも感心するよ。ハードウェアを命令型言語にマッピングする方がずっと簡単だからね。みんなが考え出す機能的な抽象を見て楽しんでるよ。
コード見た?F#には可変変数や配列があって、例えばメモリにそれを使ってるよ。
ここでF#を見るのは嬉しい!エミュレーターは言語を学ぶのに最適な方法だよ。最初に見たとき、各仕事に対してほぼイディオマティックなF#を選んでるのが良いと思った。アロケーションを減らすための簡単な改善点もあるよ:Instructions.fsの分離されたユニオンは[]にできて、フィールド名を再利用することで内部フィールドを再利用できる。あと、ちょっと気になるんだけど、いくつかのレジスタについて混乱してる。すでにバイト型なのに、`a &&& 0xFFuy`のセッターは`member val A = 0uy with get, set`よりも何も追加してないと思う。これ、時間とともに変わったのかな。
Registerのソースにはこんなコメントがあるよ: 「// レジスタはレコード型にできない。書き込み時に値を8ビットに切り詰める必要があるから、セッターが必要なんだ。// これはウェブレンダラー用で、Fableがuint8をJSのNumber(8ビット以上)にトランスパイルして、切り詰めを適用しないから。// Fableの非標準的な動作として知られている(https://fable.io/docs/javascript/compatibility.html#numeric-types)」。だから、FableがウェブターゲットでJSのNumberを使って広がることによるデータの保守的なクリーニングだと思う。
記事の中で、彼がそれをFableに移植する部分で実際に話されてるよ(彼はBlazorも試したみたい)。
めっちゃクールだね。ずっとゲームボーイ用のRustコンパイラを書こうと思ってたんだけど、こういうのを見るたびにそのプロジェクトを再開したくなるよ。
ちょっと関係あるけど、CPUサイクルをステップバイステップで表示するエミュレーター(GBじゃなくてNESかSNESだったかな?)ってなかったっけ?確かすごく遅かったけど、1000%の精度を目指してたんだよね、プレイアビリティじゃなくて。
これがあなたが言ってるやつかは分からないけど、ちょっと前に見た記憶がある。https://mtmc.cs.montana.edu/
あなたが思ってるのはno$gmb、初期のゲームボーイエミュレーターのことかな?: https://gbatemp.net/threads/no-gmb-2-5-dos-full-version.6039... NAでポケモンゴールドがリリースされる前にこれを使ってプレビューした思い出があるよ!
F#を読むと、いつもティム・ミンチンの同じ名前の曲を思い出しちゃって、頭の中で流れ始めるんだよね。
F#はいい言語だけど、C#の影にずっと隠れちゃってる気がする。ライブラリのコードの多くがC#や.NETからの受け売りだし、F#用に作られたインターフェースやライブラリはあまりないし、F#で使うための明確なドキュメントもないことが多い。
これ、すごいね!こういうプロジェクトを見るのが大好き。