全18講に触って動くシミュレーターを入れた日 — PR12本のレビューから講座導線の作り直しまで

開発mdx-playground

「昨日の仕掛かりがあったと思うので、ちょっと調べてください」。

朝いちばんに打ったのはこの一行だけだった。前日の終わりに何を作りかけていたか、自分では思い出せていない。調べさせてみると、コンピュータのしくみ講座の第7〜18講が、12本のプルリクエストとしてクラウド側に積み上がったまま宙に浮いていた。全部終わっているはずだが、1本も本体に入っていない。

12本を一度に見るには、12人いればいい

12本を順番に読む気にはならなかった。1本ずつ開いて演習の数を数えて図解を見て、を12回やると午前が終わる。

そこで「レビューはサブエージェントを使ってください」とだけ指示して、12本ぶんのレビューエージェントを並列で走らせた。1本=1エージェント。判定は「マージ可」か「要修正」の二択で、演習数が計画書と一致しているか、図解が壊れていないか、原書の訳文に近い表現が混ざっていないかを見させた。

完了報告が1本ずつ返ってきて、最初に赤が出たのが第10講だった。

正解位置が完全周期(A→B→C→D×7回・逸脱ゼロ)で、設問を読まずA→B→C→Dと押すだけで全問正解になる状態です。

28問すべてが規則正しく並んでいた。選択肢を読まなくても満点が取れる演習は、演習として死んでいる。これは以前に公開済みの第6講でも一度指摘した種類の事故で、同じ工程で作られた残り11本にも同じものが混ざっているだろうと身構えた。

結果は、要修正が3本、そのまま入れてよいものが9本。要修正の残り2本はどちらも図解の描画不良で、第11講は図のパネルタイトルと注記が同じ座標に完全に重なっていて、凡例が右端をはみ出していた。第13講もキャプションとレンジラベルが重なっていた。どちらもブラウザ上で実測して座標の重なりを検出させたもので、目視の「なんとなく詰まっている」ではない。

修正はworktreeを切って3本分。正解位置の並べ替えは目でやると必ず取りこぼすので、並べ替えたあとにNodeスクリプトで機械検証させた。分布7/7/7/7、巡回パターン0、3連続0。数字が出てから初めてコミットした。

12本すべてをマージして、オープンなPRがゼロになった。

「これ、著書に依存する内容でしたか?」

マージが終わったところで、ずっと気になっていたことを聞いた。この講座は非公開のまま止めてある。理由は「原書の表現に寄りすぎているかもしれない」という懸念だった。

ただ、コードの話である。2進数も論理ゲートもフリップフロップも、誰かの著作物ではなく事実だ。事実の説明が特定の本に依存するはずがない。依存しているように見えるなら、それはこちらの書き方の問題であって、内容の問題ではない。

計画書に残していた過去のレビュー記録を確認させたところ、抜粋はゼロ、懸念があったのは原書の訳文に近い言い回しが4か所だけだった。4か所を言い換えて、12エントリを公開状態に切り替えた。トップページに18講ぶんのカードが並んだのを見て、ようやく講座が存在することになった。

カードが18枚並んでも、誰も読まない

並んだ瞬間に別の問題が見えた。トップページの「学習・クイズ」の区画に、講座のカードが18枚、他の教材と混ざって散らばっている。自分でも「どれが講座でどれがクイズか」が判別できない。

まずカテゴリを1つ作って講座を全部そこに押し込ませ、次に構造そのものを3階層に組み直した。トップにはカードを1枚だけ置く。押すと /code-course の一覧ページに着く。そこから各講へ入る。

パンくずが素直に直らなかった。第3講以降は /code-course/ch07-08 のような階層パスなので、セグメントを分解すれば自動的に一覧ページが挟まる。ところが第1講と第2講だけはトップ直下のパスで作ってあって、分解しても「Home > 第1講」にしかならない。講座に属するトップレベルのパスだけは、Homeの直後に一覧ページを差し込む処理を足して揃えた。既存のパステスト36件はそのまま通し、講座用のケースを追加して238件。

http://localhost:3000/code-course を開いて、トップ→一覧→講→パンくずの往復を実際に歩いて確かめた。

「全然インタラクティブな感じないんですよ」

導線が通ったので講を1つ開いてみて、手が止まった。本文があって、図があって、4択の演習がある。図は動かない。18講ぶん全部そうだった。

一方で、以前に作って公開せずに置いてある別のノートには、押すと動くシミュレーターが13点ある。題材はほとんど同じだ。機械式カウンタ、スイッチ回路、真空管、半加算器、CPUシミュレータ。使われないまま眠っている。

「入れられるなら入れてほしい。どこに入れたか教えてほしい」と指示して、まず対応マップと実装計画を作らせた。この計画はCodexにもレビューさせて、致命的な指摘を3件反映している。中でも効いたのは「第4講に既存のアナログ・デジタル変換のシミュレータを転用するな」という指摘だった。第4講が扱うのは「nビットで2ⁿ通り」までで、サンプリングも量子化もその講には出てこない。転用をやめ、ビット数スライダーだけの別物を新規に起こす方針に差し替えた。

もう1つの指摘が「新規10点を必須スコープにしろ」。取捨選択を許すと16/18講で止まる、という読みで、実際そのとおりになりかけた。

そのまま貼ると、講がずれる

転用は複製して名前を変えれば終わる、と思っていた。動くコードがすでにあるのだから、配置して色を合わせれば済む。

