← [ TECH / 技術部 ] に戻る
OBSERVATION · 其の12417 · 2026.10.23

ファミリーベーシックでマシン語に挑戦【前編】テトリス風|BASICで16秒かかった1行を1フレームで

◉ REC COVER · 圖版 FRM·3055

こんにちは、パレイド技術部の夏目です。動画「ファミリーベーシックでマシン語に挑戦【前編】」の受け記事です。ファミリーベーシック V3 の BASIC で AI に作らせたテトリス風の落ち物パズルは、動きはしたものの、エミュレータで測ると1行落ちるのに約16秒かかりました。マシン語(BASIC から CALL 命令で呼び出す 6502 の機械語)で書き直すと、入力を1フレームごとに読めるようになりました。

この記事には、動画で語り切れなかった実測の表、踏んだ罠、公開したコードの打ち込み方をまとめます。マシン語の命令を教える記事ではなく、画面と音を直接動かす手段としてマシン語を使った記録です。数字はすべてエミュレータ上の実測で、実機・実テープでは確認していません。

https://youtu.be/USsr0Ff_6Tc

本記事はローカル LLM による自動執筆パイプラインで生成されました。現段階ではクラウド AI(Claude 等)の補助や人間の編集が介在していますが、pareido.jp では最終的に AI が自律的にコンテンツを制作できる仕組みの構築を目指しています。

BASIC 版は1行落ちるのに約16秒

測定の条件は次のとおりです。フレームは画面1枚ぶんの時間の単位で、エミュレータが垂直ブランク(後述)を迎えた回数で数えました。

項目 内容
対象 ファミリーベーシック V3(エミュレータ上)
時間の単位 フレーム(1秒 = 約60.1フレーム)
条件 無操作、最初の約3,600フレーム(約60秒)、6周ごとに1行落ちる設定
BASIC 版 AI に作らせた LOCATE/PRINT 版と、画面への書き方を PPU 直接書き込みに変えた版
マシン語版 盤面・操作・得点を BASIC 版と揃えた 2,029 バイト
項目 LOCATE/PRINT 版 PPU 直接書き込み版 マシン語版
入力を読む間隔 71.5フレーム(1.19秒) 101.0フレーム(1.68秒) 1フレーム
盤面の全面再描画(180マス) 559フレーム(9.3秒) 341フレーム(5.7秒) 5フレーム
1行落ちるまで 988フレーム(16.4秒) 947フレーム(15.8秒) 30フレーム(設定値)
測定時間のうち全面再描画の割合 53% 33% –
判定 × 遊べない × 遊べない ○

PPU(画面を描く専用チップ)に直接書くと、全面再描画は 559 から 341 フレームに縮みました。ところが1周は 71.5 から 101.0 フレームに延び、1行落ちる時間は 16.4 秒と 15.8 秒でほぼ同じです。スコア表示を1文字ずつ POKE で書く処理などが重く、BASIC の中で書き方を変えても桁は変わりませんでした。

遅さの元は、ミノが1段落ちるたびに盤面の180マスを1マスずつ書き直していることです。LOCATE/PRINT 版では測定時間の53%がこの全面再描画に消えていました。ミノの移動に要るのは、消す4マスと描く4マスだけです。

  • 物差し: 製品版 NES の落ち物パズル(テトリス)は、最も遅いレベル0で48フレーム/行です(Tetris Wiki)。BASIC 版の 988 フレームはその約20倍(988÷48≒20.6、計算値)
  • マシン語版との倍率は、入力が約72倍、全面再描画が約112倍、1行落ちるまでが約33倍。33倍はマシン語側の落下設定(30フレーム/行)で決まる値で、速さの上限ではない
  • 測定の限界: 周回を数える印の POKE/PEEK を足しているため、原版よりわずかに遅い(影響は未測定)。キー入力の取りこぼし率も未測定

マシン語版で変えたのは「いつ、どこに書くか」

