概要
- 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実装 の難しさなど、多くの学びと発見を得ることができた。