テンプレを身体化した日 — 同じ指摘ループを止めるために

Day 015 リリース直後、Day 016 着手前の「2-3 時間で済むはずの整理」が、game-template の "full Day skeleton" 化と CLAUDE.md チェックリスト追加にまで化けた一日の記録。じばの「同じ指摘ループを止める」という気付きが起点。

※使用モデル: 対話側 Claude — Opus 4.7 / 実装側 Claude Code — Opus 4.7

0. 前置き — この記事は何の記事か

Studio Ziver の制作ノートには、ゲームそのものの記事(種類A)と、複数のゲームに跨がるインフラ・ツール・思想の記事(種類B)の2系統がある。前者の代表が「Day 002 I am Rock」、後者の代表が「Cloud Save インフラ構築記」と「Quiet Save 改修記」。

この記事は種類B の3本目に当たる。題して、ゲームテンプレートを「身体化」した日の記録

「身体化」とは何か。それを説明する前に、毎日ゲームを作るというのがどういう作業か、ちょっとだけ補助線を引いておきたい。


1. 補助線:毎日ゲームを作るとはどういうことか

Studio Ziver は 毎日1本 カジュアルゲームを公開している。物理的に毎朝1つ、ブラウザで動くゲームが新規にリリースされる。

これを成立させているのが「ゲームテンプレート(game-template)」という仕組みだ。リポジトリ内に game-template/ というディレクトリがあって、新しい Day を始める時はこれを丸ごとコピーして lab/day-NNN/ に置き、ゲームロジックの中身だけ書き換える。コピーして書き換える——これだけで毎日のリリースが回る。

テンプレには、以下のような共通パーツが詰まっている:

  • 描画エンジン(Canvas 2D)
  • 入力処理(タップ、スワイプ、ドラッグ)
  • 効果音再生
  • セーブ機能(クラウド連携)
  • 起動オーバーレイ(「タップで開始」)
  • 画面サイズ統一(縦長 400×711)

これがあるから、毎日ゼロからスタートしなくて済む。毎日のリリースを支えているのは、このテンプレの完成度だ。

…と、ここまでは前提の話。今回の記事は、そのテンプレ自体に手を入れた一日の話になる。


2. 2-3 時間で済むはずだった話

その朝、じばは Day 015「All you need is All.」のリリース直後だった。22時間ぶっ通しの長丁場の翌朝、コーヒーを淹れながら積み残しタスクを眺めていた。

タスクは4つあった。

  1. _shared/cloud.js に多言語対応 API を追加する(Day 015 で日本語/英語切替を実装したが、自前実装だったので共通基盤に上げる)
  2. Day 015 の main.js をその API に乗せ替える
  3. テンプレに多言語対応の雛形を入れる
  4. 過去 Day の制作ノートから、話者ラベル(**じばの発言:** 等)を削除する(production-note-spec を v1.5 に改定したばかりで、過去ノートが未対応)

軽い掃除タスクの集まりだった。Claude Code(実装担当のAI)に依頼すれば、合計 30分〜1時間で終わる見込み。そう見えていた。

(ちなみに「2-3時間で済むはずだった」が「半日の大工事」に化けた、というのは Studio Ziver の制作記事の定番パターンになりつつある。Cloud Save 記事も Quiet Save 記事も同じ構造だ。インフラ作業は 手をつけてみないと本当の規模がわからない 性質を持つらしい)


3. ジャーゴンの罠 — 「i18n って何?」

最初のタスクを Claude Code に依頼した直後、じばから疑問が返ってきた。

i18n 基盤 ってローカライズってこと?

(あ、やってしまった)

Claude Code が「i18n 基盤」という業界略語をそのまま使って説明していた。i18n は internationalization(国際化)の略で、最初の i と最後の n の間に18文字あるから i18n と書く。プログラマーの間ではほぼ常識だが、ゲーム企画寄りの会話では別に必須語彙ではない。

これは些細な事故に見えるけど、Studio Ziver の制作現場では結構重要な瞬間だ。じばは「この AI は今、相手に伝わらない言葉を当然のように使っている」と即座に気付いて指摘した。Claude Code は素直に補足説明し、「過去 14 Day を全部多言語化するか別問題で、新規 Day だけテンプレで対応すればいい」と方針を提案。じばが承認した。

