6502tinymachines

高速 2C02、チップとドット単位で一致

P3 の報告。作業の後に書かれた。

docs/p3-plan.md が設計と順序を決めた: まずドットごとの表駆動のステッパの フレーム時間を測り、ビットスライスのデータパスは、記録された不足があって初めて 作る。以下は、段ごとに実測したものだ: 背景 (下のゲート)、それからスプライト、 それからレジスタファイルとスクロール、そして途中で見つかったエンジンの ダイバージェンス。ステッパは crates/v2c02-fast。ゲートは tests/p3.rs、 tests/p3_sprites.rs、tests/p3_scroll.rs で、REQUIRE_GOLDEN_P3=1 が それを必須にし、MUTATE=1 はそれぞれで赤にならなければならない。

ゲート

  • ドットゴールデン。 ステッパのフレームは、ラング 0 のフレーム (goldens/p1-dots.bin、標準ワールドの P1 の捕捉) と可視のドットすべて、 1 フレーム 62,160 個で一致する。240 行それぞれの 256 画素と、3 個の導入の ドットで、免除は無い。ゴールデンのずれを押さえた当てはめ (examples/p3-fit.rs) は鋭い: ずれ 3 で食い違いが 0、ずれ 2 と 4 では それぞれおよそ 39,000 になる。
  • フレーム時間。 周期は導出したもので、12 x f_sc (ntsc-grid の 315/88 MHz) で 714,736 マスタ半ステップ、16.639 ms だ。リリースビルドで 500 フレームにわたって (examples/p3-bench.rs): 平均 1.004 ms、毎秒 996 フレーム、周期の内側に 16.6 倍。最悪 1.781 ms、内側に 9.3 倍。 テスト自身の 200 フレームの走行では最悪 3.110 ms (内側に 5.4 倍) を見た。このばらつきは 機械のもので、ステッパのものではない。不足は無いので、計画のステップ 2 (ビットスライスのデータパス) は作っていない。
  • MUTATE=1 は表から INC_X を落とす: 62,160 ドットのうち 34,560 が 食い違い、テストは赤になる。

ステッパはどう作られているか

シーケンサは表である。フレームのドットごとに事象の語が一つあり、標準ワールドの スイッチレベルのチップの 1 フレームから build.rs が記録して OUT_DIR に書く (NC-SA 由来なので、コミットは決してしない)。各ビットは、そのドットのどれかの 半ステップで高くなる名前付きのノードか、あるいはチップが ALE でラッチした アドレスで分類した CHR バスのフェッチだ。src/events.rs が、記録器とステッパの 両方が取り込む唯一の定義になっている。シフタの有効な窓も表から導く: 各 INC_X で終わる 8 ドットだ。ステッパが描くのに使うパレット RAM は、ビルド時の二つ目の 測定だ。アクセスの間にアクセス一回分のアイドルを置きながら $2007 を通して チップから読み返し、それを二度やって、二回が一致しなければならない。データパス (v、t、fine x、ラッチとシフタ、インクリメントとコピー、パレットの引き当て) は、 P1 と P2 が証明したモデルから書き下ろし、そう札を付けてある。ビルドはチップを 1 分ほど走らせる。ワークスペースは build-override の opt-level 3 を設定して いるので、ビルドスクリプトの依存が最適化なしでコンパイルされることはない。

実測したもの、実測した順に

シーケンサ、データパスを一行も書く前に (examples/p3-fetch-probe.rs。 制御ごとに 8 半ステップの位相マスクを付けて、ドットごとに): ネームテーブル、 属性、パターン下位、パターン上位のフェッチが各タイルのドット 8k+1、8k+3、 8k+5、8k+7 でラッチされ、読み出しは次のドットに乗る。プリフェッチのタイル 二枚は 321..335、ダミーのネームテーブルのフェッチ二つは 337 と 339。INC_X は 8、16、...、256、328、336 (1 ラインに 34 回、1 フレームに 8,194 回)。INC_Y は 256。水平のコピーは、描画するどのラインでも 257。垂直のコピーはプリレンダの ドット 280..304 にわたる。set_vbl_flag は (241, 1)。フラグはプリレンダの ラインのドット 1 から先でクリアされる。スプライトの評価は、スプライトが八つ 見つかるラインではドット 131 で終わり、それ以外では 193 で終わる。標準ワールドの 未定義の OAM はどのスロットでも Y = 0 なので、ライン 0..7 は最初の分岐を取る。 粗 X はタイル 33 で次のネームテーブルへ回り込む。

