机の上のカートリッジには、どれも基板がある
ファミコンのカートリッジは、プログラムが入っただけの箱ではない。小さな回路 であり、たいていは中の一つのチップが、コンソール自身にはできない仕事をして いる。コンソールが一度に見られるのはプログラム 32 キロバイトと絵 8 キロ バイトだけで、それ以上は決して見えない。それより大きなゲームは残りをその チップの向こうに置いておき、必要になったときに一切れずつ受け取る。この チップはその仕事にちなんでマッパーと呼ばれる。チップが載った基板は、模型も その形になれなければならないものだ。なれなければ、ゲームはそもそも動かない。
9 月 12 日まで、模型が知っている基板は一つだけだった。NROM、つまり素の場合
で、チップもなく切り替えるものもなく、ゲーム全体が一度に見えている。うちの
スーパーマリオブラザーズとダックハントのカートリッジはそれではないので、
同じバイト列を両側で走らせて絵を比べようとしていたときに GxROM が並んで
入った (5ecc62d。そのダンプの話はカートリッジの報告
にある)。机の上の残り 18 本には、さらに五つの基板が要った。それらは 9 月 19 日
から 20 日にかけての、七時間ほどの間に入った。
七つの基板と、机の上のカートリッジが求めるもの
ここで OSCR リーダーで取ったダンプから数えたもの:
| 基板 | マッパー | 切り替え方 | ここにある本数 |
|---|---|---|---|
| NROM | 0 | 切り替えない | 2 |
| MMC1 | 1 | シリアルの窓口へ 1 ビットずつ | 9 |
| UxROM | 2 | 書き込み一回でプログラムの面を選ぶ | 3 |
| CNROM | 3 | 書き込みの 2 ビットで絵の面を選ぶ | 2 |
| MMC3 | 4 | 八つの面をどちら向きにも、そして画面を見張るカウンタ | 2 |
| MMC2 | 9 | 絵のチップ自身の読み出しが切り替える | 1 |
| GxROM | 66 | 一つのレジスタが両方を選ぶ | 1 |
カートリッジ 20 本、基板 7 種類。机の上に、基板のないカートリッジはない。
ほかの基板を名乗るヘッダは、名指しで断られる
模型は、半分しか分からないカートリッジを読み込まない。持っていない基板の マッパー番号は、そう言って断る。ヘッダが名乗る基板と中身が食い違っている カートリッジも同じだ:
mapper 5 is out of scope; this console has NROM (0), MMC1 (1),
UxROM (2), CNROM (3), MMC3 (4), MMC2 (9) and GxROM (66)
mapper 2 with 32 KiB of CHR ROM is not a UxROM board;
UxROM carries CHR RAM
二つ目の断りは、見た目より大事だ。ファイルは何とでも名乗れる。逆の作りの カートリッジを黙って受け取る基板は、動かないのではなく間違って動く。 気づきにくいのは後者のほうだ。
二つの基板は、コンソールがなければ試せなかった
ほかの基板のふるまいは、nes-bus 自身のテストがレジスタ一つずつ、外から 押さえている。この二つは、そのやり方ではまったく試せない。
MMC1 は 1 ビットずつ聞く。 窓口への書き込みが運ぶのは 1 ビットで、面の 番号には 5 回かかる。やっかいなのは、連続するプロセッサのサイクルに乗った 二回の書き込みが一回として数えられることだ。そして、その場で 1 を足す命令が ちょうどその組になる。プロセッサは新しい値を書く前に、古い値を書き戻すから だ。二回目が数えられるかどうかを決めるのは、それが乗ったクロックのサイクル であり、そのサイクルを知っているのはコンソールだけだ。そこでカートリッジは 書き込みのたびにサイクルを手渡されるようになり、テストはその足し算をする プログラムを走らせて、面を読み返す。サイクルを渡さない基板で同じプログラム を走らせると 2 回ずれて数が 2 と出る。テストが言うとおりのものを見ている、と 分かるのはそれによる。
MMC2 はプログラムが切り替えるのではない。 絵のメモリの左右それぞれに 面のレジスタが二つと、どちらが答えるかを言うラッチが一つある。そのラッチを 倒すのは絵のチップ自身の読み出しだ。リトル・マックの頭のてっぺんを描く タイルが、そのままタイルを切り替えるスイッチでもある。テストは四つの レジスタを外から立て、画面を一種類のタイルで埋めて絵を出すプログラムを 走らせ、そのあとは何も書かない。押さえるのは、ラッチが動いてレジスタは 一つも動かなかったということだ。テストはまず自分のプログラムを確かめる。 埋めていない画面はタイル 0 を描いて何も動かさないからで、何もなくても通る テストはテストではない。
MMC3 は画面を分ける。そのためには割り込みが線になる必要があった
MMC3 はスーパーマリオブラザーズ 2 と 3 が載っている基板で、あの ステータス表示が止まったまま残りの画面だけが流れる理由でもある。絵の チップのアドレス線を一本見張って走査線を数え、数え終わると割り込みの線を 下げ、ゲーム自身のコードが画面を取り返す。
それが動くまでに、二つのことを直した。
絵のチップが、描かないと分かっているスプライトの枠も読みに行くようにした。
実物では、スプライトの一つもない行でもそのアドレス線は動く。チップが
とにかく見に行くからだ。うちのステッパは無駄と分かっている仕事を飛ばして
いて、証拠ごと飛ばしていた (2c02 @ 10b9089)。
割り込みは、レベルではなく線になった。それまではプロセッサが次に見たときに
読まれていて、それは配線のふるまいではない。いまはマスタークロックの半歩で
17 歩ぶん、基板の後ろに保持される。12 歩でプロセッサの半サイクル、8 歩で
1 ドットになる粒度で、1 クロックの幅を押さえられるのはこれだけだ。この数は
当てはめであり、当てはめだと書いてある。examples/irq-sweep がテスト ROM を
あらゆる遅延で走らせて結果を並べると、ROM が通すのは 14 から 21 までで、その
外はない。17 はその真ん中だ。これで blargg の 4-scanline_timing が通る。
そのアドレス線のフィルタも動いた。実物の基板が持つ規則はプロセッサの クロックの立ち下がり三回だ。9 ドットはその丸め方を間違えていて、9 だと背景が $1000 にあるとき 0 行目の最初のパターン読み出しが数えられてしまい、実物が 241 クロックにする一コマが、一コマおきに 242 クロックになった。いまは 10 ドットを数える (nes-bus v0.1.6)。
20 本目のダンプは、マルチカートの最初のバンクを単独で読んだもの
絵を出さない 1 本は、そもそもゲームのダンプではない。スーパーマリオ
ブラザーズとして登録されたダンプ 54 は、スーパーマリオブラザーズとダック
ハントのマルチカート (ダンプ 55) の、プログラムの最初の 32 KiB バンクと
キャラクタの最初の 8 KiB バンクそのもので、一バイトも違わない。リーダーは
512 バイトの窓から基板を素のスーパーマリオブラザーズと同定し、そのゲームの
サイズで読んだので、二つあるバンクの片方だけを読んだ。そのバンクはゲーム
そのものだが、基板が一か所だけ変えている。ゲームのリセットが始まっていた
最初の 9 バイトが、マルチカート自身のバンク切り替え (SEI、メニューの
バンクを選ぶ基板レジスタへの書き込み、自分自身へのジャンプ) になっていて、
ゲームの本当の入口はその 9 バイト先、メニューがジャンプしてくる所にある。
素の NROM イメージでは切り替える相手がないので、プログラムはこの四命令を
永遠に回り、画面は黒いままだ: 300 コマで命令のアドレスは四つ、描画レジスタ
への書き込みはなし (examples/where-it-sits、2026-09-29)。リーダーの
チェックサムが何にも合わなかったのは、このバンクを単独で持つ市販カート
リッジがないからだ。読み直しは要らず、20 本は全部説明がつく: 自分の絵を
出すダンプが 19 本と、別のダンプの中にあるバンクが 1 本。
「自分の絵を出す」が主張のすべてだ。ここで誰かがコントローラを握って タイトル画面の先まで遊んだものは一つもない。コンソールが自分で言えるのは、 ゲーム自身のプログラムがゲーム自身の絵を画面に出した、ということだけで、 言っているのもそれだけだ。
五本のゲームは、基板ではなく分岐で止まっていた
最初の見立てが半日外れていたので、記録しておく価値がある。ペーパーボーイ、 グーニーズ II、ブラスターマスターは絵を出さず、ゼルダの伝説とバトルチェスは 一色を塗っただけだった。blargg の垂直帰線のテスト三本が、N5 報告では通ったと 記録されている場所で落ち始め、うち一本は垂直帰線の周期が大きくずれていると 言っていた。
ずれてはいなかった。examples/vbl-probe はフラグの立ち上がりを 241 行目の
2 ドット、立ち下がりを 261 行目の 2 ドット、周期をプロセッサの 29780 または
29781 サイクルと出す。絵の有無によらず、これは正確だ。間違っていたのは
プロセッサの分岐命令一つだった。テストは状態レジスタを読んでその最上位
ビットで分岐する。コアはその一時間前、別の分岐の不具合を直す探索の中で、
取るべきその分岐を取らなくなっていた (6502 @ 3805107)。五本とも今は絵を
出し、垂直帰線のテストは N5 報告の記録どおりに戻っている。
動かない基板と分岐しないプロセッサは、真っ暗な画面のこちら側からは見分けが
つかない。一方から他方まで歩いたのは examples/where-it-sits で、止まった
カートリッジを止まった命令まで戻してくれる。
まだ決着していないこと
- アドレス線のフィルタはドットを数え、実物の基板はクロックの縁を数える。 10 ドットは blargg の ROM が見るかぎりどこでも実物と一致し、ここにある ROM ではその差は見えない。だからこれは証拠ではなく理屈のほうが開いている。 決着をつける場合とともに nes-bench の未決事項にある。
- 割り込みの遅延は当てはめだ。 半歩 17 はプロセッサの半サイクルより長く、 15 番ピンからの配線にしては長すぎる。だから実際に表しているものの大半は、 カートリッジ側ではなく、プロセッサがサイクルのどこで割り込みを見るか だろう。15 番ピンとプロセッサのクロックにスコープを当てれば、帯の真ん中が 数になる。
- ダンプ 54 は読み直すカートリッジではない。 マルチカートの最初の バンクを単独で読んだもの (上記) で、20 本は全部説明がつく。
- どのゲームも、普通の意味では遊ばれていない。
この数字がどこから来たか
- 基板と断り:
crates/nes-console/src/ines.rs。 - コンソール側のテスト:
crates/nes-console/tests/mappers.rsに 6 本、crates/nes-console/tests/mmc3.rsに 2 本。 - 各基板自身の論理をレジスタ一つずつ: nes-bus の契約スイート。MMC1、UxROM、 CNROM は v0.1.4、MMC2 は v0.1.5、フィルタは v0.1.6。
- 割り込みの遅延:
crates/nes-console/src/console.rsのCART_IRQ_DELAY。examples/irq-sweepが掃いている。 - フィルタ: nes-bus の
A12_FILTER_DOTS。examples/mmc3-probeが確かめて いて、これは自分のカートリッジをどちらのやり方でも走らせるので、コマ数が そのまま読める。 - カートリッジの本数: この机の上のダンプ。OSCR で読んだもの。
- 日付:
72200f5(MMC3)、6d79167(MMC1、UxROM、CNROM)、fda1884(MMC2)。