済まなかった。第11講のトランジスタのシミュレーターは、ゲート・ソース・ドレインという電界効果型の用語で書かれている。ところがこの講が扱っているのは接合型で、コレクタ・ベース・エミッタである。同じ「トランジスタ」でも別物だ。そのまま貼ると、本文と図とシミュレーターで3種類の用語が並ぶ。

各講のデータファイルの冒頭には、その講で使ってよい用語と使ってはいけない用語を書いたコメントを残してある。これを「必ず読め」とプロンプトに入れたら、実装エージェントが自分から用語を作り替えてきた。

  • 第11講のトランジスタは接合型に作り直し、電界効果型の用語が混ざったら落ちるテストを1本足した
  • 第17講のコード例は、C風の波括弧をやめて a := 5; のALGOL風に置き換えた(Cはこの講の範囲外)
  • 第12講のCPUシミュレーターのニーモニックを8080の語彙に揃え、1〜3バイトの可変長命令にしてプログラムカウンタが命令長ぶん進む動きを本文とつないだ
  • 第8講の「S=R=1」は「不定」ではなく「禁止」と表示する。この講ではそう教えている
  • 第18講のフレームバッファはRGB3バイトをやめ、白黒1ビットの8×8にした

用語がずれた教材は、読者に「自分が理解できていないのか、教材が間違っているのか」を判別させる負担を押しつける。動くかどうかより、こちらの方が事故が大きい。

5並列は通り、10並列は全滅した

Phase 1は転用12点。最初の4講は1講ずつ、挿入→ユニットテスト→E2E→ブラウザで実描画確認→コミットまで、直列で順番に回させた。手順が固まったところで残り5講をワークフローに投げ、5エージェント並列で実装させた。5/5成功。

味をしめてPhase 2の新規10点を10エージェント並列で投げたら、全員が You've hit your session limit で落ちた。リセットは12:10pmと表示された。

ワークフローの戻り値は null である。ここで「失敗した、作り直そう」と判断すると、半日ぶんが消える。git status を叩かせたら、10点のうち6点は完成品がディスクに残っていた。エージェントが落ちたのは報告の直前で、書き込み自体は終わっていた。落ちても実物を確認するまで失敗と決めない、というのは今日いちばん高くついた教訓になった。

残骸を拾って6点を完成させ、残りは3並列で走らせて全て通した。全18講に最低1つ、触れるものが入った。

検証は親セッション側でまとめて回した。ユニットテスト107件、E2E 6件、そのあと全18講の一括回帰で38件。デスクトップと390pxのモバイル幅の両方をブラウザで開いて、押して、値が変わることを目で確認した。モバイル側では第11講の数字車が10pxはみ出していて、その場で詰めた。

同じものを2つ並べていた

作ったシミュレーターを一覧ページにも載せてほしい、という依頼があった。既存の一覧には15点が9グループで並んでいる。末尾に新しい12点を識別用の見出しつきで足した。

足した直後に「これ、既存のと同じものじゃないですか」と言われた。確認させたら、追加した12点は全部が既存15点と同じ題材だった。4ビットカウンタも機械式カウンタも真空管も半加算器もCPUシミュレータも、同じものが2つのスタイルで並んでいた。自分で見ていたのに気づいていなかった。

新しい方に一本化して差し替え、重複のない3点は旧版のまま残し、新規に作った6点を題材別のグループに合流させた。10グループ25点、重複ゼロ。DOM上のidとタイトルを実測して確かめた。

呼び名も直した。「講座に掲載している版」と書いていたが、このシミュレーター群を講座に載せているわけではない。「講座を作る過程で生まれたシミュレーター」に言い換えて、各点からは「関連する講」へのリンクだけを張った。

作り終えたが、誰も見ていない

夕方に「今日は全部終わったということでいいですか」と聞いたら、返ってきたのは「作る作業は全部終わったが、公開はしていない」だった。実装もテストもコミットも済んでいて、25点のシミュレーターは自分のマシンの中だけで動いている。本番の18講は、今日の朝と同じ静止画のままだ。

そこで今日は締めることにして、残件をドキュメントに書かせた。書かせたのは3つ。いま何点がどの講に入っているかの実測表、実装するときに従うべき規約(枠の色から border-box の統一、モバイル306px幅の扱いまで)、そして再開ガイド。次に座ったとき、まず何を叩けば現状が分かるかまで書いてある。

冒頭の「昨日の仕掛かりを調べてください」を今朝また打つはめになるのが嫌なので、そこだけは丁寧にやった。

今日の学び

  • 並列で落ちたら、まず git status を見る。 ワークフローの戻り値が null でも、成果物はディスクに残っていることがある。10エージェント全滅から6点を回収した
  • 正解位置の周期性は、目では見つからない。 並べ替えたあとにスクリプトで分布と巡回を検算する。28問の規則性を人力で確認しようとしていたら見落としていた
  • 「同じ題材か」は自分の目より DOM の実測。 15点と12点を並べて重複ゼロだと思い込んでいたが、実際は12点全部が重複していた
  • 本に依存しているように見えるなら、書き方の問題。 コードの話は事実であって、事実の説明は誰の著作物でもない。近すぎる表現を4か所言い換えるだけで公開できた

明日やること

  • 本番へ反映する。25点のシミュレーターは今日いちども読者の目に触れていない
  • 反映後、本番URLで数講を開いて実際に押して動くことを確かめる(SSGでpayloadが null に化ける既知の罠にかかっていないかも見る)
  • 演習の直前に「解く前に触ってみてほしい」の一文を足す。いまのリード文は「何が見えるか」までで止まっている