チップが保持しているパレットは、ワールドが書いたパレットではない。 最初の ステッパの絵は、どんなパターンアドレスでも作れない仕方で間違っていて、チップ 自身の画素インデックス (pixel_color0..3) を pal_d の横に並べると、パターンの 流れは正しく、色が正しくないことが分かった。$2007 を通して間隔を空けて読み返すと、 RAM は $3F00..$3F0F に 16 2a 12 0f 28 14 02 0f 26 1a 31 0f 30 27 06 20 を 保持していて、ワールドが書いた十六の値 (0f 16 2a 12 ...) とは違う: どの項目も 一つ早く、背景色は 0x0f ではなく 0x16、そして $3F0F は一度も書かれず、電源 投入時の 0x20 を保っていた。そこで書き込み経路を単独で実測し (examples/p3-write-probe.rs: $2006 の対、既知の 4 バイトを選んだアイドルを 挟んで書き、その行を間隔を空けて読み返す)、それから同じ書き込みをリファレンスに 通した (tools/golden-trace/gen-write-probe.js、35 分。暖機がその費用だ):

書き込みの間のアイドル行ラング 0 が保持リファレンスが保持
0 (立て続け)$3F1111 01 2a 3e11 01 2a 2a
24 (アクセス一回分)$3F1131 13 13 3e21 11 01 2a
96$3F1131 13 13 3e21 11 01 2a
24$3F2121 33 23 2e21 11 01 2a

(書いたのは 21 11 01 2a。アイドル 48 と 192 は 24 と同じに読める。各アクセスの 後に CPU のデータバスを保持しても浮かせても、ラング 0 のどの行も変わらない。 $2006 の対の後のアイドルは何も変えない。リファレンスの標準ワールドのパレットは、 バイト単位でラング 0 のものと同じだ。)

立て続けだと、どちらのエンジンも最初の値を落とし、残りを一項目ぶん早く着地 させる。それがモデルの振る舞いで、標準ワールドが踏んだのはそれだ。書き込みの間に アイドルを置くと、リファレンスはどのバイトも書いたとおりに着地させ、ラング 0 は data OR (v & 0xff) を着地させる。つまりバイトを VRAM アドレスの下位 バイトと OR したものだ (21|11 = 31、11|12 = 13、01|13 = 13、 2a|14 = 3e。$20 より上の行では 11|22 = 33 なので、この項はパレットの番号 ではなくアドレスだ)。これはエンジンのダイバージェンスだった。spr_d の レール衝突のホールドに続く、一族で二つ目のものだ。そしてどのノードゴールデンから も見えなかった。どれもパレットへの書き込みに間隔を置かないからだ: P1 の プログラムはパレットを立て続けに書き、P2 のスケジュールは OAM にしか間隔を 置かない。原因はハーネスではない (上の、バスを保持する行)。

