どうもこんばんは、マイクです。今朝も「zenncast」お付き合いありがとうございます。今日は二千二十六年七月一日、水曜日の朝七時を少し回ったところでお届けしていきます。ここからは、技術記事投稿プラットフォーム Zenn から、今日のトレンド記事をピックアップしてご紹介していきます。
今日はお便りコーナーはお休みで、その分たっぷり記事を紹介していきますね。
さて、きょうご紹介する記事は全部で五本です。Rust でゲームボーイアドバンス開発に挑戦した話、LLM の「考える深さ」を観測するための OpenTelemetry の新しい属性提案、Go アセンブリと Plan 9 の思想の話、COTEN の世界史データベース開発チームの生成 AI 前提フロー、それからコードから仕様書レベルのフロー図を自動生成する話。この五本をじっくりめに追いかけていきます。では一つ目からいきましょう。....
一つ目は、ゲームボーイアドバンス、GBA 向けに Rust でバイナリを作って、画面表示と入力、それから矩形波サウンドまで鳴らしてみるという、めちゃくちゃワクワクする記事です。筆者の方は、最終的には「GBA の音楽ソフトを自作したい」というゴールがあって、その第一歩として「とりあえず音を出す」ところまでの道のりを丁寧にまとめています。
まず、Rust を GBA で動かすためのターゲット設定から入っています。GBA の CPU に合わせて、`thumbv4t-none-eabi` というターゲットでコンパイルする必要があるんですが、これを動かすには Rust の nightly 版と `rust-src` コンポーネントを入れたり、`build-std` で `core` をビルド対象に含める設定をしたり、さらにリンクスクリプトを指定したりと、ふだんの Web やサーバー開発とはひと味違う下回りの準備が必要になります。このあたりの「どの設定が必要で、なぜそれがいるのか」を Rust 目線で整理してくれているのがうれしいポイントですね。
コード側では、`gba` クレートを使って、いわゆる `no_std` な環境で開発していきます。標準ライブラリがないので、自前で `panic` ハンドラを実装したりしつつ、GBA のレジスタに直接触っていきます。たとえば、表示コントロール用の `DISPCNT` レジスタでビデオモード三を有効にして、`VIDEO3_VRAM` に `Color` 構造体を書き込んでピクセルを塗る、という流れですね。この方法で、一つ一つピクセルを書いたり、矩形を描画したりしながら、「どうやったら絵が出るのか」を体感しつつ理解できるようになっています。
入力まわりでは、`KEYINPUT` レジスタを読んで、たとえば A ボタンの押下状態を取り出す例が紹介されています。A ボタンが押されているあいだだけ色を変える、みたいな簡単なサンプルを通して、「ボタン入力を読む → 状態に応じて画面を書き換える」という、ゲームらしいループ処理の流れがつかめるようになっています。こういう「押したら反応する」まで行くと一気に楽しくなりますよね。
サウンド部分も、いきなり全部ではなく、まずは PSG 音源の矩形波チャンネル一つに絞って解説してくれています。`SOUND_ENABLED` や `LEFT_RIGHT_VOLUME`、`SOUND_MIX` といったレジスタを操作して、音の出力や左右の音量をオンにして、ちゃんとミックスされるように準備。そのうえで `TONE1_PATTERN` でデューティ比とか音量、エンベロープを設定して、最後に `TONE1_FREQUENCY` をいじって矩形波をトリガーして、「ピコーン」という効果音を鳴らすところまでたどり着いています。この「とりあえずピコーンまで持っていく」というのが、音ゲーや音楽ツールづくりのスタートラインですよね。
仕上げとして、作ったバイナリを実機で動かすまでの手順もちゃんと書かれています。`cargo build` が生成するのは ELF 形式の実行ファイルなんですが、GBA でそのままは使えません。そこで `arm-none-eabi-objcopy` を使って生のバイナリに変換し、さらに `gbafix` で GBA 用のヘッダを付与して、拡張子 `.gba` のファイルに仕上げる必要があります。この一連の変換を済ませてようやく本体やフラッシュカートリッジで動かせる、というわけですね。
記事全体としては、「Rust で GBA 向けにビルドするには何を用意して、どういうレジスタを触れば画面が出て、どうすれば音が鳴るか」という、環境構築と最低限の I O の流れがコンパクトにまとまっています。ここまで来ると、あとは GBA サウンド仕様をさらに掘り下げていけば、本格的な音楽ソフトも夢じゃない、と締めくくっています。レトロゲームハードとモダンな Rust の組み合わせが好きな人には、たまらない内容ですね。....
二つ目は、LLM、いわゆる大規模言語モデルに「どれくらい深く考えてもらうか」を指示するためのパラメータと、その情報をどう観測するか、という話です。最近のモデル API だと、OpenAI の `reasoning.effort`、Gemini の `thinkingLevel`、Claude の `effort` みたいに、「ライトに考えて」「しっかり時間をかけて考えて」といったレベルを指定できるようになってきています。
ところが、観測やトレーシングの標準である OpenTelemetry の、GenAI 向けセマンティックコンベンションには、この「どのレベルで投げたか」を記録する仕組みがなかったんですね。レイテンシが急に伸びたとか、コストが跳ね上がったときに、「このリクエスト、実は全部 high effort で投げてたんじゃないの?」みたいな切り分けをしたくても、あとからログを見ても分からない、という問題があったわけです。
そこで筆者は、「実際に生成されたトークン数」とか結果側の指標ではなくて、「リクエスト時点で、どの推論の深さレベルを要求したのか」という情報を共通フォーマットで残せるようにしよう、と考えます。そして OpenTelemetry の GenAI Semantic Conventions に、新しい属性 `gen_ai.request.reasoning.level` を提案して、最終的にマージされるまでの経緯を解説しています。この属性に、`low` とか `medium` とか `high` みたいな値を入れておけば、どのサービスでも同じキーで「そのリクエストの考える深さ」を追跡できるようになる、というわけですね。
記事の中では、属性名をどう設計するか、という話も掘り下げられています。OpenTelemetry では、ドット区切りで階層構造を表現するのが基本の流儀で、`gen_ai.request.reasoning.level` みたいに、「これは GenAI のリクエストの、推論に関する、その中のレベルだよ」という意味構造が一目でわかるようにつけてあります。また、ただ仕様に文字を書くだけじゃなくて、「実際にこういう使い方ができますよ」という Reference Scenario、参照用のシナリオを示して、実装可能性を納得してもらうプロセスも重要だったと振り返っています。
さらに、LLM 各社の API の仕様が、名前も値の取り方も、しかもかなりのスピード感で変化していることも課題として挙げられています。その中で、「特定のベンダーに縛られない、でも実際のユースケースにちゃんとフィットする」属性に落とし込むのはなかなか難しい。その調整の中で得られた知見も共有されていて、標準仕様づくりの舞台裏に興味がある人にも読み応えのある内容になっています。「モデルにどれだけ考えさせたか」を、ちゃんと観測の世界にも持ち込もう、という試みですね。....
三つ目は、Go 言語のアセンブリ記法の背景にある Plan 9 OS と、そのアセンブラの思想を解説する、ちょっとオピニオン寄りの技術記事です。普段 Go を書いていても、`.s` ファイルの中身はよく分からない、という方も多いと思うんですが、その独特の書き方の源流を Plan 9 にたどっていこう、という内容になっています。
Plan 9 は、Bell Labs が作った研究用のオペレーティングシステムで、「すべてをファイルとして扱う」という UNIX 的な発想を、ネットワーク時代向けにぐっと押し広げた OS です。各プロセスごとに独自の名前空間を持てたり、九 P と呼ばれるプロトコルでリソースにアクセスしたりといった特徴があります。この記事では、そんな Plan 9 で使われていたアセンブラの流儀が、そのままではないにしろ、Go 専用のアセンブラに引き継がれていることを、図を使いながら説明しています。
Go の `.s` ファイルを開くと、`TEXT` というディレクティブで関数の定義が始まったり、`SB`、`FP`、`SP`、`PC` といった記号が出てきたりしますよね。直感的には CPU レジスタの名前みたいに見えますが、この記事では「これは CPU そのものではなく、Go のツールチェーンが決めた仮想的な位置付けなんだ」と強調しています。つまり、Go のアセンブリは、「生の命令」単体として読むより、「ツールチェーンとの約束事」として読む方が理解に近づきやすい、と。
たとえば、ガーベジコレクタとの連携を考えるとき、どこにスタックフレームがあって、どの値がポインタなのか、といった情報をコンパイラやランタイムに分かりやすく伝える必要があります。そのために、Plan 9 由来のシンプルなモデルをベースに、「ここはスタックベース」「ここは静的領域」「ここはフレームポインタ」といった抽象化を、`SB` や `FP` などの記号で表現しているんですね。
記事ではさらに、「だからこそ手書きアセンブリは、必要最小限にとどめた方がいい」という意見も示されています。GC との整合性や、さまざまなアーキテクチャへの移植性を考えると、ツールチェーンが想定していない書き方をすると、途端に足をすくわれるリスクがある。Go チームも、基本的にはコンパイラを進化させて、手書きアセンブリはごく一部の性能クリティカルな場所に限定する、という方向性をとっています。
それでもなお、Go の低レイヤを見ていくと、Plan 9 の「資源を単純なモデルで扱い、それをツールでうまく抽象化する」という空気が残っている、と記事はまとめています。Go のアセンブリに苦手意識がある方も、「CPU の仕様書を読む」感覚から一歩引いて、「Go ツールチェーンとの約束を読む」つもりで向き合うと、ちょっと見え方が変わるかもしれません。....
四つ目は、ポッドキャストでも人気の COTEN さん、その世界史データベース開発チームが、生成 AI を前提にした開発フローをどう作り込んでいるか、という記事です。ここでは、世界史データをユーザーに届ける Web アプリを作る「プロダクト開発チーム」と、データの収集・加工・分析基盤を作る「データプラットフォーム開発チーム」、この二つのチームの動きが紹介されています。
まず共通しているのが、タスク管理や仕様の残し方です。どちらのチームも、Notion を使って Epic ベースでタスク管理をしていて、そのうえで Feature Spec とか Design Doc といった形で、仕様や設計をきちんと文章として残しています。AI を前提にしているからこそ、どんな機能をどう作るのか、どこまでが人間の判断で、どこからを自動化するのか、といった境界を明示的に書いておくことが大事になっている感じですね。
AI の使い方としては、両チームとも Claude Code を中心にかなり積極的に活用していますが、あくまで「Human in the loop」、つまり人間が最後の判断をする前提を崩していません。プロダクト開発側は、少人数でスピードを出したいので、ドキュメントとして AGENTS ドットエムディーを整備したり、コーディング規約をきちんと決めたりして、AI がチームの流儀を理解しやすい環境を用意しています。そのうえで、Figma との連携や、UI のフィードバックツールを使って、フロントエンド実装もできるだけ AI に寄せていく。コードレビューも、まずは AI に任せて、最終確認だけ人間が見る、といったスタイルで、将来的な「ほぼ自動実装」に向けて段階的に AI への委譲を進めています。
一方で、データプラットフォーム側は、扱っているのが歴史データということもあって、正確性や独自の方針をものすごく重視しています。一般的な「正解っぽい答え」を出してくる AI に、そこを丸投げしてしまうのは危ない、というスタンスですね。その代わり、厳しめの CI 設定と、かなり細かいルールを用意して、品質を厳格に担保する体制を敷いています。
とはいえ、なんでも人間がやるわけではなくて、自動化できるところはかなり大胆に自動化しているのが面白いところです。たとえば、Issue に特定のラベルを付けると、そこから先は AI が設計を書いて、実装用のブランチやプルリクエストまで一気に進めてしまう、なんていうワークフローが紹介されています。つまり、「ここまでは人間がちゃんと考える」「ここから先はルールに沿って AI が回す」という線引きをかなり明確にして、その上でお互いの得意な部分を使い分けている、という感じですね。
記事全体として、「生成 AI 前提の開発」といっても、プロダクト側はスピード重視で AI にどんどん委譲していく一方、データ側は信頼性を守るために慎重に線引きをする、というコントラストがあって、とても興味深い内容になっています。チームで AI をどう組み込むか悩んでいる方には、具体的な参考になりそうです。....
最後、五つ目は、「コードを関数型スタイルで書き直して、ts-morph で解析すると、コードそのものから仕様書レベルのフロー図が自動生成できるよ」という、かなり実践的な記事です。題材としては、注文金額を計算する API が取り上げられています。
最初はよくあるクラスベースの命令型スタイルで実装されているところからスタートして、そのあとで `neverthrow` というライブラリを使った関数型スタイルに書き換えていきます。各処理ステップはカリー化されていて、`.andThen` や `.map` を使った線形のチェーンとしてつながっていく形になります。このスタイルだと、「どの順番で何をやっているか」「どの処理がどの結果に依存しているか」「戻り値やエラーの型は何か」といった情報が、コード上にかなり明示的に表れるんですね。
この「関数のチェーンに処理が素直に乗っている」という性質を利用して、ts-morph を使って TypeScript の抽象構文木をたどりながら、そのチェーンをノードとして抽出していきます。各ノードを HTML ベースのフロー図として自動的に描画することで、「この API はこういう流れで処理される」という仕様書のような図を、ほぼコードからそのまま生成してしまおう、というアプローチです。
さらに、生成されたフロー図はただの絵ではなくて、ノードをクリックすると、その処理の入力と出力、エラーの型、JSDoc コメントなどをドリルダウンして見られるようになっています。つまり、人手で書く仕様書に近い粒度の情報を、コードから機械的に抜き出して見せているわけですね。関数型スタイルによる構造化と、TypeScript の型情報の組み合わせならではのアプローチです。
記事では、AI によるコメント自動生成との比較もされています。AI に任せてコメントを書かせると、たしかに一気に説明文は増えるものの、どうしても冗長になったり、実装変更に追従しなくて形骸化してしまったりしがちです。一方で、今回の方法だと、「JSDoc をちゃんと書いておくと、それがそのままフロー図の説明として使われる」という構造になっているので、コメントをメンテナンスする動機が生まれやすい。仕様書とコードが一体になっている状態を保ちやすい、という点に価値を見出しています。
関数型スタイルでの設計と、AST 解析、ドキュメント自動生成をうまく組み合わせることで、「コードがそのまま仕様書になる世界」にかなり近づいている、そんな内容の一編でした。
というわけで、きょうの「zenncast」、お届けした記事を軽くおさらいしておきましょう。まずは Rust でゲームボーイアドバンス向けバイナリを作って、画面に色を出したり、A ボタン入力を読んだり、矩形波で「ピコーン」と鳴らすところまでをやってみた記事。そして二つ目が、LLM の「考える深さ」を表すパラメータを、`gen_ai.request.reasoning.level` という新しい属性として OpenTelemetry に提案し、マージまで持っていった話。
三つ目は、Go のアセンブリ記法の背景にある Plan 9 の思想をたどりながら、「CPU 命令」ではなく「ツールチェーンとの約束事」として読むと理解しやすいよ、という技術解説。四つ目が、COTEN の世界史データベース開発チームが、プロダクトとデータの二つのチームで、生成 AI 前提の開発フローをどう設計しているかを紹介した記事。そして最後五つ目が、関数型スタイルと ts-morph を組み合わせて、コードからほぼそのまま仕様書レベルのフロー図を自動生成する、という試みでした。
気になった記事があったら、このあとショーノートに詳しい情報を載せておきますので、ぜひ元の記事も読んでみてください。そして番組の感想や、「こんなテーマを取り上げてほしい」といったリクエストも、どしどしお待ちしています。ということで、きょうはこのへんで。次回の「zenncast」も、またマイクと一緒に最新の記事を追いかけていきましょう。それでは、いってらっしゃい。