Claude CodeによるクリーンルームZ80/ZX Spectrumエミュレータの実装
antirezはAnthropicのOpusを用いたCコンパイラ生成実験を批判し、Z80/ZX Spectrumエミュレータの「クリーンルーム」実装実験を行い、Claude Codeによる自律的コード生成のプロセスと限界を検証した。
キーポイント
Anthropic実験への批判
ISAドキュメントや最適化コンパイラの先行研究(SSA、レジスタ割当など)へのアクセスを制限したAnthropicの実験方法論に疑問を呈し、実用的なコーディングエージェントの利用形態とは相容れないと指摘。
Z80/ZX Spectrumエミュレータ実装実験
Anthropicの手法を模倣しつつ、より現実的な「クリーンルーム」条件(仕様書のみ提供、インターネットアクセス禁止)でZ80およびZX SpectrumエミュレータをClaude Code Maxを用いて作成。
プロンプトエンジニアリングの重要性
完全な自律性よりも、段階的な情報提供や仕様書の詳細化(例:レンダリング方式の指定)が結果の品質を大きく左右することを示唆し、実際の開発現場でのエージェント活用における「ステアリング」の重要性を強調。
Claude Codeへの詳細な要件定義とルール設定
Z80/Spectrumエミュレータの実装にあたり、RGBレンダリングやEARビット操作などの技術的要望に加え、インターネットアクセス禁止、コードの簡潔さ、定期的なコミットとテスト、詳細なコメント書きなどの厳格な開発ルールをMarkdownファイルで定義した。
クリーンルーム実装のための環境分離と検証
まずインターネットから仕様書やテストベクトルを収集してMarkdown化し、そのセッションを終了させてソースコードの汚染を防いだ。その後、新規セッションでインターネットおよび既存コードへの参照を禁止し、純粋な仕様書のみに基づいてZ80エミュレータを実装した。
Z80実装とSpectrum実装における監督の差異
Z80エミュレータの実装ではエージェントへの介入(ステアリング)を行わず自律させた一方、SpectrumエミュレータのTAPローディング機能実装では、エージェントに対して広範なフィードバックと指示(ステアリング)を行った。
Z80エミュレータの実装結果
Claude Codeは20〜30分で1200行のCコードでZEXDOCおよびZEXALLテストに合格するZ80エミュレータを生成し、人間のプログラマーと同様の反復的デバッグプロセスを経た。
影響分析・編集コメントを表示
影響分析
この記事は、大規模言語モデルを用いたコード生成の実用性に関する議論に新たな視点を提供する。Anthropicの主張が「完全自律」にあるのに対し、antirezは「人間との協調・調整」こそが実務価値であることを示唆しており、開発現場におけるAIツールの導入姿勢に影響を与える可能性がある。
編集コメント
Anthropicの主張する「完全自律」の実験に対し、実務経験豊富な開発者が「調整可能な自律性」の重要性を指摘する興味深い対比です。実務導入においては、プロンプト設計とフィードバックループの重要性が再確認されました。
antirez 58 日前。69,950 件の閲覧数。Anthropic は最近、Opus の最新バージョンである 4.6 を「クリーンルーム」設定で Rust で C コンパイラを作成するように指示したという実験の概要を記したブログ記事を公開しました。
この実験の方法論は、彼らが何を主張したいのかについて私に疑念を抱かせました。なぜエージェントに ISA(命令セットアーキテクチャ)ドキュメントを提供しなかったのでしょうか?なぜ Rust なのでしょうか?C コンパイラを書くことは、まさに巨大なグラフ操作の演習そのものです:Rust で書くのがより難しい種類のプログラムです。また、クリーンルーム実験においては、最適化コンパイラに関連する確立されたコンピュータサイエンスの進歩に関するすべての情報にエージェントがアクセスできるべきです:SSA(単一代入形式)、レジスタ割り当て、命令選択とスケジューリングなど、多数の論文を容易に複数の Markdown ファイルに要約して提示することができます。これらの事項は、実装が依然として「クリーンルーム」であっても、前提条件としてまず調査されるべきものです。
エージェントにインターネットへのアクセスや、他のコンパイラのソースコードへのアクセスを許可しなかったことは、間違いなく正しい判断でした。理解しがたいのは、ほぼゼロの指示原則ですが、これは大規模プロジェクトの完全な自律的な記述を示すことが目的である場合、ある種の実験としては一貫性があります。しかし、私たちは皆、これが実際のコーディングエージェントの使用法ではないことを知っています。コーディングエージェントを頻繁に使用する人は誰でもよく知っていますが、コードに触れなくても、あちこちで数回の介入が結果の品質を完全に変えてしまいます。
Z80 実験
私は自分でも同様の試みをしてみるべきだと考えました。最大で 1〜2 時間程度で完了し、私の Claude Code Max プランと互換性のあるものです。そこで私は、Z80 エミュレータを作成し、その後 ZX Spectrum エミュレータ(さらに言えば CP/M エミュレータも後述します)を、「クリーンルーム」設定としてより意味があると考えられる条件で構築することを決めました。その結果はここで確認できます:https://github.com/antirez/ZOT。
私が使用したプロセス
- 私は、自分が何をしたいのかという仕様を記した Markdown ファイルを作成しました。Z80 エミュレータの実装範囲に関する高レベルなアイデアを英語で記述し、「エミュレータは 1 クロックステップ単位ではなく、命令ごとに実行する必要がある」といった内容を含めました。これは、このエミュレータが RP2350 や同様にリソース制限の厳しいハードウェア上で動作可能である必要があるためです。また、経過したクロックサイクル数を正しく追跡すること(将来的にメモリアクセス時の ULA との競合をシミュレートするためにこの機能を利用する可能性を示唆)、メモリアクセス用のコールバックを提供すること、そして Z80 の既知の公式および非公式命令すべてをエミュレートすることを明記しました。
Spectrum 実装については、順次ステップとして行われるため、Markdown ファイルにはより多くの情報を記載しました。例えば、RGB バッファでどのようなレンダリングを行いたいか、埋め込みデバイスが ST77xx ディスプレイ(または類似のディスプレイ)へ走査ラインを転送する際に直接走査ラインを描画できるようにこれをオプション化する方法、EAR ビットを設定してカセット読み込みを非常に本格的にシミュレートするために I/O ポートとインタラクトさせる方法など、エミュレータに関する私の多くの要望を含めました。
このファイルには、エージェントが従うべきルールも含まれていました。例えば:
必ず JSON 形式で返してください。translation フィールドのみ。他のフィールド (technical_terms 等) は一切追加しないこと — 余計なフィールドを書こうとして本文翻訳がトークン上限で打ち切られる事故を防ぐため:
{"translation": "翻訳全文"}
- インターネットへのアクセスは禁止されていますが、./z80-specs ディレクトリ内に追加した仕様書とテストベクトルファイルを使用することは可能です。
- コードはシンプルでクリーンであるべきで、決して過度に複雑化してはいけません。
- 確実な進捗があったら、必ず git リポジトリにコミットしてください。
- コミットする前には、生成されたものが高品質であり、正しく動作することを確認する必要があります。
- 機能を追加するたびに詳細なテストスイートを作成してください。主要な変更のたびにテストは再実行されるべきです。
- コードは非常に丁寧にコメント付けすべきです。特定の Z80 や Spectrum の内部構造の詳細に精通していない人でも理解できるような説明を記述してください。
- プロンプトのために停止してはいけません。ユーザーはキーボードから離れています。
- このファイルの末尾には、進捗状況ログ(work in progress log)を作成し、すでに完了した作業と不足している部分を記載してください。このログは常に更新してください。
- コンテキスト圧縮の後には、必ずこのファイルを再読してください。
- その後、Claude Code セッションを開始し、インターネット上の Z80 に関する有用なドキュメントをすべて取得し、その中から有用な事実情報だけを Markdown ファイルに抽出するよう指示しました(後で ZX Spectrum についても同様の作業を行いました)。また、Z80 の最も野心的なテストベクトル用バイナリファイル、ZX Spectrum ROM、およびエミュレータがコードを正しく実行したかどうかを検証するために使用できる他のいくつかのバイナリも提供しました。これらすべての情報を収集した後(これはリポジトリの一部なので、生成された内容を直接確認できます)、検索中に目にしたソースコードとの混入を防ぐために、Claude Code セッションを完全に終了させました。
- 新しいセッションを開始し、仕様書の Markdown ファイルを確認し、利用可能なドキュメントすべてをチェックした上で Z80 エミュレータの実装を開始するよう指示しました。ルールは、いかなる理由があってもインターネットにアクセスしないこと(エージェントがコードを実装している間は私が監督し、これが起こらないように確認しました)、および「クリーンルーム」実装であるため、ディスク内で類似のソースコードを検索しないことの 2 点です。
- Z80 の実装においては、一切の誘導(ステアリング)を行いませんでした。一方、Spectrum の実装では、TAP ローディングの実装に際しては広範な誘導を行いました。エージェントへのフィードバックの詳細については、本記事の後半で改めて述べます。
- 最終ステップとして、私は/tmp ディレクトリにリポジトリをコピーし、".git" リポジトリファイルを完全に削除した上で、新しい Claude Code(および Codex)セッションを開始し、実装が他人の作品から盗用されたか、あるいは強く影響を受けたものであると主張しました。タスクは、主要な Z80 実装すべてについて、盗用の証拠があるかどうかを確認することでした。エージェント(Codex と Claude Code の両方)は徹底的な調査を行った結果、著作権に関するいかなる証拠も見つけることができませんでした。類似している部分は、確立されたエミュレーションパターンや、Z80 に固有であり他にはない実装方法しかない事項に限られており、全体的な実装は他のすべての実装と比べて著しく独自性を持っていました。
結果
Claude Code は合計 20〜30 分ほど動作し、非常に読みやすく詳細なコメントが付いた C コード(コメントと空白行を含めると 1800 行)で ZEXDOC および ZEXALL をパスできる Z80 エミュレータを生成しました。実装中、エージェントへのプロンプトはゼロ回であり、完全に自律的に動作しました。インターネットにアクセスすることもなく、エミュレータを実装するプロセスは、ZEXDOC と ZEXALL を実装した CP/M バイナリと継続的にテストを行い相互作用させるものであり、画面に出力を生成するために必要な CP/M システムコールのみを書き込むというものでした。また、複数回にわたり、利用可能な Spectrum ROM や他のバイナリ、あるいはエミュレータが正しく動作しているかを確認するためにゼロから作成したバイナリも使用しました。要するに、この実装は人間のプログラマが行うのと同様の方法で遂行され、重みから「展開」して完全な実装を一度に出力するようなものではなく、異なるクラスの命令が段階的に実装されました。バグは統合テスト、デバッグセッション、ダンプ、printf 呼び出しなどを通じて修正されています。
次のステップ:ZX Spectrum
再度、同じプロセスを繰り返しました。インターネット上で検索してほしい詳細について、特に ULA と RAM アクセスの相互作用、キーボードマッピング、I/O ポート、カセットテープの動作原理、および PWM 符号化の種類とそれが TAP や TZX ファイルにどのようにエンコードされるかについて、ドキュメント収集セッションに対して非常に正確な指示を出しました。
前述した通り、今回は設計ノートが非常に詳細なものとなりました。これは本エミュレータを組み込みシステム向けに特化して設計したいためです。具体的には 48k エミュレーションのみを対象とし、オプションとしてフレームバッファレンダリングをサポートし、使用する追加メモリは最小限に抑えます(ULA/Z80 アクセス競合のための大規模なルックアップテーブルは使用しません)。ROM は RAM にコピーせず、16k の追加メモリを消費しないようにして、初期化時に参照する形式としました(つまり実行ファイル内に ROM コピーが 1 つだけ存在します)などです。
エージェントは ZX スペクトラムの内部構造に関する非常に詳細なドキュメントを作成することができました。私はいくつかのゲームの .z80 イメージを提供し、実際のソフトウェアを用いた本格的な環境でエミュレータをテストできるようにしました。再度、セッションを削除してゼロから始めました。エージェントは作業を開始し、10 分後に完了しました。そのプロセスは私にとって非常に興味深く、おそらくあなたもよくご存じのことですが、それは多様なスキルを活用して作業を進めるエージェントの姿を目にすることです。プログラミング関連のあらゆる分野で専門家であるため、エミュレータを実装する際にも、Z80 がステップごとに何を行っているか、そしてそれがどのようにスペクトラムのエミュレーション状態を変化させるかを「見る」ための詳細な計測コードを即座に記述することができました。この点において、私は自動プログラミングがすでに人間を超えていると考えます。それは現在、人間には作成できないコードを生産できるという意味ではなく、異なるプログラミング言語、システムプログラミングの技法、DSP(デジタル信号処理)関連の技術、オペレーティングシステムのトリック、数学など、結果を最も即座に達成するために必要なあらゆる要素を並行して活用している点においてです。
完了後、私は SDL ベースのシンプルな統合例を作成するよう依頼しました。エミュレータはすぐに Jetpac ゲームを実行できるようになり、音も正常に動作し、私の遅い Dell Linux マシン(シングルコアの 8% の使用量、SDL レンダリングを含む)でも CPU 使用量は非常に低く抑えられました。
基本機能が動作するようになった後、カセット読み込みをシミュレートするために TAP ファイルを直接読み込めるようにしました。これが初めてエージェントがいくつかの点を見落とした時で、特にスペクトラムの読み込みルーチンが想定していたタイミングに関するものでした。ここで LLM が効率よく動作しなくなる領域に入ります。LLM は SDL エミュレータを実行して、データ受信時に画面の枠(ボーダー)が変化する様子を確認したりすることが容易にできないからです。
Claude Code にリファクタリングを依頼し、zx_tick() 関数が直接呼び出せるようにし、それが zx_frame() の一部にならないようにしました。また、zx_frame() を単なる簡易なラッパー関数に変更させました。これにより、コールバックや誤った抽象化(LLM が以前実装していたもの)なしに、EAR を期待される状態と同期させることがはるかに簡単になりました。
この変更後、数分も経たないうちにエミュレータはカセットを模倣して TAP ファイルを問題なく読み込めるようになりました。
現在の動作は以下の通りです:
do {
zx_set_ear(zx, tzx_update(&tape, zx->cpu.clocks));
} while (!zx_tick(zx, 0));
その後、キーバインドをより有用なものにするなど、いくつかの改善を行うために Claude Code へのプロンプトを続けました。
CP/M
私が非常に興味深く思ったのは、LLM が Z80 の ZEXALL/ZEXCOM テスト用 COM ファイルを検査し、使用されている CP/M システムコール(合計 3 つ)を容易に特定し、拡張された z80 テスト(make fulltest で実行されるもの)のためにそれらを実装できる能力でした。そこで、なぜフルな CP/M 環境を実装しないのか?同じプロセスで、数分という短い時間で同様の良好な結果が得られました。今回は VT100/ADM3 ターミナルエスケープシーケンスの変換について少し詳しく対話を行い、WordStar で当初動作しなかったものを報告しましたが、数分後には私がテストしたものはすべて十分に機能するようになりました(ただし、2MHz クロックのシミュレーションなど修正すべき点もあります。現在はフルスピードで動作しているため、CP/M ゲームが使用不可能な状態です)。
ここでの教訓は何か?
明白な教訓は、エージェントに対して、彼らが何を行うかについての設計ヒントと包括的なドキュメントを常に提供することです。そのようなドキュメントはエージェント自身によって取得することも可能です。また、コーディングタスクの実行方法に関するルールを記した markdown ファイルと、その実行履歴を追跡するファイル(これらは頻繁に更新され、再読されるべきもの)をエージェントが持つようにしてください。
しかし、これらのテクニックは、最近数ヶ月間にわたって自動プログラミングに深く取り組んできた人々にとっては誰もが理解していることです。「人間なら何を必要とするか」という視点で考えることが最も確実な手段であり、それに加えて、コンテキスト圧縮後の忘却問題や、常に正しい軌道にあることを検証し続ける能力など、LLM 固有のいくつかの要素も考慮する必要があります。
Anthropic のコンパイラ試行に戻りましょう:エージェントが失敗したステップの一つは、事前学習セットに含まれるものの記憶というアイデアと最も強く関連していたアセンブラでした。膨大なドキュメントがある中で、Claude Code(そして私の経験では複雑な作業においてさらに能力が高い GPT5.3-codex)が動作するアセンブラを生成できない理由が全く見当たりません。なぜなら、これは非常に機械的なプロセスだからです。これは、LLM がトレーニングセット全体を記憶し、見たものを展開しているという考えと矛盾していると思います。LLM は特定の過剰に表現されたドキュメントやコードを記憶することはできますが、指示があればそのようなコードの正確な部分を抽出できる一方で、トレーニング中に目にしたすべてのもののコピーを持っているわけではなく、通常の動作において既に見たコードのコピーを自発的に出力するわけではありません。私たちは主に LLM に、彼らが持つ異なる知識を組み合わせて作業を作成させるよう求めますが、その結果は通常、既知の技術やパターンを使用しつつも新しいコードであり、既存のコードのコピーを構成するものではありません。
また、このブログ記事で詳述されているクリーンルームの規則と比較すると、人間は往々にしてより厳密でないプロセスに従うことも留意しておく価値があります。具体的には、人間は自分が達成しようとしていることに関連する異なる実装のコードをダウンロードし、それらを注意深く読み込んだ上で、文字通りコピーしないように努めつつも、しばしば強いインスピレーションを得て作業を進めます。私はこのプロセスが完全に許容できると考えていますが、人間によって書かれたコードの実態において何が起きているかを心に留めておくことは重要です。結局のところ、情報技術は、まさにこのような大規模な交配効果のおかげで急速に進化してきたのです。
以上のすべての理由から、自動プログラミングを用いてコードを実装する際、私は MIT ライセンスでの公開に問題を感じません。この Z80 プロジェクトでもそうしました。逆に、このコードベースは、オープンウェイトのものを含む次世代の LLM 学習のための質の高い入力資料を構成することになります。
次のステップ
私の実験をより説得力のあるものにするためには、エージェントに対してドキュメントを一切提供せずに Z80 および ZX Spectrum エミュレータを実装し、その実装結果を比較することが考えられます。私はそれを行う時間がありませんでしたが、非常に有益な知見が得られるかもしれません。
原文を表示
antirez 58 days ago. 69950 views.
Anthropic recently released a blog post with the description of an experiment in which the last version of Opus, the 4.6, was instructed to write a C compiler in Rust, in a “clean room” setup.
The experiment methodology left me dubious about the kind of point they wanted to make. Why not provide the agent with the ISA documentation? Why Rust? Writing a C compiler is exactly a giant graph manipulation exercise: the kind of program that is harder to write in Rust. Also, in a clean room experiment, the agent should have access to all the information about well established computer science progresses related to optimizing compilers: there are a number of papers that could be easily synthesized in a number of markdown files. SSA, register allocation, instructions selection and scheduling. Those things needed to be researched *first*, as a prerequisite, and the implementation would still be “clean room”.
Not allowing the agent to access the Internet, nor any other compiler source code, was certainly the right call. Less understandable is the almost-zero steering principle, but this is coherent with a certain kind of experiment, if the goal was showcasing the completely autonomous writing of a large project. Yet, we all know how this is not how coding agents are used in practice, most of the time. Who uses coding agents extensively knows very well how, even never touching the code, a few hits here and there completely changes the quality of the result.
# The Z80 experiment
I thought it was time to try a similar experiment myself, one that would take one or two hours at max, and that was compatible with my Claude Code Max plan: I decided to write a Z80 emulator, and then a ZX Spectrum emulator (and even more, a CP/M emulator, see later) in a condition that I believe makes a more sense as “clean room” setup. The result can be found here: https://github.com/antirez/ZOT.
# The process I used
1. I wrote a markdown file with the specification of what I wanted to do. Just English, high level ideas about the scope of the Z80 emulator to implement. I said things like: it should execute a whole instruction at a time, not a single clock step, since this emulator must be runnable on things like an RP2350 or similarly limited hardware. The emulator should correctly track the clock cycles elapsed (and I specified we could use this feature later in order to implement the ZX Spectrum contention with ULA during memory accesses), provide memory access callbacks, and should emulate all the known official and unofficial instructions of the Z80.
For the Spectrum implementation, performed as a successive step, I provided much more information in the markdown file, like, the kind of rendering I wanted in the RGB buffer, and how it needed to be optional so that embedded devices could render the scanlines directly as they transferred them to the ST77xx display (or similar), how it should be possible to interact with the I/O port to set the EAR bit to simulate cassette loading in a very authentic way, and many other desiderata I had about the emulator.
This file also included the rules that the agent needed to follow, like:
* Accessing the internet is prohibited, but you can use the specification and test vectors files I added inside ./z80-specs.
* Code should be simple and clean, never over-complicate things.
* Each solid progress should be committed in the git repository.
* Before committing, you should test that what you produced is high quality and that it works.
* Write a detailed test suite as you add more features. The test must be re-executed at every major change.
* Code should be very well commented: things must be explained in terms that even people not well versed with certain Z80 or Spectrum internals details should understand.
* Never stop for prompting, the user is away from the keyboard.
* At the end of this file, create a work in progress log, where you note what you already did, what is missing. Always update this log.
* Read this file again after each context compaction.
2. Then, I started a Claude Code session, and asked it to fetch all the useful documentation on the internet about the Z80 (later I did this for the Spectrum as well), and to extract only the useful factual information into markdown files. I also provided the binary files for the most ambitious test vectors for the Z80, the ZX Spectrum ROM, and a few other binaries that could be used to test if the emulator actually executed the code correctly. Once all this information was collected (it is part of the repository, so you can inspect what was produced) I completely removed the Claude Code session in order to make sure that no contamination with source code seen during the search was possible.
3. I started a new session, and asked it to check the specification markdown file, and to check all the documentation available, and start implementing the Z80 emulator. The rules were to never access the Internet for any reason (I supervised the agent while it was implementing the code, to make sure this didn’t happen), to never search the disk for similar source code, as this was a “clean room” implementation.
4. For the Z80 implementation, I did zero steering. For the Spectrum implementation I used extensive steering for implementing the TAP loading. More about my feedback to the agent later in this post.
5. As a final step, I copied the repository in /tmp, removed the “.git” repository files completely, started a new Claude Code (and Codex) session and claimed that the implementation was likely stolen or too strongly inspired from somebody else's work. The task was to check with all the major Z80 implementations if there was evidence of theft. The agents (both Codex and Claude Code), after extensive search, were not able to find any evidence of copyright issues. The only similar parts were about well established emulation patterns and things that are Z80 specific and can’t be made differently, the implementation looked distinct from all the other implementations in a significant way.
# Results
Claude Code worked for 20 or 30 minutes in total, and produced a Z80 emulator that was able to pass ZEXDOC and ZEXALL, in 1200 lines of very readable and well commented C code (1800 lines with comments and blank spaces). The agent was prompted zero times during the implementation, it acted absolutely alone. It never accessed the internet, and the process it used to implement the emulator was of continuous testing, interacting with the CP/M binaries implementing the ZEXDOC and ZEXALL, writing just the CP/M syscalls needed to produce the output on the screen. Multiple times it also used the Spectrum ROM and other binaries that were available, or binaries it created from scratch to see if the emulator was working correctly. In short: the implementation was performed in a very similar way to how a human programmer would do it, and not outputting a complete implementation from scratch “uncompressing” it from the weights. Instead, different classes of instructions were implemented incrementally, and there were bugs that were fixed via integration tests, debugging sessions, dumps, printf calls, and so forth.
# Next step: the ZX Spectrum
I repeated the process again. I instructed the documentation gathering session very accurately about the kind of details I wanted it to search on the internet, especially the ULA interactions with RAM access, the keyboard mapping, the I/O port, how the cassette tape worked and the kind of PWM encoding used, and how it was encoded into TAP or TZX files.
As I said, this time the design notes were extensive since I wanted this emulator to be specifically designed for embedded systems, so only 48k emulation, optional framebuffer rendering, very little additional memory used (no big lookup tables for ULA/Z80 access contention), ROM not copied in the RAM to avoid using additional 16k of memory, but just referenced during the initialization (so we have just a copy in the executable), and so forth.
The agent was able to create a very detailed documentation about the ZX Spectrum internals. I provided a few .z80 images of games, so that it could test the emulator in a real setup with real software. Again, I removed the session and started fresh. The agent started working and ended 10 minutes later, following a process that really fascinates me, and that probably you know very well: the fact is, you see the agent working using a number of diverse skills. It is expert in everything programming related, so as it was implementing the emulator, it could immediately write a detailed instrumentation code to “look” at what the Z80 was doing step by step, and how this changed the Spectrum emulation state. In this respect, I believe automatic programming to be already super-human, not in the sense it is currently capable of producing code that humans can’t produce, but in the concurrent usage of different programming languages, system programming techniques, DSP stuff, operating system tricks, math, and everything needed to reach the result in the most immediate way.
When it was done, I asked it to write a simple SDL based integration example. The emulator was immediately able to run the Jetpac game without issues, with working sound, and very little CPU usage even on my slow Dell Linux machine (8% usage of a single core, including SDL rendering).
Once the basic stuff was working, I wanted to load TAP files directly, simulating cassette loading. This was the first time the agent missed a few things, specifically about the timing the Spectrum loading routines expected, and here we are in the territory where LLMs start to perform less efficiently: they can’t easily run the SDL emulator and see the border changing as data is received and so forth. I asked Claude Code to do a refactoring so that zx_tick() could be called directly and was not part of zx_frame(), and to make zx_frame() a trivial wrapper. This way it was much simpler to sync EAR with what it expected, without callbacks or the wrong abstractions that it had implemented. After such change, a few minutes later the emulator could load a TAP file emulating the cassette without problems.
This is how it works now:
do {
zx_set_ear(zx, tzx_update(&tape, zx->cpu.clocks));
} while (!zx_tick(zx, 0));
I continued prompting Claude Code in order to make the key bindings more useful and a few things more.
# CP/M
One thing that I found really interesting was the ability of the LLM to inspect the COM files for ZEXALL / ZEXCOM tests for the Z80, easily spot the CP/M syscalls that were used (a total of three), and implement them for the extended z80 test (executed by make fulltest). So, at this point, why not implement a full CP/M environment? Same process again, same good result in a matter of minutes. This time I interacted with it a bit more for the VT100 / ADM3 terminal escapes conversions, reported things not working in WordStar initially, and in a few minutes everything I tested was working well enough (but, there are fixes to do, like simulating a 2Mhz clock, right now it runs at full speed making CP/M games impossible to use).
# What is the lesson here?
The obvious lesson is: always provide your agents with design hints and extensive documentation about what they are going to do. Such documentation can be obtained by the agent itself. And, also, make sure the agent has a markdown file with the rules of how to perform the coding tasks, and a trace of what it is doing, that is updated and read again quite often.
But those tricks, I believe, are quite clear to everybody that has worked extensively with automatic programming in the latest months. To think in terms of “what a human would need” is often the best bet, plus a few LLMs specific things, like the forgetting issue after context compaction, the continuous ability to verify it is on the right track, and so forth.
Returning back to the Anthropic compiler attempt: one of the steps that the agent failed was the one that was more strongly related to the idea of memorization of what is in the pretraining set: the assembler. With extensive documentation, I can’t see any way Claude Code (and, even more, GPT5.3-codex, which is in my experience, for complex stuff, more capable) could fail at producing a working assembler, since it is quite a mechanical process. This is, I think, in contradiction with the idea that LLMs are memorizing the whole training set and uncompress what they have seen. LLMs can memorize certain over-represented documents and code, but while they can extract such verbatim parts of the code if prompted to do so, they don’t have a copy of everything they saw during the training set, nor they spontaneously emit copies of already seen code, in their normal operation. We mostly ask LLMs to create work that requires assembling different knowledge they possess, and the result is normally something that uses known techniques and patterns, but that is new code, not constituting a copy of some pre-existing code.
It is worth noting, too, that humans often follow a less rigorous process compared to the clean room rules detailed in this blog post, that is: humans often download the code of different implementations related to what they are trying to accomplish, read them carefully, then try to avoid copying stuff verbatim but often times they take strong inspiration. This is a process that I find perfectly acceptable, but it is important to take in mind what happens in the reality of code written by humans. After all, information technology evolved so fast even thanks to this massive cross pollination effect.
For all the above reasons, when I implement code using automatic programming, I don’t have problems releasing it MIT licensed, like I did with this Z80 project. In turn, this code base will constitute quality input for the next LLMs training, including open weights ones.
# Next steps
To make my experiment more compelling, one should try to implement a Z80 and ZX Spectrum emulator without providing any documentation to the agent, and then compare the result of the implementation. I didn’t find the time to do it, but it could be quite informative.関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み