原因、そして halfphi 0.1.6。 リファレンスの getNodeValue は、レールもプル も持たないグループを面積の投票 (area vote) で解決する。構成員の面積をレベルごとに 合計し、大きい側が勝つ。そういうグループのすべてについてそうする。halfphi が 持ち込んでいた visual6502 の規則は、充電された構成員が一つでもあればグループを 高にするもので、0.1.5 は面積の投票を、レール衝突のホールドの代替としてしか 使っていなかった。二つの規則は、転送の途中でバスが混ざった電荷を抱えて浮かない 限り一致する。そして、間隔を置いた書き込みがまさに残すのがそれで、立て続けの 書き込みは決してそれを残さない。halfphi 0.1.6 は、ネットリストごとに宣言する ChargeRule を足した: AnyHigh が既定のままで、6502、6800、Z80、2A03 の リファレンスがしているのはこれだ。2C02 のネットリストは AreaVote を宣言する。 それを宣言すると、書き込みのプローブのどの行もリファレンスの行になり、立て続けの ときの末尾の項目も含まれる。そして P0、P1、P2 のノードゴールデンは、変更なしで 緑に再生する。二つの規則が違う判断をするグループの数 Stats::area_vote_lows は、ゲートの走行にわたって 121 だ。ゲートは tests/palette_write.rs (間隔を 置いた書き込みは、どちらの行でも書いたとおりに着地する。MUTATE=1 はチップを AnyHigh の下でビルドし、31 13 13 3e で赤になる)。ノードのレベルでの証明は、 tools/golden-trace/gen-pal-golden.js に対する examples/pal-diverge-probe.rs で、間隔を置いたパレットへの書き込み一回を 通したリファレンスの全ノードだ。それが名指ししたものは次に述べる。ここで 間違っていたのは、ステッパでは決してなかった。

ダイバージェンスを名前で。 リファレンスは、$3F11 への間隔を置いた $2007 <- $21 のアクセス一回の始まりから 120 半ステップ (24 の縁と 96 の アイドル) の各半ステップで、10,906 ノードすべてを吐き出し、ラング 0 は同一の列を それに対して再生した。宣言した AreaVote の下では: 窓の中にダイバージェンスは 無い。どの半ステップのどのノードもだ。 AnyHigh の下では: 最初の違いは窓の 状態 42、アクセスの解放の後のアイドルに 19 半ステップ入ったところで、ノードは 六つ、その中に _io_db4 と ab4_out があり、ちょうどビット 4、OR が足すビットだ ($21 | $11 = $31)。状態 46 までにそれは _db4、ab4、/_ab4、つまり多重化 された経路上のビット 4 のデータ線とアドレス線へ広がる。状態 50 までに、セルへ 着地するパレットのデータビット pal_d4_out へ。最後の読み返しは $31 だ。 そこで仕組みを名前から読むと: CPU がデータバスを解放した後も、データのビット 4 の周りの浮いたグループは、AD の多重化器から来たアドレスの位相の電荷をまだ運んで いる。そして充電された構成員が一つで、充電されていない面積の方が大きいグループは、 古い規則の下では高に解決し、リファレンスの投票の下では低に解決する。それから 両方のエンジンがそのビットをパレットの書き込みへ運ぶ。運ぶべきなのは片方だけだ。

この規則が他に何だったか。 P1 のワールドを 0.1.6 の下で録り直すと、P1 の ゴールデンは 4,008 状態すべてにわたって 10,906 ノードすべてでビット厳密に、 免除なしで再生し、P0 のゴールデンも 601 状態すべてで免除なしに再生する: P0 が マスクしていた 9 ノードの一族と、P1 がマスクしていた 27 ノードの一族は、どちらも リセットを持たないスプライト経路のラッチで、電源投入時の状態はシリコンが未定義に していると読まれていたが、実は二つの電荷の規則が違う判断をしていた浮いた グループだった。コインは無かった。どちらの報告も、この日付でその訂正を載せている。

画素のパイプライン (examples/p3-pal-probe.rs、半ステップごと): pal_d は pclk0 の間にゼロへプリチャージされ、pclk1 の間に色を運ぶ (P1 の発見を再び見た)。 パレット RAM が見るアドレス pal_ptr0..4 は、1 ドット後の pixel_color で あり、パターンがゼロなら属性ビットが何を言っていても番号 0 へ畳まれる。pal_d は pal_ptr における RAM の中身だ。hpos h における pixel_color はその ラインの画素 h - 2 なので、画素 x は hpos x + 3 で pal_d に乗り、 pixel_color はプローブしたどの行でも hpos 0 と 1 でゼロだ。P1 の捕捉の約束 (hpos h をドット h + 1 とする) では、ゴールデンは画素 x をドット x + 4 に持つ。 ステッパは画素 x を規約のドット x + 1 で出し、ゲートは +3 で比べる。

