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命令を明確にモデリング
- 例:LoadInstrやArithmeticInstrなど命令種別を区別
- 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 TetrisやCHIP-8エミュレータ経験でCPUは理解しやすかったが、PPUは習得に時間を要した
- ピクセル単位での処理に苦労しつつも、実装を通じて理解が深まった
このように、Fame Boy開発を通じてコンピュータの仕組みやF#の強み、テスト駆動開発の有用性、PPU実装の難しさなど、多くの学びと発見を得ることができた。