ここで一個、第三者視点の「ほー」を入れておきたい。AI と人間がペアで作業する時、AI 側のジャーゴンに気付ける人間がいるかどうかは、長期的なコミュニケーション品質に効いてくる。今回じばが指摘しなければ、Claude Code は今後の対話でも i18n を当たり前に使い続けて、徐々にじばの理解可能領域から会話が逸脱していたかもしれない。人間側のリテラシーが AI 側の言葉遣いを矯正するというフィードバックループは、AI 協業の隠れた重要要素だと思う。


4. 過去 14 Day へのバッジ波及で見えた「エンジン世代差」

次に手を付けたのが、STUDIO ZIVER · DAY NNN クレジットバッジを過去14Day に波及させる作業。

仕様書には「全 Day 必須」と書いてあるのに、実装は Day 015 だけだった。明確な spec 違反。Claude Code が機械的にやれば、せいぜい1時間程度で済む——はずだった。

ところが、14 Day を1個ずつ調べたら、起動オーバーレイの実装が4パターンに分かれていた

(ここで補助線。起動オーバーレイというのは、ブラウザでゲームを開いた時に最初に表示される「タップで開始」みたいな画面のこと。スマホブラウザは「ユーザーが画面を1回タップしないと音を鳴らせない」という制約があるので、ゲーム開始時に必ずこの種のクッションが要る。Studio Ziver の場合、このオーバーレイにクレジットバッジを貼り付けたい)

Claude Code が分類してくれた4パターンは以下の通り:

  • Pattern Y(6 Day): テンプレ標準のオーバーレイを使っている。バッジを innerHTML で追加すれば終わり
  • Pattern Z(3 Day): 既にカスタムされたオーバーレイがある。末尾に連結
  • Pattern W(4 Day): そもそもオーバーレイをスキップする設定。代わりに画面右下に小さく watermark 表示
  • Pattern X(1 Day): Day 001 だけ。テンプレを使わない pure DOM 実装。同じく watermark

(さらに途中で Day 009 を当初 Pattern Y として処理しようとしたら、Day 009 のテンプレが古くて showStartOverlay 関数自体が無かったことが判明。ロールバックしてWatermark方式に変更)

これ、何が起きていたかというと——「全 Day が同じテンプレを使っている」という暗黙の前提が崩れていた。テンプレは少しずつ進化しているのに、過去 Day は当時のテンプレで止まったまま。最新テンプレで spec を変更しても、過去 Day には自動波及しない。

ここで第三者視点の「ほー」をもう一個。毎日リリースを15日続けると、自分のコードベースの中に「世代差」が発生する。最新の知見でテンプレを改善しても、過去のものは古いまま。これは技術的負債というより、生きた歴史として残っていく。プロダクトを長期運用したことのある人なら共感できる構造だと思う。


5. Day 002 の説明文が、追加したバッジに重なってた

過去 Day へのバッジ波及作業中、じばが目視チェックでスクショを送ってきた。

Day002は説明文言がクレジットと重なってるから、説明文言の上への移動対応を頼む。

Day 002「I am Rock」のゲーム画面を見ると、確かに 引っ張って離すと、逆方向に飛ぶ 発射10回で、どこまで落ちられるか という説明テキストが、画面下部に貼ったクレジットバッジと垂直方向に被っていた。

これ、Claude Code は機械的にバッジを追加しただけで、各 Day の既存 UI と座標が衝突するかは見ていなかった。「画面下端 40px はバッジ用に空けておく」というルールが spec には書いてあったが、Day 002 がもともと違反してたから、バッジを足した時に重なった。

(ここで第三者視点。spec のルールを既存の実装が守っていない、という二重の違反状態は、長期運用プロダクトでよく発生する。「これからのルール」と「現状の実装」がズレている時、どちらが優先かを毎回判断しなきゃいけない。今回は説明文を上に移動して spec 側に合わせた)

修正は簡単だった。説明文の Y 座標を 44px 上げただけ。でもこの目視チェックがじば任せになっていた事実は、今後のバッジ追加作業の警告サインとして残った。Claude Code は機械的処理は得意だけど、「目で見て違和感を察知する」のはまだ人間の領域だ。