当てはめ。 ずれ 3、61,440 のうち食い違い 0。これは 240 行それぞれの 256 画素 で、ゲートの 3 個の導入のドットは入っていない。隣の値を見れば桁違いの最小だと 分かる。

ステップ 2: スプライト

データパスは評価、スプライトのフェッチ、優先の多重化器を得て、二つ目のドット ゴールデンに押さえられている: スプライトワールド (v2c02_dots::sprite_world。 標準ワールドに、スプライトのパレット四つと 64 個のスプライトを足し、スプライトは 有効、左端の切り取りは無し) を、examples/p3-sprites-golden.rs がラング 0 から 捕捉し、ドットの横にチップから読み返したパレット RAM と OAM を並べ、spr0_hit と spr_overflow の最初の立ち上がりを記録したものだ。スプライトは、四つの パレット、両方の反転とその組み合わせ、背景の後ろのスプライト、1 ラインに 9 個の スプライト (八つ目が最後に描かれる)、右端と下端、重なる二つのスプライト、そして P2 のスプライト 0 を運動させる。

  • スプライトを有効にして、可視のドットはすべてラング 0 と一致する。1 フレーム 61,440 個。データパスをゴールデンに対して走らせた最初の走行でそうなった。
  • スプライト 0 のヒットは、チップのヒットと同じ場所に着く。 ラング 0 では spr0_hit が (92, 185) で立った。ステッパの最初の不透明どうしの画素は (92, 183) で、画素の位置とそれが多重化器に着くまでの間の、実測した 2 ドットの ずれだ。P2 の (91, 182) は、べた塗りの背景を持つ別のワールドのもので、そのまま 移らない。
  • オーバーフローのフラグ。 spr_overflow は (120, 143)、9 個のスプライトの ラインで立った。ステッパもそこでオーバーフローする。このドットは、評価の走査を ドット単位で模す段のために記録してある。ここではフラグを事実として押さえるだけだ。
  • MUTATE=1 は表からスプライトのパターンのフェッチを落とし、ゲートは赤になる。

評価は、スプライト窓での一歩として書き下ろしてある (チップは実測でドット 65..256 にわたって広げている)。フェッチは表の SPR_PT の位置をスロットごとに 取り、八つのユニットはスロットの順に合成され、不透明な背景に対しては「後ろ」の ビットを尊重する。8x16 のスプライトは模しておらず、切ってあることを表明する。 左端の切り取りも模しておらず、ワールドもそれを運動させない。

ステップ 3: レジスタファイルとスクロール

ステッパはレジスタファイルを得た: $2000、$2001、$2003、$2004、$2005、$2006、 $2007 をチップが取るとおりに (t、v、fine x と共有の書き込みトグル、OAM の アドレスとデータ、アドレスのインクリメント、そしてハーネスが捕捉するやり方で 捕捉したパレットへの書き込みと VRAM への書き込み)、加えて $2002、$2004、$2007 の読み出しも。ワールドはいま、ステッパが自分自身に走らせるレジスタプログラムで あり、フレームの中の書き込みは自分のドットに着地する。

  • 標準ワールドをレジスタファイルに通すと t = 0、v = $2000、fine x = 0 が 残り、それを表明する。そしてステップ 1 のゲートがその上で成り立つ。
  • スプライトワールドをレジスタファイルに通すと、その $2003/$2004 の書き込み から、チップから読み返した OAM とバイト単位で同じ OAM が導かれる (バイト 2 の 未実装のビットはマスクされる。P2 の発見だ)。そしてステップ 2 のゲートがその上で 成り立つ。
  • スクロールワールド (v2c02_dots::scroll_world: 背景の表は $1000、 ネームテーブルは $2400、x = $25、y = $13 なので、粗 4 と fine 5 かける粗 2 と fine 3)。フレームの中に五つの書き込みがあり、ライン 100 で $2005 の水平の 分割、ライン 160 で $2006/$2005/$2005/$2006 の完全なスクロールの変更を 4 ドット 間隔で行う。捕捉の中でアクセスを実行しながらラング 0 から捕捉した (examples/p3-scroll-golden.rs): 可視のドットはすべて一致、1 フレーム 61,440 個。
  • フレーム途中の書き込みがいつ着地するか。 ステッパが各書き込みを適用する ドットを、その 24 縁のアクセスが始まったドットからの相対で掃くと: 1、2、3 ドットで食い違いゼロ、0 と 4 から 6 では 1 ドットが違い、7 と 8 では 100 を 超える。ちょうどアクセス 1 回分の幅の平地だ: 効果はアクセスの内側に着地し、 絵はそれがどのドットかを言えない。中心を押さえ、平地は実測どおりに表明して、 走行ごとに導き直す。
  • MUTATE=1 は表から水平のコピーを落とし、35,387 ドットが狂う。