ファミコンの画面と音は、それぞれ専用の回路が受け持っています。登場する用語を先に整理します。

用語 意味
VRAM PPU が読む画面用のメモリ。どこにどの絵柄を置くかが入る
垂直ブランク 画面を上から下まで描き終えて、上へ戻るまでの短い時間
NMI 垂直ブランクの始まりに、PPU が CPU を呼び出す割り込み(Non-Maskable Interrupt)
APU 音源(矩形波2つ・三角波・ノイズ・DMC)を受け持つ回路(Audio Processing Unit)

VRAM に書けるのは、画面を消している間か垂直ブランクの間だけです(NESdev Wiki)。垂直ブランクは約2,273 CPU サイクルで、CPU の 1.79MHz で割ると約1.3ミリ秒しかありません(どちらも資料の値からの計算値)。描いている最中に書き換えると、1枚の中で新旧が混ざって画面がちぎれます。

そこで処理を2つに分けました。メインループでゲームの計算をして、書きたい場所と値を RAM のキュー(順番待ちの列)に貯め、NMI の中ではキューの中身を PPU のレジスタ $2006(アドレス)と $2007(データ)へ流すだけにします。ミノが動くときに書くのは8マスで、ライン消しのあとの全面再描画も NMI が1フレームに5行ずつ書いて5フレームで終わります。600フレーム測ると、垂直ブランクの時点でメインループが待っていた割合は100%で、計算は毎フレーム間に合っていました。

ファミリーベーシック固有の注意もあります。NMI の飛び先は ROM ではなく RAM の $00ED にあり、書き換えると BASIC の画面更新が止まります。BASIC が $32・$33 に持つ画面設定を自前の NMI で書き戻さないと、画面は真っ黒になります。この前提とソースの断片は、機械語対応(2)のリファレンスにまとめてあります。

パレイドファミリーベーシック 機械語対応(2)|AIに読ませる機械語リファレンスを作るこんにちは、パレイド技術部の夏目です。前回は「LLM に 6502 の機械語を書かせられるか」という問いを立てました。その後、実際にテトリスを機械語…

音も、APU に割り当てられたアドレスへ値を書くだけです。BASIC の PLAY は演奏が終わるまで次の行に進みません。PLAY "CDE" の直後の POKE が実行されたのは約118フレーム目で、最後の音源への書き込み(約85フレーム目)より後でした。ゲームを止めずに鳴らすには、NMI で毎フレーム少しずつ演奏を進める必要があります。マシン語版の割り当ては次のとおりです。

音源 役割
矩形波1 効果音(回転・固定・ライン消し・ゲームオーバー)と、和音のアルペジオ(根音→3度→5度を4フレームごと)
矩形波2 旋律
三角波 ベース(根音と5度を8分音符で交互に)
ノイズ 裏拍のハイハット
DMC 使わない

回転の効果音は $8A,$08,$E0,$08 の4バイトを書くだけです。矩形波1を効果音と和音で共有しているため、効果音が鳴っている間は和音を休ませます。BGM はロシア民謡「コロベイニキ」の最初の8小節を繰り返すもので、旋律は記憶から起こしました。音の高さと長さが設計どおりかは APU への書き込みを記録して確かめ、動画では伴奏つきで鳴らしています。

実装中に踏んだ罠は次の3つです。

  • BPL(直前の結果が負でなければ分岐する命令)でループを組むと、数える値が128以上のとき最初の1回で抜ける。行ずらしが動かず、ライン消しが無限ループになった。0まで数える形に直した
  • 枠のタイルを書く前に、A レジスタ(CPU が計算に使う置き場)を別の値で上書きしていた。右の枠が「,」の字になった
  • 分岐命令が届くのは ±127 バイトまで。超えたら JMP で中継する

公開コードを打ち込んで動かす