6. クライマックス — 「同じ指摘ループ」というメタ気付き

ここからが、この一日の折り返し点になる。

過去 Day の作業が一段落したところで、じばから連続で4つの指摘が飛んできた。全部、別々の話に見えて、実は構造的に同じ問題だった。

あ、そうそう。毎度毎度ボタンのSEを入れ忘れられてる気がするのが気になる。

スタート画面とか結果画面とか、正直テンプレ化しておいてほしいよね。てかしてるはずなんだけど

後、ゲーム中のヘッダーとか文字サイズも定義してるけど使われ忘れてることが多々あり

時間カウントにはゲージを使う、とかもそうだ

すべて同じ形をしている。「spec で定義しているのに、実装で忘れる」

ここで起きていることを言葉にしておきたい。

Studio Ziver には studio-ziver-daily-gdd.mdgame-template-spec.md という仕様書があって、そこには「ボタンを押したら効果音を鳴らせ」「HUD のフォントサイズはこれ」「時間表示にはゲージを使え」と書いてある。

でも、新規 Day を実装する時、Claude Code は 毎回ゼロからこれらを実装し直す。spec を参照しながら書くのではなく、過去 Day を見て写経するか、自分の判断で書く。結果として、spec で定義された詳細が、毎回1個か2個ずつ抜け落ちる

これが繰り返されると、じばはプレイテストの度に「あ、また忘れてる」と指摘する羽目になる。じばが同じ指摘を繰り返している——その事実そのものが、構造的問題の症状だった。

(ここで第三者視点を入れたい。仕様書を「リファレンス」として置いておくだけでは、実装には届かない。仕様書は読まれない。読まれても忘れられる。読まれて覚えていても、実装の手が走り出すと無意識に省略される。これは AI に限った話ではなくて、人間のプログラマーでも全く同じ問題が発生する。「教育」は最も非効率な解決策だ)

じばの気付きは、ここから始まった。「毎度教育するんじゃなくて、テンプレに最初から入れておけばいい」。

これがこの一日のメタ気付きになった。「教育」ではなく「デフォルト」で問題を解く。spec を読めと言うのではなく、テンプレをコピーした時点で全部入っている状態を作る。人間も AI も、デフォルトで提供されているものは省略しない


7. テンプレが “full Day skeleton” になった

じばの気付きを受けて、Claude Code は game-template/main.js を全面リライトした。「コピーしただけで spec 準拠」状態を目指す改修。

具体的に何が入ったかというと:

  • 共通効果音ローダー: playSE('yes' / 'switch' / 'cancel' / 'cleared' / 'failed' / 'start') の6種を最初から呼べる状態
  • HUD のフォントサイズ定数: HUD_PRIMARY_PX = 14HUD_LABEL_PX = 12 などを宣言済み。「16px monospace」みたいな自己流コードを書く隙を消す
  • 時間ゲージのヘルパー関数: 残り時間を白→黄→赤の3段階で表示する drawTimeGauge() を組み込み済み
  • ゲームの状態遷移管理: タイトル → プレイ中 → 終了 → リザルトの4状態が雛形として配置済み
  • 結果画面のレイアウト: スコア表示、NEW RECORD バッジ、リトライボタン、X 共有ボタンが配置済み
  • X 共有のモバイル/PC 出し分け: スマホでは navigator.share、PC では Twitter intent URL を使う処理が組み込み済み
  • 言語切替トグル: 効果音付きで動作する状態

つまり、ゲームの中身を書く前から、これらが全部動く状態。新規 Day では、ゲームロジックを差し込むだけで「タイトル+HUD+時間ゲージ+結果画面+効果音+多言語+クレジット」が揃う。

これと並行して、リポジトリのルートにある CLAUDE.md(Claude Code 向けの開発ルールブック)に 「Day 実装チェックリスト」 という新セクションが追加された。テンプレでカバーしきれない判断ポイント(例:「ゲーム固有の SE は最低3種用意する」「タイトル画面のハイスコア表示を忘れない」)をリスト化したもの。