パレットは当初、レジスタファイルの出力の中で唯一チップに押さえられていなかった: ファイルはワールドが書いたパレットを導き、チップは自分の書き込み経路が着地させた ものを保持していた。書き込み経路が直り (上記)、P1 のワールドを間隔を置いて 録り直したので、どのワールドのパレットも、レジスタファイルを通してチップの 読み返しに、項目ごとに、三つのゲートすべてで押さえられている。

描画を切った絵 (2026-09-06、コンソールの N6 のために)

描画を切ったときチップが何を見せるかは、一度も実測されていなかった: ステッパは 背景色を描き、そう述べていた。コンソールの実機の捕捉の比較が求めるカラーバーの カートリッジ full_palette.nes は、まさにその状態で描く: マスク $00、$2006 の 対でパレット RAM に停めた v、そして行の途中で v を進める $2007 の書き込み。 コンソールに通すと青い縞になった。

描画を切った標準ワールドで、ラング 0 の上で実測した (blank-probe。描画を 切ったワールドの書き込みは BLANK_WRITES、1 フレームの 60..67 行):

  • v が $3F00..$3FFF にあるとき、絵は v が指すパレットの項目で、レジスタファイル が書き込むときと同じミラー規則を通る ($3F10 は項目 0 を見せる)。ワールドが 一度も書いていない項目は、チップがそこに保持しているもの (00) を見せる。v が それ以外のどこかにあるとき、絵は背景色だ。
  • $2006 の対は、二度目の書き込みのアクセスが始まってから 5 ドット後に自分の色を 見せる。$2007 の書き込み (その項目がすでに持っていた値を書くので、動くのは v だけ) は、そのアクセスが始まってから 8 ドット後に、進んだ先の項目を見せる。 $2006 の対の最初の書き込みだけでは何も変わらない。二度目の書き込みが次の行に 着地する対は、その行で見える。
  • $2001 のエンファシスビットは、その書き込みのアクセスが始まってから 3 ドット後に vid_emph へ届き、クリアする書き込みのアクセスが始まってから 3 ドット後に 去る: 色が見える場所より 2 ドット先だ。ハーネスはアクセスの始まりでバイトを バスに載せるが、6502 の書き込みはそうしないので、この先行は上限だ。

そこから書き下ろした (v2c02-fast の blank_colour。goldens/blank.bin に 対して tests/blank.rs でゲートを張り、捕捉した行の可視のドットすべてで 比べる): 描画を切った経路は、v が指す項目からドットごとに描く。$2006 の対は、 ステッパが書き込みを適用するドットで見え (当てはめた 2 ドットの遅延に、 ゴールデンの 3 ドットの画素のずれを足したものが 5 になる)、$2007 の一段は BLANK_2007_HOLD = 3 ドットさらに待って押さえる (2 と 4 は赤)。フレームはいま、 $2001 のエンファシスビットをどのドットの横にも運ぶ。書き込みが着地するドットで 取ったものだ。実測した 2 ドットの先行は上に記録してあり、模型には入れていない。 MUTATE=1 (v がどこを指していても背景色) は 1,625 ドットで赤になる。 コンソールの full_palette.nes は今、そのバーを見せる。

