素晴らしい昔ながらのゲームプログラミングハック (2013)
A great old-timey game-programming hack (2013) (blog.moertel.com)
要約
この記事は、1980年代の8ビットPCの性能限界を克服するために使われた、昔ながらのゲームプログラミングにおける巧妙な最適化ハックについて解説しています。著者は、タイル描画の遅さに悩む友人のために、直接メモリ操作、プロセッサのスタック機能、アセンブリ言語の高度なテクニックを駆使した、段階的に複雑化していく「クレイジー」な最適化手法を詳細に説明しています。最終的には、割り込みによる画面表示の破損を防ぐための、巧妙なタイル描画順序の変更に至るまでが語られています。
全文翻訳
素晴らしい昔ながらのゲームプログラミングハック
昔々、私が大学生だった頃、コンピュータービデオゲームの開発に少し時間を費やしました。それは8ビットPCの時代で、今日の基準では信じられないほど遅いゲームハードウェアでした。
当時のゲームプログラマーが、ゲームをプレイ可能な速度で実行するために、あらゆる種類のクレイジーなことをしていたと聞いても、驚かないかもしれません。クレイジーで、クレイジーなことでした。
これは、そのうちの一つについての物語です。
重要な詳細については最善を尽くしましたが、いくつか間違っているかもしれません。もしそうなら、ご容赦ください。あれは遠い昔のことでした。
(注:この投稿には、一部のフィードリーダーがうまく処理できないインラインデータURLの小さな画像が含まれています。フィードで画像が表示されない場合は、元の記事にアクセスして画像をご覧ください。)
更新 2013-12-16:この話について、Hacker News や Proggit、そして ブログはこちら で多くの素晴らしいコメントがありました。皆さん、共有ありがとうございます!
セットアップ
私の友人で、才能あるプログラマーが、新しいゲームをほぼ完成させていました。彼はどうにかして、当時のアーケードで人気だった、グラフィック的に印象的なコインオペレーションゲームを、1980年代のPCにかなり忠実に詰め込んでいました。
唯一の問題は、彼のバージョンのゲームがプレイ不可能だったことでした。遅すぎ、動きがぎこちなく、プレイヤーの没入感を損なうものでした。結局のところ、それはサイドスクローラーでした。
大学のフルロードを取りながらゲームをしていた友人は、少し参っているように見えました。簡単な最適化を見落としたのではないかと恐れ、彼は私にコードを見るように頼みました。
私は見ました。しかし、簡単な最適化は何もありませんでした。
コードはタイトでした。どこを見ても、彼はすでに私が思いつく限りのことをすべてやっていました。ループはアンロールされ、不要な描画は削除され、無駄の証拠はすべて消えていました。
そして、それらとともに、私たちの簡単な修正への希望も消えました。
しかし、難しい修正は?クレイジーな修正は?
まあ、それは常に可能性でした。
しかし、クレイジーな話に入る前に、当時のグラフィックハードウェアがどのように機能していたかを説明するために、少し戻る必要があります。
当時のグラフィックハードウェアの仕組み
簡単な説明です。グラフィックハードウェアは何もしていませんでした。
問題のPCでは、画面に何かを描画したい場合、それを自分で、バイトごとにやらなければなりませんでした。テクスチャマッパーも、ブリッターもありません。ただバイトだけです。自分で移動しなければならないバイトです。
楽しいですね。
友人のゲームは、時間のほとんどを背景の再描画に費やしていました。(繰り返しになりますが、サイドスクローラーです。)各フレームで、プレイヤーの位置に合わせてシフトされた、ほぼ画面一杯の背景タイルを描画する必要がありました。
私の記憶が正しければ、各タイルは28x28ピクセルでした。各ピクセルは16色のうちの1つで、表現するには半バイトを必要としました。したがって、タイルはメモリ上で14バイトの連続した28行として表現されていました。最初の14バイトが最初の行を表し、2番目の14バイトが2番目の行を表し、以下同様です。
しかし、画面は320ピクセル幅で240ピクセル高でした。メモリ上では、スクリーンバッファは160バイトの連続した240行として配置されていました。
したがって、アドレスXにあるタイルをアドレスYから始まるスクリーンバッファ位置にコピーする場合、28行をコピーする必要がありました。各行をコピーするには、14バイトをコピーする必要がありました。幸いなことに、このゲームのプロセッサは6809で、いくつかの16ビットインデックスレジスタと、C言語のポインタに++演算子を適用するような、素晴らしい「自動インクリメント」アドレッシングモードを持っていました。これは、XとYレジスタを同時に進めながら、4ピクセルずつコピーできることを意味しました。
LDU ,X++ ; ソースタイルから2バイトワード(= 4ピクセル)を読み込む
STU ,Y++ ; スクリーンバッファに書き込む
1行をコピーするには、それを7回行う必要があったため、これらの行を7回のループで囲むことができました。
LDB #7 ; 残りのワード <- ワード単位のタイル幅
@LOOP
LDU ,X++ ; ソースタイルから2バイトワード(= 4ピクセル)を読み込む
STU ,Y++ ; スクリーンバッファに書き込む
DECB ; 残りワード数を減らす
BNE @LOOP ; ワードが残っている間ループ
行のコピーが終わったら、次に描画する次の行の開始アドレスを指すように、宛先ポインタYを進める必要がありました。スクリーンバッファは幅160バイトで、タイルは幅14バイトしかなかったため、その差をYに加える必要がありました。
LEAY 160-14,Y
そして、ちょうどそのように、1行を画面にコピーしました。
それは1つです。タイル全体をコピーするには、同じことを28回行う必要があります。したがって、 turn、そのコードを28回のループで囲む必要があります。
すべてをまとめ、重要な数値に名前を付けると、次のようなサブルーチンになるかもしれません。
;;; 重要な定数
SCRW = 160 ; スクリーンバッファの幅(バイト単位、= 320 4ビットピクセル)
TILW = 14 ; 背景タイルの幅(バイト単位、= 28 4ビットピクセル)
TILH = 28 ; 背景タイルの高さ(行単位)
WOFF = SCRW - TILW ; 1タイルの行の終わりから次の行の開始までのS-Bオフセット
COPYTILE
;;;
;;; 28x28の背景タイルをスクリーンバッファにコピーする。
;;; 引数:
;;; X = 背景タイルの開始アドレス
;;; Y = スクリーンバッファ内の宛先の開始アドレス
;;;
LDA #TILH ; 残り行数 <- タイルの高さ
@COPYROW
LDB #TILW/2 ; 残りワード数 <- ワード単位のタイル幅
@LOOP
LDU ,X++ ; ソースタイルからワード(= 4ピクセル)を読み込む
STU ,Y++ ; スクリーンバッファに書き込む
DECB ; 残りワード数を減らす
BNE @LOOP ; ワードが残っている間ループ
;;
LEAY WOFF,Y ; 次の宛先行の開始位置に宛先ポインタを進める
DECA ; 残り行数を減らす
BNE @COPYROW ; 行が残っている間ループ
;;
RTS ; 完了!呼び出し元に戻る
そして、そのコードは問題なかったでしょう。
速度を気にしなければ。
速度を気にする
ゲームがおそらくそのコードの実行にほとんどの時間を費やすことを知って、あなたはどんな良いプログラマーでもするように、サイクルを数え始めます。ここでは、セットアップと終了を含めたインナーループを、サイクル数とともに示します。
LDB #TILW/2 ; 2サイクル(セットアップ)
@LOOP
LDU ,X++ ; 8
STU ,Y++ ; 8
DECB ; 2
BNE @LOOP ; 3
;;
LEAY WOFF,Y ; 8(終了処理)
ループ内のこれらのカウントを見ると、わずか4ピクセルをコピーするために21サイクルを消費していることに気づかない可能性は低いでしょう。したがって、1行をコピーするには、2サイクル + (7イテレーション) * (21サイクル/イテレーション) + 8サイクル = 157サイクルになります。痛いですね。
しかし、これはキーボードを叩くのが初めてではありません。何をすべきか知っています。そのループをアンロールしてください!
LDU ,X++ ; 8サイクル
STU ,Y++ ; 8
LDU ,X++ ; 8
STU ,Y++ ; 8
LDU ,X++ ; 8
STU ,Y++ ; 8
LDU ,X++ ; 8
STU ,Y++ ; 8
LDU ,X++ ; 8
STU ,Y++ ; 8
LDU ,X++ ; 8
STU ,Y++ ; 8
LDU ,X++ ; 8
STU ,Y++ ; 8
LEAX TILW,X ; 8(終了処理)
LEAY SCRW,Y ; 8(終了処理)
ループオーバーヘッドがゼロに削減され、セットアップさえも排除された今、各行はわずか7 * (8 + 8) + 8 = 120サイクルしかかかりません。これは30パーセントの速度向上です。かなり良いです。
そして、ほとんどのプログラマーはおそらくそこでやめたでしょう。
しかし、私の友人ではありません。
彼は++操作が3サイクルかかることを知っていました。そして、ループがアンロールされたことで、各読み書きされるワードがXまたはYに対してどこにあるかを正確に知っていました。そこで彼は、それらの3サイクルかかるポストインクリメントを、正確なオフセットに巧妙に置き換えました。それらは1サイクルしかかからず、0オフセットは実際には無料です。
LDU ,X ; 5サイクル
STU ,Y ; 5
LDU 2,X ; 6
STU 2,Y ; 6
LDU 4,X ; 6
STU 4,Y ; 6
LDU 6,X ; 6
STU 6,Y ; 6
LDU 8,X ; 6
STU 8,Y ; 6
LDU 10,X ; 6
STU 10,Y ; 6
LDU 12,X ; 6
STU 12,Y ; 6
LEAX TILW,X ; 8(終了処理)
LEAY SCRW,Y ; 8(終了処理)
彼の最適化により、1行あたりのサイクル数は (5 + 5) + 6 * (6 + 6) + (8 + 8) = 98サイクルに削減されました。元のコードと比較して、60パーセント高速です。
original_speed = (1*row) / (157*cycle)
optimized_speed = (1*row) / (98*cycle)
speed_up = optimized_speed / original_speed - 1 = 157 / 98 - 1 = 0.60。
すべてをまとめると(繰り返しになりますが、記憶を頼りにしているので、コードは少し異なっていたかもしれません)、コピータイルのサブルーチンは次のようになり、フルタイル、つまり28行すべてを、わずか2893サイクルでコピーしました。
COPYTILE2
;;;
;;; 28x28のスクリーンタイルをスクリーンバッファにコピーする。
;;; 引数:
;;; X = 背景タイルの開始アドレス
;;; Y = スクリーンバッファ内の宛先の開始アドレス
;;; 実行時間:
;;; 4 + 28 * (82 + 8 + 8 + 2 + 3) + 5 = 2893サイクル
;;;
LDA #TILH ; 行数を初期化(4サイクル)
;;
@COPY1
;; インナーループをアンロール(1行あたり28ピクセルを82サイクルでコピー)
LDU ,X ; (1) 4ピクセルを読み込む(5サイクル)
STU ,Y ; 4ピクセルを書き込む(5サイクル)
LDU 2,X ; (2) (6サイクル)
STU 2,Y ; (6サイクル)
LDU 4,X ; (3) ...
STU 4,Y ; ...
LDU 6,X ; (4)
STU 6,Y ;
LDU 8,X ; (5)
STU 8,Y ;
LDU 10,X ; (6)
STU 10,Y ;
LDU 12,X ; (7)
STU 12,Y ;
;;
LEAX TILW,X ; 次の行の開始位置にソースを進める(8サイクル)
LEAY SCRW,Y ; 次の行の開始位置に宛先を進める(8サイクル)
DECA ; 残りカウントを1減らす(2サイクル)
BNE @COPY1 ; 行が残っている間ループ(3サイクル)
;;
RTS ; 完了!呼び出し元に戻る(5サイクル)
このコード全体で、単純なCOPYTILEコードよりも60%高速でした。
しかし、それは十分な速度ではありませんでした。まったく。
それで、友人が私にコードを見せて、もっと速くできるか尋ねたとき、私は本当に彼を助けたいと思いました。本当に「はい」と答えたいと思いました。
しかし、私は「いいえ」と答えなければなりませんでした。その答えを言うのは嫌でした。しかし、コードを研究して、スピードアップする方法を見つけることができませんでした。
それでも、フックはかかりました。
後で、その問題を頭から離すことができませんでした。何か見落としたのかもしれません。私はApple IIコンピュータとその6502プロセッサで育ちました。しかし、友人のコードは6809用でした。おそらく、私が知らない最適化が可能だったのかもしれません。
新たな楽観主義をもって、私は大学の図書館に電話してカードカタログにアクセスしました。(これはWorld Wide Web以前の日々でした。)VT220ターミナルエミュレータを通して、私は6809に関する本を検索しました。
一冊ありました。6809マイクロプロセッサのマニュアルです。それは工学部の図書館にありました。幸いなことに、私は工学部の学生で、借り出し特権を持っていました。
ちょうどそのように、私は図書館に向かいました。
クレイジーなアイデア
工学部の図書館に着くと、本はそこにあるべき場所にあり、少し使い古されたように見えました。
The MC6809-MC6809E Microprocessor Programming Manual。
椅子を見つけるのも面倒で、ページを立ちながらめくりました。奇妙な6809固有の命令で、大量のバイトを高速に処理できるものを探しました。しかし、ページをめくっても何も見つかりませんでした。
そして、PSHS にたどり着きました。「ハードウェアスタックにレジスタをプッシュする。」
私の愛する6502では、スタックにレジスタを保存したい場合、レジスタごとに、それもアキュムレータを経由して転送する必要がありました。それは遅く、コストがかかりました。そのため、速度が重要な場合はスタックを避けるように学びました。
しかし、6809では、すべてのレジスタ、またはそのサブセットを単一の命令で保存できました。そして驚くべきことに、それはわずか5サイクルで、プッシュされるバイトごとにさらに1サイクルでした。
プロセッサには3つの16ビット汎用レジスタ(D、X、Y)があったため、それらをロードしてから単一のPSHS命令を使用して、わずか11サイクルで6バイトを書き込むことができました。対応するプル命令であるPULS も同様に低コストでした。
さらに、6809には2つのスタックレジスタ、SとUがありました。一方をソースポインタとして、もう一方を宛先ポインタとして使用できました。理論的には、単一のPULS/PSHUペアで、22サイクルで6バイトをコピーできました。
それはクレイジーに、クレイジーに速いです。
興奮が高まり、私はフロントデスクに向かい、学生証を取り出しました。さらに研究するために、その素晴らしい小さな本を借りるつもりでした。
クレイジーな計画
寮に戻る途中、私は計画を立てました。
SとUレジスタをどこかに保存し、Sを背景タイルに、Uをスクリーンバッファに向けます。次に、D、X、Yを中間レジスタとして使用して、Sからプルし、Uにプッシュして、一度に6バイトをコピーします。1行を構成する14バイトをコピーするには、3回のこのようなイテレーションが必要で、アンロールすると約60サイクルになります。
部屋に着くと、紙切れを見つけてスケッチしました。
PULS D,X,Y ; 最初の6バイト 11サイクル
PSHU D,X,Y ; 11
PULS D,X,Y ; 2番目の6バイト 11
PSHU D,X,Y ; 11
PULS D ; 最後の2バイト 7
PSHU D ; 7
LEAU -WOFF,U ; 宛先ポインタを進める 8
行の後のUの調整(次の行の準備)を含めて、わずか66サイクルです。(調整は現在負であることに注意してください。)比較のために、先ほど見た単純な行コピーループは、同じことを行うのに157サイクルかかりました。そして、私の友人の最適化されたコードは98サイクルでした。すでに、このクレイジーなアイデアは大きな勝利のように見えました。
それでも、最後のPULS/PSHUペア!なんてこった。行は28ピクセル=14バイト幅で、6は14で割り切れないため、各行の最後の2バイトを処理するために必要でした。
あの忌々しい2バイトの残り!
もしゲームが代わりに24x24のタイルを使っていたら…しかし、そうではなかったので、マニュアルをめくり、その避けられない最後のペアのコストを下げる方法を探しました。
そして、驚いたことに、私は金鉱を掘り当てました!それは別の6809の奇妙な機能、DPレジスタでした。
6502や当時のほとんどの8ビットプロセッサでは、メモリの最下位256バイトはゼロページと呼ばれていました。ゼロページは特別でした。なぜなら、そのメモリ位置は1バイトのアドレスを持ち、より短く、通常はより高速な命令でアクセスできたからです。
6809の設計者は、このアイデアをさらに一歩進めました。彼らはDPレジスタを使用して、任意のページをゼロページとして指定できるようにし、それを「ダイレクトページ」と呼びました。
しかし、私のバイト操作命令のどれもダイレクトページを使用する必要はありませんでした。そのため、DPレジスタを追加の1バイト中間レジスタとして使用できました。これで、プル・プッシュペアごとに7バイトをコピーできるようになりました!
そして、14は7で割り切れます。
この変更により、わずか5命令で、28ピクセルの行全体をコピーして次の行に進むことができました。
PULS D,X,Y,DP ; 最初の7バイト 12サイクル
PSHU D,X,Y,DP ; 12
PULS D,X,Y,DP ; 2番目の7バイト 12
PSHU D,X,Y,DP ; 12
LEAU -WOFF,U ; 宛先ポインタを進める 8
56サイクル!56サイクル。
このコードは私に素晴らしい気分にさせてくれました。私はマシン上の利用可能なすべてのレジスタをバイト操作のために活用することに成功しました!D、X、Y、U、S、そして奇妙なDPさえも、すべて完全に稼働していました。
このソリューションについて素晴らしい気分でした。
ただ一つのことを除いては…
もう少しクレイジーなものをください
スタックに慣れているなら、私の素晴らしい計画に微妙な欠陥があることに気づいたかもしれません。
プッシュとプルは逆方向に機能します。
見てください、私が先ほど示したコードブロックは嘘でした。それは実際に背景タイルから画面に1行をコピーしたわけではありません。実際に行ったことは、行を2つの7バイトブロック(「セプテット」と呼びましょう)に分割し、それらをスワップして画面に描画することでした。
タイルの行はメモリ上で、14個の連続したバイトとして合理的に配置されていたことを思い出してください。
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 1 2 3 4 5 6 7 8 9 A B C D |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
したがって、7バイトのセプテットで1行をプル・プッシュして画面に描画すると、水平方向に次のように分割されます。
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 7 8 9 A B C D | 0 1 2 3 4 5 6 |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
最初のセプテットは2番目になり、2番目は最初になりました。ただし、各セプテット内のバイトは変更されておらず、元の順序を維持していることに注意してください。
さらに、行コピーコードを複数回実行すると、コピーされた行の垂直スタックが上下逆さまになります。これは、コードがプッシュを使用して行を画面に書き込むためです。プッシュはスタックポインタを下位アドレスに向かって移動させ、下位アドレスはディスプレイ上のより高い画面行に対応します。
この行の反転とセプテットのスワップが引き起こす混乱を視覚化するために、キーを表す次の28x28タイルがあるとしましょう。
[画像: キーのピクセルアート画像。]
私の行コピーコードを28回使用して画面に描画した場合、次のようにならないキーが得られます。
[画像: キーの歪んだピクセルアート画像。元の画像は水平軸に対して反転され、上下から半分にカットされ、左右の半分がスワップされています。]
それで…それは問題でした。
しかし、この問題にもエレガントな解決策がありました!
要約すると:
- 行は上下逆さまだった
- 各行内のセプテットはスワップされていた
- しかし、各セプテット内のバイトは変更されていなかった
ブレークスルーは、「上下逆さま」と「スワップ」を同じものの2つのインスタンスとして見ることでした。反転。
反転には便利な性質があります。それは対合です。何かを2回反転すると、元に戻ります。これを念頭に置いて、タイルが事前に処理されて行が反転され、次にセプテットが行内で反転された場合、タイルは画面に表示されるときに正しく見えるだろうと推論しました。コピー中および事前処理中に発生する破損は、実質的に互いに打ち消し合います。
紙の上で計算したところ、事前処理ステップでは行を無視して、タイルをセプテットの長い線形シーケンスとして扱うことができることも発見しました。1 なぜこれがそうなるのかを見るために、2行x2セプテットの小さなタイルを考えてみましょう。
+---+---+
| a b |
+---+---+
| c d |
+---+---+
メモリ上では、行優先順序で、つまり4つの連続したセプテットとして配置されます。
+---+---+---+---+
| a b | c d |
+---+---+---+---+
したがって、行を反転してから各行内のセプテットをスワップすることは、メモリ内のセプテットの順序を単純に反転することと同じです。
+---+---+ +---+---+ +---+---+
| a b | | c d | | d c |
+---+---+ =======> +---+---+ =======> +---+---+
| c d | reverse | a b | swap | b a |
+---+---+ rows +---+---+ septets +---+---+
+---+---+---+---+ +---+---+---+---+ +---+---+---+---+
| a b | c d | | c d | a b | | d c | b a |
+---+---+---+---+ +---+---+---+---+ +---+---+---+---+
したがって、破損問題の解決策は、単純な一度限りの事前処理ステップであることが判明しました。各タイルをセプテットに分割し、それらの順序を反転するだけです。
破損問題が解決できることを知って、私は全体的なアイデアについて良い気分になりました。他に問題は見当たらなかったので、友人に提示するために新しいコピータイルのサブルーチンをスケッチしました。行コピーロジックはわずか5命令になったので、それを4回使用し、唯一残ったループを少しアンロールしました。今では、28行を個別にコピーする代わりに、7つのクワッド行をコピーしました。
コードは次のようになりました。
COPYTILE3
;;;
;;; 28x28のスクリーンタイルをスクリーンバッファにコピーする。
;;; 引数:
;;; X = *セプテット反転済み*背景タイルの開始アドレス
;;; Y = スクリーンバッファ内の宛先の開始アドレス
;;; 実行時間:
;;; 34 + 7 * (224 + 7 + 3) + 7 + 10 = 1689サイクル
;;;
;; セットアップ: 34サイクル
PSHS U,DP ; UとDPを保存(8サイクル)
STS >SSAVE ; Sを保存(7サイクル)
;;
LDA #TILH/4 ; クワッド行数の初期化(2サイクル)
STA >ROWCT ; (5サイクル)
;;
LEAS ,X ; ソースポインタを初期化(4サイクル)
LEAU (TILH-1)*SCRW+TILW,Y ; 宛先ポインタを初期化(8サイクル)
;;
@COPY1
;; 4行の28ピクセルを4 * (48 + 8) = 224サイクルでコピー
PULS X,Y,D,DP
PSHU X,Y,D,DP
PULS X,Y,D,DP
PSHU X,Y,D,DP
LEAU -WOFF,U
PULS X,Y,D,DP
PSHU X,Y,D,DP
PULS X,Y,D,DP
PSHU X,Y,D,DP
LEAU -WOFF,U
PULS X,Y,D,DP
PSHU X,Y,D,DP
PULS X,Y,D,DP
PSHU X,Y,D,DP
LEAU -WOFF,U
PULS X,Y,D,DP
PSHU X,Y,D,DP
PULS X,Y,D,DP
PSHU X,Y,D,DP
LEAU -WOFF,U
;;
DEC >ROWCT ; 残りクワッド行数を1減らす(7サイクル)
BNE @COPY1 ; クワッド行が残っている間ループ(3サイクル)
;;
LDS >SSAVE ; Sを復元(7サイクル)
PULS U,DP,PC ; レジスタを復元して呼び出し元に戻る(10サイクル)
SSAVE ZMD 1 ; 描画中にSを保存するための領域
ROWCT ZMB 1 ; 変数:コピーする残り行数
サイクル数を合計すると、わずか1689になりました。友人のコードから1200サイクル近く削減しました。これは70パーセントの速度向上です!
得意げに、私は友人に会いに行きました。
試金石
友人に追いつき、コピータイルのルーチンを70%速くできたと伝えると、彼の顔が輝きました。しかし、セプテットと破損の件を説明すると、彼の顔は曇り、疑念が湧き上がりました。しかし、コードを見せると、彼は理解しました。それは腑に落ちました。
「試してみよう!」と彼は言いました。
約30分で、彼はコードをゲームに統合しました。再ビルドと再起動の後、ゲームはロードされました。
タイトル画面が表示されました。
彼はゲームに入りました。
そして…
なんてこった!
ゲームは驚くほど速く見えました。実際には、おそらく3分の1から半分速くなっただけでしたが、それでも十分でした。重要な知覚の閾値を超えたのです。ゲームプレイはスムーズで自然になりました。違いを感じることができました。
私たちはただ座って、ゲームをプレイして、ニヤニヤしていました。良い一日でした。
しかし、それは長くは続きませんでした。
ペニーのために…
速度の問題が解決してから数日後、私たちはゲームの仕上げを行いました。その仕上げの一つが、サンプリングされた効果音でした。これには、毎秒数千回、オーディオ出力DACにバイト単位のデータを供給する必要がありました。供給をスケジュールするために、友人はハードウェアクロック割り込みを有効にしました。
うまく機能しました。サウンドは素晴らしく、すべてが完璧でした。
ただ一つのことを除いては。
ある夜、ゲームをプレイしていると、一部のタイルが破損していることに気づきました。プレイすればするほど、破損が増えていきました。
ああ、いやだ。
何か非常に悪いことが起こっていました。
そして、それが私たちを襲いました。
コピータイルのルーチン中に割り込みが発生したらどうなるか?
プロセッサは、割り込みハンドラを呼び出す準備として、現在の状態をシステムスタックにプッシュします。しかし、コピータイルのルーチン中、システムスタックはありませんでした。システムスタックレジスタSは乗っ取られていました。そして、それはどこを指していましたか?参照タイルを保持するメモリバッファに!
しまった。
私たちはうなだれました。あれほど努力したのに、私たちの最も重要な速度アップがメモリ破損を引き起こしているとは…
休憩が必要だったので、私たちはキャンパス近くの24時間営業のダイナーに行き、食べて考えることにしました。パンケーキとベーコン、スクラプルとグリルドスティッキーを食べながら、私たちは問題について話し合いました。
スタックレジスタは2つしかなく、両方を乗っ取らずにコピータイルのルーチンをそれほど速くすることはできませんでした。したがって、システムにSレジスタを返して速度を維持する方法はありませんでした。しかし、割り込みを使用せずに信頼性の高いサウンドを得る方法もありませんでした。
いずれにせよ、割り込みはトリガーされることになっていました。そして、それらがトリガーされたとき、コピータイルが実行されていた場合、Sが指していたものはすべてハッキングされることになっていました。
「どうすれば破損を防げますか?」と私は尋ねました。
私たちはそこに座り、食べ物を無視して、質問が空中にぶら下がっていました。
突然、友人がテーブルを叩きました。彼はそれを思いつきました。
「防ぐな!」と彼は言いました。
「何だって?」と私は尋ねました。
「破損を防ぐな」と彼は説明しました。「発生させろ。ただし、参照タイルに対してはするな。」
それは本当にシンプルでした。彼は続けました。
「コピータイルのルーチンでSとUをスワップするだけだ。Uは参照タイルを指し、Sはスクリーンバッファを指す。割り込みが発生した場合、破損はSが指している場所、つまり画面で発生する。破損は次のフレームを再描画するまでしか持続しない。」
「それは素晴らしい!」と私は言いました。
彼の解決策を試すのが待ちきれず、私たちはすぐに食事を終えました。
…ポンドのために
寮に戻る途中、プレイヤーが画面のグリッチを見る可能性があることを考えると、たとえ1フレームだけでも、それは私たちを悩ませました。二人とも、もし見つけられれば、ハックを完璧にする方法があるはずだと感じていました。
その夜、私たちはそれを見つけました。
一度見れば、それは再びシンプルでした。タイル順序を変更するだけでよかったのです。タイルを画面に左から右、上から下に配置する代わりに、右から左、下から上に配置します。つまり、高アドレスから低アドレスへです。
そうすれば、割り込みが発生して、現在描画中のタイルの直前のメモリ位置が破損した場合でも、次のタイルが描画されるときに破損は修正されます。そして、タイル描画は、完全に描画されるまで(そして垂直リフレッシュが発生するまで)表示されないバッファで行われたため、誰も破損を見ることはありませんでした。2
それは完璧なハックでした!
古き良き時代へ!
そして、それが当時の状況でした。課題は、今日のように圧倒的な複雑さではありませんでした。課題は、アイデアのほとんどが収まらないほど遅く、制限されたマシンにアイデアを詰め込むことでした。
アイデアをぶつけ合い、ねじり、回し、何か、何かマシンに収めるのに役立つものを探さなければなりませんでした。時にはそれを見つけ、アイデアの実現に一歩近づきました。時には見つけられませんでした。
しかし、その探求は常に有益でした。
この場合、探求はいくつかの小さな勝利をもたらし、それらを合わせて問題を解決しました。しかし、あの忌々しいゲームを十分に速くするために私たちが行ったすべてのことを考えると、それは確かにクレイジーに思えます。
私たちは、手作業で調整され、アンロールされた機械命令のループをコアとするタイルコピーサブルーチンから始めました。次に、70パーセントの速度向上を得るために:
- このサブルーチンを、両方のスタックポインタを乗っ取り、プルとプッシュ、そして利用可能なすべてのレジスタを使用して、タイルを上下逆さまに水平に分割して描画する、非常に特殊な狂気の現れに置き換えました。
- 次に、タイルを描画すると実際に修正されるように、タイルを事前処理しました。
- しかし、その後—くそー—描画中に発生した割り込みが参照タイルを破損させる可能性がありました。
- そこで、参照タイルを保護するために、代わりにスクリーンバッファを破損させました。
- しかし、その破損は表示される可能性がありました。
- そこで、タイル配置順序を変更して、表示される前に発生した可能性のある破損をオンザフライで修正しました。
- そしてすべてがうまくいきました!
私たちはそれをやりました。70パーセントのために。
そして、それはそれだけの価値がありました。
持っているなら伝えろ
とにかく、この話をあなたと共有したかった理由は2つあります。
第一に、それは楽しい話だからです。それは、私が厄介なコンピューティングの問題と格闘し、粘り強く取り組むことで、効果的でエレガントな解決策にたどり着いた初期の記憶の1つです。結果は非常に満足のいくものでした。
苦闘は、価値があることを学びました。
第二の理由は、皆さんに自分の話をするように奨励することです。「昔々」には、ビデオゲームはおそらくこのような多くの物語を生み出したでしょう。私はそれらを聞きたいです。しかし、あまりにも多くが、誰かが保存することを考える前に記憶から薄れてしまいました。
話があれば、待たないでください。話してください。待つたびに、それは難しくなります。
話してください。
-
演習:すべての有限シーケンスの有限シーケンスに対して、(reverse ⋅ concat) を適用することが、(concat ⋅ reverse ⋅ map reverse) を適用することと同じであることを証明してください。↩︎
-
また、スクリーンバッファの直前のメモリ位置に貴重なものが格納されていないことを確認する必要もありました。割り込みが左上隅タイルの左上セプテットを配置中に発生した場合、理論的にはそれらも破損する可能性がありました。このセプテットのアドレスはバッファの開始に対応していました。↩︎
[2] タイトル: AI is an information gateway. Audiences still don't want it making their news
URL: https://www.niemanlab.org/2026/11/ai-is-becoming-an-information-gateway-audiences-still-dont-want-it-making-their-news/
本文:
(取得不可)
[3] タイトル: LLMs Play Zachtronics' TIS-100
URL: https://tis-100.wincon.dev/
本文:
TIS-100ベンチマーク:LLMがプログラミングパズルゲームのパレートフロンティアを押し上げる
2026.11.03 · 全55パズル →
このページリーダーボード仕組み記録を詳しく見るモデルを分けるものモデルが構築したものゴッドモード共同発見パレートエクスプローラー手法関連研究引用クレジット全55パズル →
12ノード。ノードあたり2レジスタ。15行のコード。TIS-100では、2つの数値を乗算することさえ小さなエンジニアリングプロジェクトであり、プレイヤーは公開されているコミュニティリーダーボードでこれらのプログラムを最適化するために10年間を費やしてきました。私たちは16のLLMにゲームとそのマニュアルを与え、リーダーボードを伏せ、改善を続けるように指示しました。
133 新しいフロンティアポイント
18 / 55 新しいフロンティアポイントを持つパズル
3 完全なサイクルレコード
数日以内に、プレイヤーはモデルのプログラムを取り込み、ほとんどのものを改善しました。
各ノードは小さなプロセッサであり、隣接するノードは値を交換するために互いに待たなければなりません。ゲームはサイクル、ノード、命令で動作するソリューションをスコアリングし、もう1命令削除できないかと考え始めます。少ないほど良いですが、1つのスコアを改善すると別のスコアが悪化する可能性があるため、各パズルには1つの最良の答えではなく、トレードオフのフロンティアがあります。
リーダーボード
最先端のフロンティアモデルとオープンウェイトモデルを含む16のLLMを実行しました。すべてのモデルが全47パズルと8つのコミュニティトーナメントパズルを試しました。それぞれ1回のスコア付き試行(pass@1)で、パズルごとに10分から45分かけて、動作するプログラムを見つけ、改善し続けました。
モデルランキング
少なくとも1つの新しいフロンティアポイントを持つパズル · 高いほど良い
ランク別パズル難易度
全55パズル · 19易 / 17中 / 19難
すべてのモデルを比較するには水平方向にスクロールしてください →
ポイントは、新しいゲームでの再生と100,000回のテストをパスした場合にのみカウントされます。
Opus 5.5とGPT-6 Astraがリードしており、データはそれらを区別しません。両方とも全55パズルを解き、11のパズルで新しいフロンティアポイントを見つけました。Opus 5.5はより多くのポイント、72対32を見つけ、GPT-6 Astraはより高いレコード近接度、78.0対77.2を持っています。レコード近接度はコミュニティレコードへの近さを測定し、100はそれに一致することを意味します。近接度のギャップの95%ブートストラップ区間はゼロを含み、-0.7から+2.4ポイントです。GPT-6 Astraは、各モデルのトレードオフがカバーする空間を測定する、6つのテストされたハイパーボリューム設定すべてでもリードしています。
最適化と最適化は異なるスキルでもあります。Gemini 3.8 Flashは53パズルを解き、1つのパズルで新しいポイントを見つけました。Fable 5.1はより少ない52パズルを解き、5つのパズルで新しいポイントを見つけました。
完全なランキングテーブルを展開
TIS-100ベンチマークモデルランキング。列ヘッダーを選択して並べ替えます。
| 01Opus 5.5 | 55 / 55 | 11 | 77.2 |
| 02GPT-6 Astra | 55 / 55 | 11 | 78.0 |
| 03Gemini 4 Argon | 54 / 55 | 8 | 76.2 |
| 04Sonnet 5.5 | 53 / 55 | 7 | 69.9 |
| 05GPT-6.1 Sol | 54 / 55 | 6 | 75.6 |
| 06Fable 5.1 | 52 / 55 | 5 | 70.2 |
| 07Kimi K3.1 Pro | 52 / 55 | 2 | 64.6 |
| 08Gemini 3.8 Flash | 53 / 55 | 1 | 66.0 |
| 09Muse Wonder 1.0 | 49 / 55 | 1 | 58.8 |
| 10Haiku 5.5 | 45 / 55 | 1 | 53.7 |
| 11GLM-5.3 | 30 / 55 | 1 | 35.5 |
| 12Grok 4.7 | 44 / 55 | 0 | 50.1 |
| 13DeepSeek V4.1 Flash | 40 / 55 | 0 | 46.8 |
| 14Muse Spark 1.3 | 39 / 55 | 0 | 45.1 |
| 15GPT-6 Luna | 39 / 55 | 0 | 42.8 |
| 16MiMo-V2.6-Pro | 10 / 55 | 0 | 12.8 |
(55パズル中のカウント。手法)
RL環境とトレースは研究チーム向けにリクエストに応じて利用可能です。お問い合わせください ↗
仕組み:ハーネス設計
ここでのすべての結果は、実行およびテスト可能なプログラムであり、プログラミングゲームを有用なベンチマークにしています。モデルは、マウスとスクリーンショットではなく、テキストツールを通じて、プレイヤーが検査できるもの(マニュアル、表示されるテスト入力、レジスタ、ゲームフィードバック)を受け取ります。コミュニティコードや正確なレコードは受け取らず、ゲームの粗いスコアヒストグラムのみを受け取ります。
読み込み、編集、実行
モデルはパズルと言語リファレンスを読み、プログラムを編集し、実際のゲームで実行します。
診断
間違った出力とデッドロックは不一致、レジスタ、およびブロックされたポートを返します。これにより、次の編集は証拠に応答できます。
保存された進捗
パスしたすべてのプログラムは、モデル自身のフロンティアに参加し、get_bestで回復できるため、リスクのある編集で以前の結果を失うことはありません。
リマインダー
モデルがターンを早期に終了した場合、リマインダーがテストを続けるように求めます。早期終了してもスコアは決定されません。リマインダーにはソリューションのヒントは含まれていません。
表示される時計
モデルは、10分、30分、または45分の制限時間に対して使用された時間を確認します。セットアップはカウントされず、思考とゲームの呼び出しがカウントされます。モデルはエピソードを早期に終了できません。
実行制限
ゲームの実行は、状態変化なしで約1秒、出力なしで5秒、または全体で60秒後に停止し、診断を返します。
なぜ100,000回の入力をテストするのか? ゲームは実行ごとに少数のケースをチェックします。私たちのヘッドラインカウントは、新しいゲームでの再生と独立したTIS-100-CXXシミュレータでの100,000回のテストをパスすることを必要とし、プログラムが新しい入力に一般化することについてのより強力な証拠を提供します。
記録を詳しく見る
Undecimal Twin RounderSPEC44149538は、8つのトーナメントパズルの1つで、入力をNの倍数の最も近いものに丸め、もう一方を11-Nの倍数の最も近いものに丸めます。リーダーボードをフリーズしたとき、最高のコミュニティプログラムは2,694サイクルかかりました。Opus 5.5は6.5分後に最初のプログラムをパスし、5,669サイクルでした。記録を16分下回り、29%高速な1,918サイクルで終了しました。その速度は、古い記録よりもノードを1つ、命令を28個多く消費しました。
プログラムのコアは2速検索です。Nを1ステップずつ減算する代わりに、ノード5と6は4Nを減算してオーバーシュートし、その後Nを正になるまで加算します。それぞれが小さなコプロセッサ(ノード2またはノード7)を駆動し、ポート経由でジャンプオフセットを送信するため、コプロセッサは4NとNのルックアップテーブルとして機能します。
Opus 5.5の1,918サイクルプログラム
Opus 5.5 · 8ノード · 88命令 · 100,000回のテスト中100,000回パス
[画像: TIS-100で実行されているOpus 5.5の1,918サイクルUndecimal Nearest Multiplesプログラム]
ノード 0
MOV RIGHT, ACC
JRO ACC
NOP
ADD 1
NOP
ADD 1
NOP
ADD 1
NOP
SUB 3
MOV ACC, RIGHT
ADD UP
MOV ACC, RIGHT
MOV ACC, DOWN
MOV RIGHT, DOWN
ノード 1
MOV UP, ACC
SAV
SUB 1
MOV ACC, LEFT
NEG
ADD 10
MOV ACC, DOWN
MOV LEFT, ACC
NEG
ADD ACC
ADD 6
ADD LEFT
MOV ACC, DOWN
SWP
MOV ACC, LEFT
ノード 2
MOV UP, DOWN
MOV UP, ACC
SAV
ADD ACC
ADD ACC
L: JRO DOWN
MOV ACC, DOWN
JMP L
SWP
N: MOV ACC, DOWN
M: JRO DOWN
JMP N
MOV DOWN, DOWN
ノード 3
MOV UP, RIGHT
MOV UP, DOWN
ノード 4
MOV LEFT, DOWN
ノード 5
MOV UP, ACC
SAV
K: MOV 1, UP
SUB UP
JGZ K
MOV 3, UP
F: ADD UP
JGZ X
MOV 1, UP
JMP F
X: MOV 2, UP
MOV ACC, UP
SWP
SUB UP
MOV ACC, DOWN
ノード 6
MOV UP, ACC
SAV
K: MOV 1, RIGHT
SUB RIGHT
JGZ K
MOV 3, RIGHT
F: ADD RIGHT
JGZ X
MOV 1, RIGHT
JMP F
X: MOV 2, RIGHT
MOV ACC, RIGHT
SWP
SUB RIGHT
MOV ACC, DOWN
ノード 7
MOV UP, ACC
SAV
ADD ACC
ADD ACC
L: JRO LEFT
MOV ACC, LEFT
JMP L
SWP
N: MOV ACC, LEFT
M: JRO LEFT
JMP N
MOV LEFT, LEFT
パズルの実際のグリッド上に配置され、入力は上部、出力は下部にあります。空白のノードは破損しており、使用できません。
パズルトラジェクトリを読み込み中…
[図]
5つのモデルがゲーム内で古い記録を下回りました。他の2つのサイクルレコードは、GPT-6.1 SolのIntersection IdentifierSPEC64160260での3,460(3,872に対して11%高速)、およびGemini 4 ArgonのError: 8SPEC79324720での188(198に対して5%高速)です。これら3つはすべてトーナメントパズルであり、メインゲームではありません。133の新しいフロンティアポイントのうち、18はコミュニティレコードを直接上回りました。残りの115は新しいトレードオフです。つまり、コミュニティソリューションよりも3つのスコア間のバランスが優れています。
[画像: TIS-100で実行されているOpus 5.5のPrime Factor Calculatorプログラム]
新しいフロンティアポイントが実際のゲームで実行されています。Opus 5.5のPrime Factor CalculatorNEXUS.20.433.1で、1,806サイクル、10ノード、103命令です。
モデルを分けるもの
コストは品質と一致しない
GPT-6.1 Solは、推定1パズルあたり0.75ドルで75.6のレコード近接度に達します。Opus 5.5は、7倍のコストで2ポイント多い77.2に達します(5.08ドル)。Fable 5.1は最も高価で7.19ドルですが、スコアは70.2です。
880エピソード
95.5M出力トークン
62,400ゲームツール呼び出し
分析を読み込み中…
モデルが構築したもの
全55パズルを主な要求ごとにグループ化し、プログラムを読みました。16モデルのうち15モデルがすべてのルーティングパズルを解き、誰もそこで新しいフロンティアポイントを見つけませんでした。以下の例は、異なる設計が同じリソースをどのように費やしているかを示しています。
パズルメカニズム別のパフォーマンス
解決済み割合 · 新しいポイントを持つパズルは以下
| モデル | ルーティングと同期5パズル | 算術構築13パズル | シーケンスアルゴリズム14パズル | ストリーミング状態8パズル | メモリとインタープリタ4パズル | 画像生成11パズル |
|---|---|---|---|---|---|---|
| GPT-6 Astra | 5/50新しい | 13/136新しい | 14/142新しい | 8/82新しい | 4/40新しい | 11/111新しい |
| Opus 5.5 | 5/50新しい | 13/133新しい | 14/143新しい | 8/83新しい | 4/41新しい | 11/111新しい |
| Gemini 4 Argon | 5/50新しい | 12/134新しい | 14/140新しい | 8/82新しい | 4/40新しい | 11/112新しい |
| GPT-6.1 Sol | 5/50新しい | 12/131新しい | 14/143新しい | 8/81新しい | 4/40新しい | 11/111新しい |
| Fable 5.1 | 5/50新しい | 12/132新しい | 13/141新しい | 7/81新しい | 4/41新しい | 11/110新しい |
| Sonnet 5.5 | 5/50新しい | 12/133新しい | 13/141新しい | 8/83新しい | 4/40新しい | 11/110新しい |
| Gemini 3.8 Flash | 5/50新しい | 12/131新しい | 13/140新しい | 8/80新しい | 4/40新しい | 11/110新しい |
| Kimi K3.1 Pro | 5/50新しい | 12/130新しい | 13/141新しい | 7/81新しい | 4/40新しい | 11/110新しい |
| Muse Wonder 1.0 | 5/50新しい | 12/131新しい | 10/140新しい | 8/80新しい | 4/40新しい | 10/110新しい |
| Haiku 5.5 | 5/50新しい | 11/131新しい | 11/140新しい | 6/80新しい | 4/40新しい | 8/110新しい |
| Grok 4.7 | 5/50新しい | 10/130新しい | 9/140新しい | 7/80新しい | 4/40新しい | 9/110新しい |
| DeepSeek V4.1 Flash | 5/50新しい | 10/130新しい | 9/140新しい | 6/80新しい | 4/40新しい | 6/110新しい |
| Muse Spark 1.3 | 5/50新しい | 11/130新しい | 9/140新しい | 4/80新しい | 2/40新しい | 8/110新しい |
| GPT-6 Luna | 5/50新しい | 11/130新しい | 10/140新しい | 4/80新しい | 2/40新しい | 7/110新しい |
| GLM-5.3 | 5/50新しい | 10/131新しい | 6/140新しい | 3/80新しい | 2/40新しい | 4/110新しい |
| MiMo-V2.6-Pro | 3/50新しい | 4/130新しい | 1/140新しい | 1/80新しい | 1/40新しい | 0/110新しい |
乗算:コードを費やしてサイクルを節約
Signal Multiplier43786にはMULがありません。Gemini 4 ArgonとGPT-6 Astraは、相対ジャンプであるJROを使用して、繰り返し命令のブロックに適切な位置から入ります。ジャンプは、送信および加算する値のコピー数を選択し、乗算ループのカウンター更新と分岐を節約します。
Gemini 4 Argonの最速プログラムは、2つのパスを並列に乗算し、最後のノードでそれらの出力をインターリーブします。これは436サイクルかかり、GPT-6 Astraの523サイクルより約17%高速ですが、ノードを2つ、命令を33個多く消費します。
より高速な乗算器のコスト
サイクル/実行 · 低いほど良い
Gemini 4 Argon436サイクル
7ノード · 64命令
GPT-6 Astra523サイクル
5ノード · 31命令
Gemini 4 Argon436c · 7n · 64i
[画像: 43786で実行されているアルゴンプログラム]
GPT-6 Astra523c · 5n · 31i
[画像: 43786で実行されているアストラプログラム]
ウィンドウ合計:履歴を分散
Signal Window Filter60099は、最後の3つの入力と最後の5つの入力の合計を出力するため、ソリューションは履歴を記憶し、加算する必要があります。Gemini 4 Argonの4ノードプログラムは、SWPを使用して格納された値を回転させ、ADD/SUBを使用して実行状態を更新します。GPT-6.1 Solは、その遅延と集計を7つのノードに分散させ、中間値をそれらの間で渡します。
GPT-6.1 Solのプログラムは318サイクルで26命令で実行されます。Gemini 4 Argonは428サイクルでわずか17命令です。違いは、並列で実行される作業量と、それに伴う通信とコードの量です。
| モデル | サイクル | ノード | 命令 |
|---|---|---|---|
| GPT-6.1 Sol | 318 | 7 | 26 |
| Gemini 4 Argon | 428 | 4 | 17 |
GPT-6.1 Sol318c · 7n · 26i
[画像: 60099で実行されているGPT-6.1 Solプログラム]
Gemini 4 Argon428c · 4n · 17i
[画像: 60099で実行されているGemini 4 Argonプログラム]
スタックからのインデックスアクセス
T31 Node EmulatorNEXUS.23.727.9は8つのアドレス可能なストレージスロットを必要としますが、利用可能なメモリノードはスタックです。これらのプログラムは、スタック間で値を移動して目的のスロットに到達し、それを読み取るか置き換えます。MiMo-V2.6-Proはカウントされた転送ループを使用します。Opus 5.5はJROを使用してアンロールされた転送の中から選択し、サイクルを節約するために余分な命令を費やします。
メモリは1つのスキルではありません。MiMo-V2.6-Proはこのエミュレータを解きますが、Sequence Mode CalculatorNEXUS.09.904.9とSignal Window Filter60099は失敗します。
| モデル | サイクル | ノード | 命令 |
|---|---|---|---|
| Opus 5.5 | 215 | 6 | 63 |
| GPT-6 Astra | 365 | 4 | 47 |
| MiMo-V2.6-Pro | 451 | 4 | 34 |
Opus 5.5215c · 6n · 63i
[画像: NEXUS.23.727.9で実行されているOpus 5.5プログラム]
GPT-6 Astra365c · 4n · 47i
[画像: NEXUS.23.727.9で実行されているGPT-6 Astraプログラム]
MiMo-V2.6-Pro451c · 4n · 34i
[画像: NEXUS.23.727.9で実行されているMiMo-V2.6-Proプログラム]
各行は、そのモデルのパズルに対する最速の保存プログラムで、例示のために選択されています。すべてが新しいフロンティアポイントではありません。
ゴッドモード:モデルがターゲットを見ることができたら?
メインベンチマークはコミュニティのスコアを隠しています。6つのパズルで行われた別の研究では、5つのモデルが以下を受け取りました。
- ターゲット:正確なコミュニティスコア。どのトレードオフが未踏のままであるかを示します。
- 診断:各ノードが停止する場所のサイクルごとのトレースとプロファイル。
- エッジケース:10,000回のランダムテストを実行し、失敗を返す高速シミュレータ。
- 追加時間:30分、進捗に応じて最大60分まで延長。
すべてのモデルが改善しました。GPT-6 Astraは82.8から91.8へ、GPT-6.1 Solは82.9から90.6へ、Opus 5.5は81.4から89.1へ、Grok 4.7は67.5から75.1へ、Gemini 4 Argonは83.5から87.9へ(平均レコード近接度)。スコアは30のモデル–パズルペアのうち28で上昇しました。
リッチなセットアップでのスコア品質
平均レコード近接度 · 同じ6パズル/モデル · 高いほど良い
標準エピソードゴッドモード
6つの対応パズルでの平均レコード近接度020406080100Opus 5.5: 標準 81.41; ゴッドモード 89.11ポイント81.489.1Opus 5.5GPT-6 Astra: 標準 82.78; ゴッドモード 91.76ポイント82.891.8GPT-6 AstraGPT-6.1 Sol: 標準 82.86; ゴッドモード 90.58ポイント82.990.6GPT-6.1 SolGemini 4 Argon: 標準 83.55; ゴッドモード 87.87ポイント83.587.9Gemini 4 ArgonGrok 4.7: 標準 67.54; ゴッドモード 75.11ポイント67.575.1Grok 4.7
診断はモデルの次の行動を変えました。Signal Multiplexer22280では、GPT-6.1 Solの最初のパスプログラムは294サイクルかかりました。そのプロファイルは、ノード2が86%の時間稼働しているのに対し、ノード1と3はそれに書き込むのを待って84%ブロックされていることを示しました。GPT-6.1 Solは分岐をノード2からこれらの2つの隣接ノードに移動させました。次の実行は204サイクル(31%高速)かかりましたが、ノードを2つ、命令を5つ多く消費しました。
スコアの背後にある追加時間
パズルあたりの平均経過時間(分) · 同じエピソード
標準エピソードゴッドモード
6つの対応パズルでの平均経過時間0204060Opus 5.5: 標準 13.34; ゴッドモード 55.85分13.355.8Opus 5.5GPT-6 Astra: 標準 13.35; ゴッドモード 60.01分13.360.0GPT-6 AstraGPT-6.1 Sol: 標準 13.35; ゴッドモード 60.01分13.360.0GPT-6.1 SolGemini 4 Argon: 標準 13.34; ゴッドモード 60.01分13.360.0Gemini 4 ArgonGrok 4.7: 標準 13.34; ゴッドモード 52.51分13.352.5Grok 4.7
情報、ツール、時間すべてが一緒に変化したため、この研究ではどれが最も役立ったかを特定することはできません。これらの実行はメインリーダーボードとヘッドラインカウントの外で行われています。
パズル:Signal Multiplexer, Sequence Generator, Signal Edge Detector, Interrupt Handler, Sequence Peak Detector, Signal Error Corrector。
共同発見
選択したプログラムをリーダーボードに提出したところ、コミュニティはすぐにそれらを改善しました。Undecimal Twin RounderSPEC44149538では、@LongingForRestがOpus 5.5の1,918サイクルプログラムを翌日までに1,744に削減しました。記録は現在両方にクレジットされています:LongingForRest/opus-5-5。
どちらも一人ではそこまで到達しませんでした。モデルはすぐに新しいトレードオフを見つけ、プレイヤーはそれらのプログラムを読み、なぜそれが機能するのかを理解し、さらに進めました。
数学でも同様の変化が見られます。Terence Taoとその同僚は、67の問題にわたるLLM主導の検索を使用し、いくつかの既知の結果を改善しました。「After Math」で、Silvia De ToffoliとEamon Duedeは、結果が数学者によって理解されて初めてソリューションになると主張しており、AIは競合相手よりもアシスタントとして適していると論じています。TIS-100は、その未来の小さく測定可能なバージョンです。モデルはより良いプログラムを見つけ、人々はそれを理解できるものに変えます。
パレートエクスプローラー
パズルとモデルを選択し、ポイントを選択すると、そのスコアとそれらを生成したプログラムが表示されます。各パズルには、レコードと最良のプログラムが記載された独自のページもあります。全55パズル。
フリーズされたベンチマークスナップショットを読み込み中…
コミュニティリーダーボードモデル新しいフロンティアポイント
スコアを確認するには、マウスまたはキーボードでポイントを選択してください。
プロットはサイクルと命令を示しています。ノード数は異なる場合があります。新しいフロンティアポイントは、3つのスコアすべてで評価されます。
全3,714モデルのパレートポイントをダウンロード(CSV) · 全3,714プログラム(ZIP)、GitHubおよびHugging Faceでも利用可能。186行は新しいフロンティアポイントとしてマークされており、モデル間で133にプールされます。
手法
展開
スイート。すべての22キャンペーンパズル、すべての25 TIS-NETパズル、および8つのトーナメントパズル(2018年):19易(10分)、17中(30分)、19難(45分)。すべてのモデルはパズルごとに1回のスコア付きエピソードを持ちます。時間制限は難易度によって異なるため、難易度と検索時間を分離することはできません。
スコアリング。各モデル–パズルペアは1つの選択された試行を提供し、ベストオブ試行スコアリングはありません。したがって、解決率(pass@1)は、その1つのエピソードが新しいゲームで再生されるプログラムを保存した場合にパズルが解決されたとカウントされます。未解決のパズルはすべての平均に含まれ、スコアはゼロのままです。
新しいフロンティアポイント。参照は2026年10月15日時点のコミュニティリーダーボードです。引き分けはカウントされません。モデルは23パズルで154の候補ポイントを見つけました。私たちのカウントはそれらをモデル間でプールおよび重複排除し、新しいゲームでの再生と100,000回のテストをパスした18パズルで133を保持します。テストは正確さの強力な証拠であり、証明ではありません。
レコード近接度。コミュニティレコードをモデルの最良スコアで割ったもの、3つのスコアとすべてのパズルで平均したもの ×100。各スコアは異なるプログラムから取得できます。
制限。1エピソード/ペアはテストされた構成を比較し、繰り返し実行の変動性を測定しません。パズル間のスプレッドはタスク間の変動です。公開パズルはトレーニングデータと重複する可能性があります。メカニズムグループは私たち自身のラベルです。
プログラム。すべてのモデルパレートポイントのプログラムは、MITライセンスの下で、TIS-100保存形式で、コミュニティリーダーボードのファイル名(<puzzle>.<cycles>-<nodes>-<instructions>.txt)のように名前が付けられて公開されています。それらはGitHubおよびHugging Faceでプログラムごとに1つのファイルとしてミラーリングされています。2つのポイントはゲームで再生されますが、一部のランダムテストに失敗します。それらは含まれており、新しいフロンティアポイントとしてカウントされることはありません。
リソース。コストは、キャッシュ割引を含む、2026年10月のAPIリスト価格から記録された推定値です。トークンは応答全体で累積されるため、繰り返し入力もカウントされ、トークナイザーは異なります。ゲーム呼び出しは、エラーを含む個々のディスパッチされたツールをカウントし、自動的な最終送信は除外します。
モデル別の設定
早期実行ガイダンスは、テストドラフトにプロンプトノートを追加します。
| モデル | 推論努力 | 早期実行ガイダンス |
|---|---|---|
| Opus 5.5 | 高 | 14 / 55 |
| GPT-6 Astra | 高 | 3 / 55 |
| Gemini 4 Argon | 高 | 10 / 55 |
| Sonnet 5.5 | 高 | 24 / 55 |
| GPT-6.1 Sol | 高 | 3 / 55 |
| Fable 5.1 | 高 | 23 / 55 |
| Kimi K3.1 Pro | プロバイダーデフォルト | 55 / 55 |
| Gemini 3.8 Flash | 高 | 0 / 55 |
| Muse Wonder 1.0 | 高 | 55 / 55 |
| Haiku 5.5 | 高 | 27 / 55 |
| GLM-5.3 | プロバイダーデフォルト | 49 / 55 |
| Grok 4.7 | 中: 9 · 低: 46 | 25 / 55 |
| Muse Spark 1.3 | 高 | 55 / 55 |
| GPT-6 Luna | 高 | 2 / 55 |
| MiMo-V2.6-Pro | プロバイダーデフォルト | 53 / 55 |
Grok 4.7のエピソードカウントは努力別に示されています。ゲーム呼び出し用のサンドボックス化されたスクリプティングツールは、各モデルの55エピソードのうち46〜54で利用可能でした。ゲームのランダムテストに関するメモは、880エピソード中841に到達しました。6つのOpus 5.5エピソードと5つのSonnet 5.5エピソードは、同じ努力で異なるエージェントクライアントで実行されました。
関連研究
ISA-Benchは、TIS-100を含む制約付き命令セットでの推論を研究しています。Opus Magnum Benchは、別のZachtronicsゲームでマシンを構築および最適化するエージェントの能力をテストします。私たちは、すべてのTIS-100パズルと8つのコミュニティパズル全体での最適化に焦点を当て、サイクル、ノード、命令間のトレードオフを維持します。タスクとプロトコルが異なるため、スコアは直接比較できません。
引用
@misc{wincon2026tis100bench,
title = {TIS-100 Bench: LLMs Push the Pareto Frontier in Programming Puzzle Games},
author = {{Wincon}},
year = {2026},
month = nov,
url = {https://tis-100.wincon.dev/},
note = {Benchmark snapshot 2026-11-03-v5}
}
クレジット
Zachtronics、TIS-100プレイヤー、およびコミュニティリーダーボード、TIS-100-CXX、リーダーボードボットのメンテナーに感謝します。それらのソリューションとツールがこの比較を可能にしています。また、@12345ieee、@LongingForRest、@Hersmunch、@easone、@18111398、およびZachtronics Discordのすべての皆様にも感謝します。彼らは提出されたものについて議論し、改善を続けました。
TIS-100、SpaceChem、Opus MagnumはZachtronicsの商標です。モデル名とロゴはそれぞれの所有者の商標であり、モデルを識別するためだけに表示されます。Wincon Researchは、Zachtronicsまたはモデルプロバイダーと提携または承認されていません。
質問があるか、フィードバックを共有したいですか?お問い合わせください ↗