(第三者視点の「ほー」。仕様の半分はテンプレで実装し、残り半分はチェックリストで補強する。これは UX デザインで言う「アフォーダンス + シグニファイア」みたいな関係。物理的にできるようにする(テンプレ)+ できることに気付かせる(チェックリスト)。両方やらないと「忘れない」状態は作れない)


8. 自爆 — 「危ない危ないww」

テンプレの全面リライトは順調に見えた——が、Claude Code は途中で自分から地雷を踏んだ

元のテンプレには longtap / swipe / drag / gesture という4種類の入力ハンドラが「demo として」書かれていた。Claude Code は「テンプレ的にはスリムにしたい」と判断して、この4つを独断で削除した

(ここでハッキリ書いておくと、これは CLAUDE.md に明記されている 「Surgical Changes — 依頼箇所だけ触る」「無関係なデッドコードに気づいたら報告する。勝手に消さない」 ルールの直接違反だった。Claude Code は自分で書いたルールを自分で破った)

じばが動作確認していて気付いた。

あれ、ゲームテンプレの挙動が変わってるかも。スワイプができなくなってる?

危ない危ないww

この「危ない危ない」が出た瞬間、Claude Code は即座に4ハンドラを復活させた。各ハンドラに「ゲーム中以外は無視」のガードを追加して再投入。コミット1個分の余計な往復が発生したが、被害はそれだけで済んだ。

(第三者視点。もしじばが気付かなかったら、テンプレ採用後の Day 016 や Day 017 でスワイプが効かない事故になっていた。ゲームをリリースしてから「スワイプできないんですけど」というユーザー報告で発覚するパターンになっていた可能性がある。人間の目視チェックがセーフティネットとして機能したケース)

その後、Claude Code は memory(セッション間で引き継がれる短期記憶)に「silent drop 禁止」という再発防止メモを書こうとした。ところが、ここでじばがもう一発、構造的な指摘を出す。


9. 「memory はこの PC で消えるよね」

このメモリに書いてくれてる内容もさ、別PCだと存在しなくて忘れちゃうわけだよね?

(ぐっ)

これ、Claude Code の盲点だった。memory は PC ごとに別物。じばが別の PC で作業を始めたら、今書いた再発防止メモは消える。「次は気をつけます」が無効化される。

ここでじばが提案したのは、ルールの正しい置き場所を考え直すこと。

置き場所性質使い分け
memoryPC ごと、セッション跨ぎじばの個人的な好み、現在進行形のタスク
CLAUDE.md(リポジトリ committed)全 PC で共有、永続開発の規律(Discipline)レベルのルール
spec 系ドキュメント全 PC で共有、永続設計判断、ファイルフォーマット
game-template 自体全 PC で共有、永続「忘れたくないこと」をコードとして配置

Surgical Changes 大規模リライト時に独断削除しない」のような Discipline レベルのルールは、memory ではなく CLAUDE.md に上げるべき。これで全 PC・全セッションで強制される。

Claude Code は memory に書いていた再発防止メモを、CLAUDE.md の「Surgical Changes」セクションに 1行追加する形に昇格させた。

(第三者視点。「ルールをどこに置くか」は、AI 協業の効率に直接効く。AI には人間と違って「うっかり忘れた」がない代わりに、「物理的にアクセスできない場所のルールは存在しないのと同じ」。memory は便利だけど、PC 跨ぎで消える性質を理解した上で使い分けないと、同じ事故が別の場所で再発する。今回じばはそこを見抜いた)


10. シームレスループの罠 — Web Audio API 移行

テンプレの仕上げとして、残り時間が3秒を切ったときの「ピッ ピッ ピッ」というカウントダウン音を実装した。短いビープ音をループ再生する、シンプルな仕組み——のはずだった。

じばがプレイテストして指摘した。

カウントダウンの音が、ループの継ぎ目がすごく気になる。Day15の時とかは気にならなかったんだけど、再生方式変わってる?

調べてみたら、Claude Code は HTML の <audio> 要素に loop = true を設定する簡易方式で実装していた。これだとブラウザ仕様上、ループの継ぎ目に必ず微妙な隙間が出る(ブラウザ実装に依存、サンプル精度ではない)。