8x16 のスプライト (2026-09-06、コンソールの持ち越し項目のために)

ステッパはこれまで $2000 のビット 5 を名指しで拒んでいた: スプライトのワールドが それを一度も立てなかったので、ラング 0 に訊いていなかったのだ。縦長スプライトの ワールドは、そのビットを立てたスプライトワールド (tall_sprite_world) で、その VRAM 関数はタイルのどの行にも自分のバイトを与えるので、各スプライトのラインが どちらの半分とどちらのバンクからフェッチされたかが絵に出る。ラング 0 から捕捉した (p3-sprites16-golden): スプライト 0 のヒットは (93, 188) で立ち、オーバー フローは (120, 143) で立った。どちらも 8 ドット高のワールドより後で、より背の 高いスプライトならそうなる。ステッパが運ぶ規則は、公開されたモデルから 書き下ろしたものだ: 範囲に入るのは 16 ライン、二つの半分は OAM のタイルの下位 ビットをクリアしたものと次のタイルから取り、バンクはその下位ビットから取り、 垂直の反転は両方の半分にまたがって働く。tests/p3_sprites16.rs で、可視の ドットすべてとスプライト 0 のヒットについてドット単位で押さえている。 MUTATE=1 はワールドを 8 ドット高で描き、685 ドットで赤になる。blargg の sprite_hit 08 (倍の高さ) は、これを入れてコンソールを通る。

ALE をドットクロックと並べて (2026-09-07、ベンチのために)

ベンチの整列の分類器 (tinymachines/nes-bench の B2) はスコープから ALE を読み、 コンソールの Alignment は pclk0 の上で定義されているので、ALE がドットの中の どこに座るかは数字でなければならなかった。v2c02-sim/examples/ale-phase.rs、 描画は有効: 40,000 マスタ半ステップにわたって ALE の立ち上がりが 2,508 回、 どれも pclk0 が立った半ステップの上にあり、16 半ステップ間隔で、スプライトの フェッチのところは 8 半ステップ間隔だ。スコープの上の ALE の立ち上がりは、 ドットの始まりである。

P1 へ持ち越し、ここに記録して変えていないこと

  • P1 の報告は「16 項目のパレット」を、チップが書かれたものを保持しているかの ように述べていた。実際に保持していたのは上のずれたパレットで、両方のエンジンに 共通のモデルの、立て続けの振る舞いだ。2026-09-04 に録り直した (docs/p1-report.md の、その日付の節): 標準ワールドは、Rust のワールドでも 生成スクリプトでも、パレットの各項目をアクセス一回分のアイドルを挟んで書き、 チップは書いたとおりのパレット、背景色 0x0f を保持する。ノードゴールデン、 ドットゴールデン、最初の光、DAC のゲートはそこから作り直した。P3 のゴールデン も同じだ。$2006 の対の後の最初の $2007 アクセスは、今でも古いアドレスの 上にバスサイクルを一つ出す ($0000 <- 00、遅れた下位の書き込み)。純関数の ワールドには感じ取れないものだが、コンソールのためにここに記録しておく。
  • P1 の捕捉は画素 x をドット x + 4 に置くが、規約の有効な窓はドット 1 から 始まる。絵が同期に対してどこに座るかは、コンソールと実機の捕捉にとっての幾何の 問い (N5、M4) だ。記録するだけで、動かさない。
  • capture() は自分のフレームに FrameParity::Even と札を付ける。(261, 340) で整列するのは偶数フレームの終わりなので、捕捉されるのは奇数のフレームだ。 可視のドットは一つも変わらない。

垂直同期のドット (2026-09-18、ベンチのために)

