検索/エミュレータを、実機と同じバスとタイミングの動きで作る

エミュレータを、実機と同じバスとタイミングの動きで作る

エミュレータを作る・直す時に使う。ハードウェアの部品ごとにコードを分け、バスの読み書き・タイミング・DMAを実機どおりに再現し、実機で合格する参照プログラムでテストし、セーブステートと公開APIを安定させる。

このAIルールを使うには
## 分かりにくい実装の理由を書き、型チェックとテストを回す 元のハードウェアを知らないエンジニアにも読めるように、エミュレータのコードを書きます。特殊な実装を見れば意図が伝わると決めつけず、タイミング規則、副作用、回避策が存在する理由を説明します。動作の理解に役立つ場合は、信頼できるハードウェア資料へのリンクを示します。 リポジトリに記載されたフォーマット、リント、型チェック、テスト、ビルド、監視モード、ベンチマークのコマンドを確認して使用します。必須の確認を変更作業の一部として扱います。 - 反復作業中にフォーマットと対象を絞ったテストを実行します。 - 公開宣言や型付き境界を変更した時は型チェックを実行します。 - コミットまたは引き渡しの前に、正本となる全テストスイートを実行します。 - 大きな変更や性能低下が疑われる場合は、性能ベンチマークを実行します。 - 無関係なリファクタリングを、対象を絞った修正から分離します。 メソッド、オプション、コールバック、エクスポート、対応する入力形式を変更した時は、公開例とAPIリファレンスを更新します。局所的な実装詳細は近くのコメントに記し、プロジェクトレベルのガイドには複数コンポーネントに関わるアーキテクチャと規則だけを記します。 ## エミュレータを、ハードウェアの部品と同じ単位のコンポーネントに分ける エミュレータを、モデル化するハードウェアサブシステムに沿って構成します。小さなオーケストレーターでコンポーネントを連携させ、描画、音声、状態通知、永続メモリ更新のコールバックを提供します。 プロセッサ、映像、音声、カートリッジまたはメモリのマッピング、プログラム読み込み、描画ヘルパー、コントローラー、直列化の責務を分離します。各コンポーネントに明確なインターフェースを与え、内部状態をそのコンポーネント自身に管理させます。 継承は、ハードウェアの派生形が共通の基礎動作を持ち、特定のマッピング規則やバンキング規則だけをオーバーライドする場合に限って使用します。独立したサブシステムにはコンポジションを優先し、コンポーネント境界を越える隠れたアクセスを避けます。 共通の時間基準を定義します。フレーム単位で実行する場合も各フレーム内では正確なサイクル数の管理が必要ですが、表示タイミングとスケジューリングはホストアプリケーションが引き続き担います。 すべての状態保持コンポーネントが使用する、対称な直列化・復元契約を通じてセーブステートの変換を一元化します。 ## ダミーリードやダミーライトも含め、バスの読み書きを実機どおりに再現する バスを単純なアドレスから値への検索ではなく、状態を持つ動作としてモデル化します。実際のハードウェアは直前の値を保持したり、未割り当てまたは書き込み専用アドレスからラッチ済みデータを返したり、値の一部のビットだけを駆動したりする場合があります。 異なるハードウェアバスが独立した状態を保持する場合は、それぞれのラッチを追跡します。観測可能なロード、書き込み、スタック操作、オペコード取得、割り込みベクター取得のたびに、対応するラッチを更新します。デバイスが値の一部だけを駆動する場合は、そのビットと保持されたバス状態をハードウェアどおりに合成します。 ダミーリードとダミーライトを実際のバス操作として実装します。プログラムが返された値を使用しない場合でも、ラッチ済みの値を更新し、I/Oの副作用を発生させることがあります。 各アドレッシングモードのバス操作順を明示的に実装します。 - インデックス付きアドレッシングでは、ページ補正の前に中間アドレスを読む場合があります。 - ゼロページインデックスでは、インデックスを適用する前にインデックス未適用のアドレスを読む場合があります。 - 間接インデックスでは、実効アドレスへ到達する前にポインターまたは中間アドレスを読む場合があります。 - ストア命令とread-modify-write命令では、ページ境界越えがない場合でも必須のダミーアクセスを行うことがあります。 - read-modify-write命令では、変更後の値を書き込む前に元の値を書き戻すことがあります。 フェッチがスタック操作の前後に行われる場合は、命令固有の順序を保ちます。同じ最終レジスタ値になる操作でも、順序を変えるとバス上で観測可能な誤りが生じることがあります。 ## 命令の途中でもデバイスの時間を合わせ、DMAを実際のバスサイクルとして扱う エミュレータが一つのコンポーネントを不可分に実行する一方、実際のハードウェアでは各コンポーネントが同期して進む場合、命令途中のレジスタアクセスで古い状態が見えることがあります。アクセスが観測可能になる境界で、このずれを補正します。 現在の命令が完了したバス操作数を追跡します。同期対象デバイスのレジスタを読み書きする前に、経過した操作に対する正確なクロック比率だけそのデバイスを進めます。後のフレーム単位の進行量から適用済みの処理を差し引き、時間を二重に数えないようにします。 割り込みフラグの生成と割り込みの配送を分けてモデル化します。抑止制御は外部割り込み信号を抑えるだけでなく、状態フラグ自体の生成を阻止する場合があります。テストではこの二つの動作を区別しなければなりません。 DMAを実際のバスサイクルの占有として表現します。転送によって現在のバス値が変化したり、プロセッサの進行が遅れたり、バス状態に出力が依存する命令の結果が変化したりする場合があります。DMA、命令のアクセス、デバイスの副作用の順序を保ちます。 文書化された非公式命令や境界ケースの命令は、信頼できる資料とテストだけに基づいて実装します。一般的なソフトウェアが使用しないという理由だけで、そのサイクル動作を単純化してはいけません。 ## 実機で合格する参照プログラムで、エミュレータをテストする 目的が異なる複数層のテストを使用します。 - 単体テストでは、初期化、命令の意味、マッピング規則、メモリミラーリング、状態遷移、エラー処理を検証します。 - 対象を絞った回帰テストでは、フレームバッファ、音声サンプル、バストレースなどの決定的な結果を固定します。 - 統合テストハーネスでは、信頼できる診断プログラムを実行し、詳細なマシン状態またはエラーコードを比較します。 - ハードウェア精度テストスイートでは、分離した命令テストでは明らかにできない相互作用を対象にします。 実機で合格することが分かっている確立済みの参照プログラムは、正しいものとして扱います。参照プログラムのせいにする前に、エミュレータ、テストハーネス、テストフィクスチャ、環境を調査します。 許容する失敗は、限定的な理由とともに明示的に記録します。既知の失敗一覧は、新しい回帰を隠したり既存の例外範囲を広げたりする許可ではありません。 ハードウェアが中間アクセスを観測できる場合は、最終値と操作順序の両方をテストします。仕様訂正や以前の誤分類により実装を変更するたびに、回帰テストケースを追加します。 ## セーブステートを完全に戻せるようにし、クラッシュ後は実行を止める セーブステートには、プロセッサ、映像、音声、マッパー、ラッチ、割り込み、タイミングの状態を含め、将来の実行に影響するすべてのコンポーネントを含めなければなりません。データを解析できることだけでなく、直列化後に復元すると同じ実行を再現できることを検証します。 コアを一つのホスト環境に結合せず、明確なコールバックまたはストレージ境界を通じて永続メモリを提供します。 通常のコントローラーは、明示的なボタン遷移とシリアルプロトコルを持つ単純な状態機械として保ちます。特殊な周辺機器は、固有の空間・タイミング・光検出動作を保つ専用APIを通じてモデル化します。 復旧不能な実行例外の後は、明示的なクラッシュ状態を設定し、それ以降のフレーム実行を防ぎます。部分的に変更された状態から続行せず、文書化されたリセットまたはプログラムの再読み込みを復旧に必須とします。 受け付けるプログラム入力と公開コールバックの型を安定させます。ブラウザとサーバーの利用者が同じAPI契約を認識できるように、公開型宣言を実装と同時に保守します。 ## 生成ファイルはビルドだけで作り、テストしていない所を報告する 配布バンドル、圧縮ファイル、ソースマップ、宣言ファイルは、承認されたビルドを通じて生成します。生成された成果物を直接編集してはいけません。 プロジェクトが対応を表明するすべてのランタイム対象とモジュール形式を検証します。継続的インテグレーションでは、ローカルで必須となるものと同じフォーマット、リント、ビルド、型、テストのゲートを実行します。 ハードウェア動作を変更する前に、信頼できる仕様と実装資料を確認します。分かりにくいコードの理由を対象操作の近くに記録し、順序を変えた場合に観測できる影響も含めます。 最終差分を確認し、意図しない生成ファイル編集、古い公開文書、未テストのタイミング経路、無関係な整理がないか調べます。実行した対象確認、必要な場合のベンチマーク結果、既知の失敗、未検証のまま残るハードウェア経路を報告します。