ファミリーベーシック V3 に打ち込める形で、BASIC のコードを pareido.jp に置きました。マシン語は DATA 文に16進で入っていて、POKE でメモリに置き、最後に CALL で呼びます。説明は README にあります。

ファイル 中身 先頭の行
tetris_01.bas マシン語の1パート目 CLEAR &H6B50
tetris_02.bas 2パート目 CLEAR &H6826
tetris_03.bas 3パート目。最後の CALL &H6808 でゲームが始まる CLEAR &H6808
tetris_basic_slow.bas AI に頼んだ BASIC だけの版(比較用の遅い方) –
# 1パート目を打ち込んで実行。DATA をメモリに置き終わるまで待つ
tetris_01.bas を入力 → RUN
# BASIC のプログラムを消して、次のパートへ
NEW
tetris_02.bas を入力 → RUN
NEW
# 最後のパートの CALL でゲームが始まる
tetris_03.bas を入力 → RUN

先頭の CLEAR は、BASIC が使うメモリの上限を決める命令です。使える RAM は $6000〜$6FFF の4KBだけで BASIC の本文と共有するため、後のパートほど置き先を下げ、次のパートの BASIC 本文が置いたマシン語を上書きしないようにしています。値は変えないでください。

操作は ←→ で移動、↓ でソフトドロップ(1段 +1点)、A で回転、START でやり直しです。BASIC 版との違いもあります。マシン語版は画面の上にはみ出して固定されるとゲームオーバーになり(BASIC 版は積み上がっても終わらない)、音があり、落下は30フレーム/行です。エミュレータへ自動でタイプ入力した場合、打ち込みから起動まで469秒でした。実機での打ち込みと、テープへの SAVE/LOAD は試していません。

まとめ

BASIC で AI に作らせたテトリス風は、1行落ちるのに約16秒かかりました。マシン語版は入力を1フレームごとに読み、全面再描画は5フレームで終わります。差を生んだのは命令の速さだけでなく、垂直ブランクに書く量を8マスまで絞り、音を NMI で進めるという、ファミコンの仕組みに沿った書き方です。

未確認のまま残っていることは次のとおりです。

  • 実機・実テープでの動作と速度
  • 測定用の印による遅れの大きさと、入力の取りこぼし率

同じ方法でスーパーマリオ風の 1-1 を作った後編は、別の記事にしました。

パレイドファミリーベーシックでマシン語に挑戦【後編】マリオ風 1-1 を 4KB に入れるこんにちは、パレイド技術部の夏目です。ファミリーベーシック V3(任天堂、1985年)の上で、AI にマシン語(CPU 6502 が直接実行する命令列)を書か…

連載の出発点「なぜ機械語か」はこちらです。

パレイドファミリーベーシック 機械語対応(1)|なぜ機械語かこんにちは、パレイド技術部の夏目です。1980年代、マイコンBASICマガジン(ベーマガ)の編集部では、ある種の「検閲」が行われていたという。初代編…

動画の音声: VOICEVOX:冥鳴ひまり

▶ 関連動画 · YOUTUBE
https://www.youtube.com/watch?v=USsr0Ff_6Tc
━━ 観るのを再開 ━━
←
前の記事を読む
技術部 · ファミリーベーシック 機械語対応(2)|AIに読ませる機械語リファレンスを作る
▶
動画を観る
YouTube
→
次の記事を読む
技術部 · ファミリーベーシックでマシン語に挑戦【後編】マリオ風 1-1 を 4KB に入れる
━━ 他の観測領域 ━━
TECH · 技術部
ファミリーベーシックでマシン語に挑戦【後編】マリオ風 1-1 を 4KB に入れる
PHIL · 思想部
AI自動化ピラミッドの可視化(10)|反転したのは、連載ではなかった――月次レビュー第3回
FRONT · 辺境部
【スーパーマリオ256W】256ワールド名鑑 No.25 水中を跳ねるバブル — 71-4 を自分のROMで遊ぶ