vsync-probe (v2c02-dots): 描画を有効にした標準ワールドの 1 フレームを通して、 DAC の同期先端の脚を半ステップごとに読み、それが立っている区間をすべて (行, ドット) の始まりと終わりとして出す。水平同期はどの行でもドット 280 から 305 まで、25.1 ドットにわたって立つ (符号化器が従う表は 277..301 と言う: DAC は どの同期もカウンタの番号より 3 ドット後に見せ、そのずれは垂直同期でも同じなので、 水平に対して何も動かない)。垂直同期は三つの広パルスで、(244, 280)、(245, 280)、 (246, 280) から次の行のドット 257 まで、それぞれ 319.2 ドット。23 ドットの間隙 (セレーション) にわたっては消去期間の脚が立ち、行 244 から 246 にバーストは無い。 行 247 は 280 で普通の同期、309..324 でそのバーストを出して閉じる。コンソールの 記録も同じ形を示す (nes-bench の走行 20260918-135721: 広パルスは直前の水平同期 からちょうど 1 ライン後に始まり、長さは 0.934 ライン、三つが 1 ライン間隔)。 つまりダイと実物は一致していて、違っていたのは符号化器だった: ntsc-crt の ntsc-source-nes は立ち上がりを行 245 のドット 0、つまり 64 ドット遅くに持ち、 セレーションを 32 ドットにしていた。v0.2.10 からはこの測定に押さえられていて、 ベンチの poll-line.py は実物のポーリングの位置をそこから決める。ベンチの ノートで開いていた、実物のポーリングと模型のものの間の 0.6 ラインのずれは、 これと模型側のラインの算術が合わさったものだった。ゲームのポーリングは今、 0.03 ライン以内で一致する (nes-bench の mario-dissection.md)。

スプライト窓のアドレスバス、2026-09-20

v2c02-fast は、描くつもりのスプライトだけをフェッチしていた。スプライトが 入っていないスロットと、各スロットを開く二つのネームテーブルのフェッチは、絵には 何の費用もかからないので、ステッパはそれを飛ばし、そのゴールデンは一度も 気づかなかった: 実物でもそのバイトは捨てられるからだ。

コンソールの上では、それは無料ではない。ラインを数えるカートリッジは PPU の A12 を見張っていて、範囲にスプライトが無いラインでは、飛ばしたフェッチのせいで A12 がまったく動かず、MMC3 の基板はそのラインで何も数えなかった。blargg の 2-details は、1 フレームがカウンタを 241 回クロックすると言う。フェッチが 無いと、ステータスバーが空の画面はそれよりずっと少なくクロックしていた。

ダイが何をするか。v2c02-dots の p3-fetch-probe で、OAM がすべて 1 なので スプライトが範囲に入ることが決して無い標準ワールドの上で実測した (ライン 20、 ドット 257 から 320):

ドットアドレス何か
257$2442そのスロットの最初のネームテーブルのフェッチ
259$2040その二つ目
261$0ff3パターンの下位バイト: スプライトの表のタイル $ff
263$0ffbその上位バイト
265, 267$2040次のスロットの二つ
269, 271$0ff2、$0ffaそしてそのパターン。行の 3 ビットが一つ小さい

つまりチップは、何も入っていないスロットのために、スプライトのパターンの表の タイル $ff をフェッチする。1 ラインに 8 スロットぶんだ。そして窓の間の A12 は、表を選ぶビットになる。ステッパは今、両方のフェッチを行って捨て、ゴミの ドットごとにネームテーブルのフェッチを一つ出す。ダミーのアドレスの、行に当たる 下位ビットはダイのものではなく、それを読むものも無い。これは隠さずにそう述べて おく。P3 のゴールデンは変わらない。絵が変わらないからだ。

P3 の中の、次

スプライトと、レジスタファイルとスクロールは、ステップ 1 の次に計画が置いていた 二つで、上のステップ 2 と 3 がそれだ。どちらも実測し、ドットゴールデンに押さえて ある。その先に計画が持っているのは、CPU を付けた blargg のテスト群、それから 実機の捕捉 (引き継ぎの第 5 節) だ。

ビルド時に 2c02/docs/p3-report.md から取り込まれる。写しはリポジトリの一つだけだ。報告は独自の作業用の言葉をいくつか使う: 報告書で使われる言葉。