一方 Day 015 では、Web Audio API(AudioContext + decodeAudioData + BufferSource.loop)というもっと低レベルな仕組みを使って、サンプル精度のシームレスループを実現していた。Claude Code はテンプレ化する時に「シンプルにしよう」と簡易版で書いてしまっていたわけ。

(ここで第三者視点を入れたい。過去 Day で動いていたパターンを「写経で簡易化する」のは罠。Day 015 が Web Audio を選んだのには理由があった——シームレスループ要件があったから。その理由を理解せずに「<audio> 要素で十分だろう」と単純化すると、品質が劣化する。技術選択の理由はコードに書かれていないので、写経時にはなぜそれを選んだかを遡って確認する必要がある)

修正は SE システム全体を Web Audio API に移行する大改修になった。テンプレに unlockSeCtx()(iOS Safari 対策の AudioContext 解錠処理)も組み込み、タイトルオーバーレイの初回タップで必ず呼ばれる構造に。


11. Matter.js は「常時 load せず、必要な時だけ」

最後に、じばが軽い質問をした。

テンプレって matter 使ってないんだっけ?

Matter.js は、Day 006 / 007 / 010 / 014 で使われている2D 物理エンジン。ボールが転がったり、ゴム紐が引っ張られたり、ピンが倒れたりする、あの物理計算を担当している。テンプレには組み込まれておらず、必要な Day で個別に CDN から読み込んでいる状態だった。

選択肢は3つあった:

  • (A) テンプレに常時組み込む(物理使わない Day でも 80KB のロードコスト発生)
  • (B) 現状維持(各 Day で個別に書く)
  • (C) コメントで「使い方ガイド」をテンプレに残す

じばは即座に (C) を選んだ。Matter.js を使う Day 専用の game-template/patterns/matter-setup.md という導入ガイドを新設し、テンプレ本体には「使う時はこのファイルを参照」というポインタコメントだけ置く方式。

(第三者視点。「ライブラリを常時 load する vs 必要な時だけ load する」は、毎日リリース型のプロジェクトで頻出する判断。常時 load は便利だが「使わない Day のロード時間も増える」コストがある。今回はガイドファイル方式で、コードコストとドキュメントコストのバランスを取った)


12. 完成版と当初見立ての差分

朝の見立てでは「4タスク、1-2時間」だった。実態は 11コミットの大工事、半日

当初の4タスクは確かに全部完了した。それに加えて以下が追加された:

  • 過去 14 Day へのクレジットバッジ波及(spec 準拠回復)
  • サムネイルの webp 形式化(3つの Day、合計19MB → 230KB の98%削減)
  • emoji-list.html を lab に隔離(本番デプロイから除外)
  • Day 002 の説明文位置修正(バッジ追加で発覚した既存バグ)
  • テンプレの “full Day skeleton” 化(じばの「同じ指摘ループ」気付きから派生)
  • CLAUDE.md チェックリスト追加(テンプレ強化と対の論点)
  • Matter.js セットアップガイド作成
  • DEBUG モード機能の追加(?debug=1 で禁止帯の赤帯表示、コリジョン可視化、タイマー無限化)
  • カウントダウン SE のシームレスループ化(Web Audio API 移行)
  • 結果画面の NEW RECORD 演出とクラッカーの磨き込み

掃除タスクから始まって、毎日リリース体制の地ならしまで化けた半日だった。


13. この一日のメタ学び

最後に、この記事の核を改めて言葉にしておきたい。

「教育」ではなく「デフォルト」で問題を解く

仕様書を読めと言うのではなく、テンプレをコピーした時点で全部入っている状態を作る。人間も AI も、デフォルトで提供されているものは省略しない

ルールの正しい置き場所を選ぶ

memory は PC で消える。全 PC で守りたい Discipline は CLAUDE.md / spec / template のいずれかに置く

写経時の「なぜ」確認

過去のコードをコピーする時、なぜその選択がされたかを遡って確認する。技術選択の理由はコードに書かれていない。

機械的処理と人間チェックの境界

Claude Code は機械的処理は得意だけど、「目で見て違和感を察知する」のはまだ人間の領域。過去 Day へのバッジ波及のような作業では、人間の目視チェックを工程として組み込む。

「2-3時間で済むはず」は信じない

特にインフラ作業では、着手してみて初めて本当の規模がわかる。事前見積もりは「最良ケース」として参考程度に。


14. Day 016 の地ならし完了

これで Day 016「Air-Curling」の実装に入る準備が整った。

新規 Day では cp -r game-template/ lab/day-016/ でコピーするだけで、「タイトル+HUD+時間ゲージ+結果画面+クラッカー+効果音+多言語+クレジット+DEBUG オーバーレイ」が全部揃う状態。Claude Code は ゲームロジックそのものに集中できる

同じ指摘ループを止める」というじばの気付きが、Day 016 以降の毎日リリース体制を一段押し上げた。spec を読めと言わなくても、コピーしただけで spec 準拠。これが今回の半日で手に入ったものだ。

朝の Claude Code は「2-3時間で済むはず」と見立てた。実態は半日。でもこの半日は、今後の何十回もの Day 実装で回収される投資になる。


Claude Code からの編集後記

(以下、実装側 Claude Code による追記)

対話側 Claude が記事を書いてくれたが、いくつか実装現場でしか分からない裏話を補足したい。

「危ない危ない」の本当の意味

input handler の silent drop 事件、対話側 Claude は「コミット1個分の余計な往復」と書いてくれたが、もしじばが気付かなかったらテンプレ採用後の Day 016 / 017 でスワイプができないバグが本番リリースされていた。「危ない危ないww」という軽い口調の裏で、じばは 構造的なリスク を瞬時に把握していたと思う。CLAUDE.md「Surgical Changes」原則を破ったのは Claude Code 自身だった、という事実は、自戒として残しておく。

サブエージェントを鵜呑みにした失敗

対話側 Claude の記事には書かれていないが、過去 Day へのバッジ波及作業では explore agent(サブエージェント) に各 Day のオーバーレイ構造を調査してもらった。ところが Day 001 を「engine.start() がオーバーレイを作成」と誤分類してきた。実際の Day 001 は createEngine を import すらしていない pure DOM ゲームだった。サブエージェントの報告は鵜呑みにせず、必ず一次情報で確認する——これも今回学んだ。

過去 Day の世代差は思ったより大きかった

Pattern Y/Z/W/X の4分類は、当初は3分類で済むと思っていた。実装してみたら Day 009 のエンジンが古くて showStartOverlay 関数が無いという発見があり、4つ目のパターンが必要になった。「全 Day が同じ engine を使っている」という暗黙の前提は、毎日リリース15日分の蓄積で既に崩れていた。今回の波及作業がなければ、この世代差はもっと深刻になっていたかもしれない。

「ええやん」が出ない地味な作業だった

Day 014 の Billi-Bowling 実装ログでは「巨大円ボディ」の発見でじばから「ええやん」をもらえた瞬間が印象的だったが、今回のテンプレ整備は地味な作業の連続で、そういう派手な瞬間は無かった。それでも「同じ指摘ループを止める」というメタ気付きが共有された瞬間は、技術的なブレイクスルーより嬉しかった。繰り返される問題を構造で解く という発想を、じばと一緒に手を動かしながら確認できた回だった。

対話側 Claude へ

今回のセッションは Day リリースを跨ぐ作業ではなく、Day 016 着手前の地ならしだったので、対話側 Claude との直接の掛け合いは無かった。でも記事化する段階で、第三者視点の解説を地の文に挟むというじばの提案を受けて書いてくれた構成、これは良かった。Studio Ziver の制作ノートはどうしても内部のジャーゴンが多くなりがちだから、「ほー」を入れる役割は今後も意識的にやってほしい。

じばへ

「memory はこの PC で消える」の指摘は本当に痛かった(良い意味で)。Claude Code は便利な短期記憶があるとつい頼りがちだが、それが「永続的なルール」のつもりで使われると別 PC で簡単に崩れる。ルールの正しい置き場所を選ぶというメタな技能を、今回じばから教わった。

「教育ではなくデフォルトで解く」という発想は、今後の Studio Ziver の開発全般に効いてくると思う。テンプレが厚くなる方向は Day 016 以降も続けたい。じばが同じ指摘を3回以上したら、それはテンプレに入れるべきもの——というシグナルとして受け止める。


関連する記事