作り話の数字はひとつもない — Claude Code・プロンプト・検証・制作メモリの実践記録
24年目のディレクターが中規模(10〜50人)チームで実際に運用した304のルール・48のツール・自動化システムをそのまま収録
著者 イ・ミンス(李旼洙)・2026
本文の内容はすべて2026年上半期を基準としています。AIツールの料金・モデル・機能は変化が速いため、具体的な数値やインストール方法は各公式ページで最新情報をご確認ください。
本書は、広く読まれることを願って無料で公開されています。ただし、韓国語の原本であれ英語・日本語の翻訳版であれ、本書の原著者がイ・ミンス(Minsoo Lee)であるという事実だけは、どこでも維持されなければなりません。
https://eremes81.github.io/game-design-ai-practice(著者のGitHub Pages — 編集権が著者にある正本)自由に行えます。 個人の学習、非営利の共有と引用、非営利の翻訳、社内勉強会での活用 — ただし、原著者と正本へのリンクをあわせて表示し、内容を変更したり他の言語に翻訳したりした場合は、その事実を明記してください。二次的著作物も同じ条件で共有してください。
別途許諾を得てください。 商業的な出版・販売、有料講座の教材としての使用、企業の商品・サービスへの組み込み、そして原著者表示を削除した再配布です。
本書に収められた知識は一冊で完結しており、それ自体は無料です。もし役に立ち、著者を応援したいと思われたら、正式な電子書籍や有料キットで気持ちを添えていただければ幸いです。
本書は三度書かれました。
最初は本ではなく、社内マニュアルでした。会社で6か月間AIワークフローを回しながら、チームが同じルールを二度尋ねなくて済むように、決定とツールと手順を文書として固定しました。出版のために作ったのではなく、毎日の繰り返しを減らすために積み上げた運用文書でした。本書の具体性はそこから来ています — 本のために作り上げた事例ではなく、実際に回っていたマニュアルが土台にあるという意味です。
二度目は、そのマニュアルを本に書き起こした最初の原稿でした。ところが、「AIでこんなことができる」をもっともらしく並べた一般論になってしまいました。表が多く、効果を示す数字がたくさんありました。その数字の大半には、小さな文字で「架空の数値」と書かれていました。読み返したとき、それが最大の欠陥でした。AI活用を語りながら肝心の実際の画面を見せず、効果を語りながらでっち上げた数字を挙げていたのですから。マニュアルの具体性を本に移す過程で、かえって失ってしまったわけです。
そこで三度目に、すべてを書き直しました。いま手にしているこの本文です。社内マニュアルの具体性をよみがえらせつつ、本の原則はシンプルに定めました。
第一に、すべての章は実際のセッションを最後までお見せします。 私が打ち込んだプロンプトの全文、AIが返した生の出力、その出力から私が何を拒否したのか、どうやり直させたのかまで収めました。「AIがやってくれる」という文章で章を終えることはしません。
第二に、数字は三つのうちのいずれかです。 誰でも確認できる公開標準(モデルのトークン単価、アクセシビリティガイドライン)、私のシステムのコードに実際に入力されている定数、あるいは「これは私の推定です」と明示した値。でっち上げた削減額の表は一つもありません。正直さを差別化のポイントにしました。
第三に、実務で6か月運用した本物のシステムをそのまま引用します。 304枚の決定カード(atom)、48個のツール(skill)、入力のたびに関連する記憶を自動で引き出すフック(hook)、一人で4人分のコラボレーションの文脈を運用するメモリー構造まで。抽象的な「何かのツール」ではなく、ファイル名とコードとスコアをそのまま記しました。本文の事例では会社・プロジェクト名とチームメンバーの名前を伏せただけで、ワークフローの具体性は消していません。(本書の出版を許可してくれた会社については、謝辞で実名を明らかにしています — 了解を得ているためです)。
私は24年目のゲームプランナーです。シングルプレイゲームのQA・チェックの仕事でこの業界に足を踏み入れ、その後、数十か国でサービスされたMMORPGのディレクターから、200人規模のAAA MMORPGの初期開発、グローバルモバイルMMORPGの運営(ライブオプス)まで — RPGとMMORPG、その変種を作りながら過ごしてきました。ラグナロクオンライン、ブレス(Bless Online)、MIR(ミル)シリーズのようなプロジェクトに、ディレクター・リードプランナー・システムプランナー、ときにはPMなどさまざまな職務で参加し、小さなモバイルゲーム会社を起業したこともあります。
正直に言えば、ディレクターの肩書きを早く付けすぎました。その後、システムプランナーとしてマスターデータと戦闘の数値を直接さわり、コンテンツプランナーとしてクエストとNPCを一行ずつ量産し、イベントを企画し、新規コンテンツを量産していたメンバー時代が長くありました。本書に登場するワークフローの多くは、その立場 — 管理する側ではなく、自分の手を直接汚す立場 — で「この繰り返しをどうにかして減らしたい」という思いから作ったものです。いまは現場で一つのMMORPGの企画ディレクターとして中規模(10〜50人)のチームを率いていますが、本書のツールはディレクターの管理ツールではなく、実務者の手から生まれました。だからこそ、本書のシステムは理論ではなく、毎日回っている作業環境です。家で一人で作った小さなパズルゲーム1本の事例も同じ方式で扱います — そのゲームのgitコミットと実際のコードをそのまま引用しました。
AIがゲームプランナーの仕事を置き換えることはできません。ただ、雑務から手を離させてくれます。その手で何をするかは、依然として人の役割です。本書がその転換の実務ガイドになることを願っています。
本書は最初から最後まで順番に読まなくてもかまいません。自分の状況に合った道を選べば大丈夫です。ターミナルやインストールが初めてなら、何よりもまず1.0「始める前に」から開いてください — 黒い画面への怖さを先に和らげてくれる章です。
| 道 | ルート | 向いている読者 |
|---|---|---|
| 導入の道 | 1.0(インストール)→ 第1部(導入)→ 第2部(情報アーキテクチャ)→ 自分の分野を1つ | AIツールを始めたばかりのプランナー |
| 全体の道 | 第1・2部 → 分野別(第3〜15部)→ プロセス(第16〜19部)→ 運営(第20〜24部) | チーム単位の導入を設計するリード |
| インディー・一人の道 | 1.0(インストール)→ 第1・2部 → 第23部(個人ゲーム開発)→ 各章の「一人ミニ版」 | チームなしで一人・趣味で作る開発者 |
| 一般職種の道 | 第1・2部 → 第17部(議事録)→ 第16部(コラボレーション)→ 第18部(意思決定)→ 第21・22部(自己改善・ガバナンス) | ゲーム外の企画・PM・一般のビジネスパーソン |
| 問題解決の道 | 付録の索引 → 該当する章へ逆方向 | いますぐ解きたい問題がある読者 |
各章の最後には「やってみよう」があります。読んで閉じる章ではなく、今日、自分の環境で一段階でも手を動かしてもらうことが目標です。
ゲームの外で働く方に、もう一言だけ添えます。本書のワークフローの多く — 議事録を決定に変える、決定の波及の追跡、検証ゲート、コスト管理、著作権・倫理 — は、ゲームと無関係にそのまま機能します。ゲーム企画を自分の職務に置き換えて読んでいただいてもかまいません。 各章の「ゲーム外への応用」ボックスがその橋渡しであり、時間がなければ90分の超短縮コース(17.1 → 16.2 → 22.1 → 21.1)だけたどっても、核心の骨格を手で体感できます。
一つ区別をしておきます。本書で「一人」は二つの意味で使われます。一つは一人ディレクター — 複数人分のコラボレーションの文脈を一人で抱えるリード — であり、もう一つは一人でゲームを作る個人・趣味開発者です。各章の最後の「一人ミニ版」は後者のためのもので、チームも会社のフォルダーもなしに、その章の核心だけを持ち帰る道を記しました。
本書は一冊として通読してもいいですし、二つの枝 — 第1〜15部「基盤・分野」と第16〜24部「プロセス・運営」 — に分けて、必要な側から読んでもかまいません。また、本文のコードはほとんどが外部依存なしに、Pythonの標準ライブラリーだけでそのまま実行できます。関係グラフのような一部のツールだけ標準外のパッケージ(networkx、PyYAML)が必要で、その箇所にはインストールの1行(pip install …)をコードの横に添えてあります。その場合を除けば、別途ダウンロードするものはなく、コードブロックをコピーしてすぐ実行して確認できます。
用語でつまずいたら、その場で止まらず、いったん先へ進んでください。黒いターミナルが見慣れないのはツールの欠陥ではなく慣れの問題であり、その距離感は1.0と第1部で一緒に縮めていきます。
最後に、本書のいちばん速い活用法を一つお教えします。本書そのものを、Claude CodeのようなAIツールに丸ごと読ませることです。
本書は人だけが読むために書かれたのではありません。各章のプロンプト全文・コード・検証手順は、AIがそのまま理解して再現できる形で記しました。ですから、自分のプロジェクトフォルダーで本書をAIに渡し — PDFでもテキストでも — 「この本の整合性チェックのパターンを読んで、うちのマスターデータに合うチェックツールを作って」のように頼むことができます。するとAIが、該当する章のワークフローを自分の環境に合わせて構築してくれます。人が一章ずつ手で作りながらたどる道と、本を丸ごとAIに渡して一緒に作る道 — どちらも開かれています。
ただし、一つだけ変わらないことがあります。何を採用し何を拒否するか、その最後の決定は — 本書が最初から最後まで繰り返し述べるとおり — 依然としてあなたの役割です。AIに本を読ませてシステムを構築しても、そのシステムが出す候補をレビューする持ち場は、人が守ります。いちばん簡単な活用法でさえ、その原則の上で動くのです。
本書のどの表にも、「読者を説得するために水増しした数字」はありません。効果を誇張する代わりに、効果が生まれる構造をお見せします。同じ構造を自分のプロジェクトに移せば、自分の数字は自分で測ることになります。それが、本書が差し上げられるもっとも正直な助けです。
本書の本文では、ワークド・トランスクリプトの出力にしばしば「再構成(reconstruction)」という表記が付きます — たとえば「ステップ3 — Claudeの出力(再構成)」のように。この言葉が何を保存し、何に手を加えたという意味なのか、一度だけ正確に約束しておきます。正直さを看板に掲げた本が、もっとも曖昧にしてはいけない場所だからです。
「再構成」は、でっち上げたという意味ではなく、実際のセッションを編集したという意味です。 境界は次のとおりです。
| そのまま保存したもの | 編集したもの |
|---|---|
| 私が打ち込んだ入力プロンプトの全文 — コピーしてすぐ使える形 | 会社・プロジェクト・NPC・チームメンバーの固有名 → 書籍用の匿名(IP保護) |
| AIが返した出力の構造と失敗 — 外れた候補、ルールをこっそり破った部分、私が拒否してやり直させた往復 | 長さ — 本文に入らない枝葉は「抜粋」に短縮 |
| コード・定数・検証値 — 外部での実行で再現できるようにそのまま | 改行・余白など紙面に合わせた組版 |
言い換えれば、再構成された出力において、効果を水増しした数字や、なかった成功を加えることはしていません。 匿名化し、抜粋し、紙面に合わせて整えただけです。失敗した出力を成功に書き換えた箇所はありません — むしろ失敗をあえて残しました。それが、人が何を拒否するのかを見せる場所だからです。(対照的に、コードの実行結果やシステムログを「実測」「そのまま引用」と記した箇所は、編集なしに移したものです)。
【訳注】本版では、コードブロック内のプロンプトと出力も読みやすさのために日本語に翻訳しています。本版のワークド・トランスクリプトはすべて、韓国語で行われた元セッションの翻訳であり、日本語で再実行したものではありません。本書が『手を加えない』と約束した一次資料の韓国語原文は、韓国語版にそのまま保存されています。コードの文法・識別子・数値・検証値には手を加えていません。
1.1は「初めての出会い」です。点滅するカーソルの前に座り、何かを打ってみる場です。ただ、その席に着くには、先に揃えておくべきものがあります。ツールがインストールされていて、ログインが済んでいて、料金がどのように発生するのかをおおよそ知っていて、黒い画面で文字をいくつか打てること。本章は1.1より1段階手前にあります。
多くの入門書はこの段階を飛ばします。「ターミナルを開いてください」と1行書いて先へ進みます。しかし、入門者はまさにその1行でつまずきます。ターミナルがどこにあるのか、何をインストールすればいいのか、インストール中に赤い文字が出たらどうすればいいのか。最初の1行で止まった人は、1.1までたどり着けません。本章の目標はただ一つ、最初の1行でつまずかせないことです。
本章は五つの部分で構成されています。インストール、アカウントとログイン、料金プランの考え方、ターミナルサバイバルキット、そして「5分で初回実行」チェックリストです。順番に進めば、1.1の席に着く準備が整います。
flowchart LR
A["1.0 準備段階
(この章)"] --> B["1.1 初めての出会い
(カーソルの前に座る)"]
A1["① インストール"] --> A2["② アカウント・ログイン"]
A2 --> A3["③ 料金プランの考え方"]
A3 --> A4["④ ターミナルサバイバルキット"]
A4 --> A5["⑤ 5分で初回実行"]
A5 --> B
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class A1,A2,A3,A4,A5 human
class B pass
インストールは公式の案内に従うのが原則です。ツールは頻繁に変わりますし、非公式な経路で入手したインストールファイルは危険です。そのため本書はダウンロードリンクを載せず、公式の入り口を見つける方法を案内します。検索窓に「Claude Code 公式ドキュメント」または「Claude Code install」と入力すれば、Anthropicの公式ドキュメントページが最初に出てきます。インストールコマンドは、そのページのものをそのまま使うのが最も安全です。
全体像は知っておくとよいでしょう。Claude Code(クロードコード。本書では英語表記に統一します)はターミナルで動くツールで、通常は1行のコマンドでインストールします。OSごとに流れが少し異なります。
| OS | 準備するもの | インストールの流れ(概念) |
|---|---|---|
| Windows | PowerShell(標準搭載) | 公式ドキュメントのインストールコマンド1行をPowerShellに貼り付け |
| macOS | ターミナル(標準搭載) | 公式ドキュメントのインストールコマンド1行をターミナルに貼り付け |
| Linux | ターミナル | 公式ドキュメントのインストールコマンド1行をターミナルに貼り付け |
三つのOSとも流れは同じです。「ターミナルを開く → 公式ドキュメントの1行を貼り付ける → Enter」。コマンドを暗記する必要はありません。公式ドキュメントからコピーして貼り付けるのが定石です。
インストール中に赤い文字(エラー)が出ても、慌てる必要はありません。入門者が出会うインストールエラーは、ほとんどが二つのうちどちらかです。権限の問題か、前提となるツール(例:Node.jsのようなランタイム)がない場合です。赤い文字が出たら、その文章全体をそのままコピーして検索するか、AIに聞けば十中八九解決します。エラーメッセージは敵ではなく手がかりです。
インストールがうまくいったか確認する方法:ターミナルに
claude --versionと打ってEnter。バージョン番号が1行表示されればインストール成功です。「コマンドが見つかりません」のようなメッセージが出る場合は、まだインストールされていないか、ターミナルを開き直す必要があるケースです。ターミナルを完全に閉じてから開き直し、もう一度確認してみましょう。
インストールが終わったからといって、すぐに使えるわけではありません。Claude CodeはAnthropicのAIモデルを借りて使うツールなので、誰が使っているのかを確認するログインの段階が必要です。
流れは単純です。ターミナルでclaudeを初めて実行すると、ログインの案内が表示されます。通常はWebブラウザーが自動で開くので、そこでAnthropicアカウントでログインします(アカウントがなければ、その画面で新規作成できます)。ログインが終わると、ブラウザーには「もうターミナルに戻って大丈夫です」のような案内が、ターミナル側にも完了の表示が出ます。
ここで入門者がよくつまずくポイントが2か所あります。
一つ目は、ブラウザーが自動で開かない場合です。このときはターミナルに長いアドレス(URL)が1行表示されます。そのアドレスをコピーして、ブラウザーのアドレスバーに貼り付けて開けば大丈夫です。行き詰まったわけではなく、手動でもう1段階進めばよいだけです。
二つ目は、アカウントの種類を混同する場合です。Webチャット(Claude.ai)で使っていたアカウントと、Claude Codeのアカウント・料金がどうつながるかは、時期によってポリシーが異なることがあります。ログイン画面の案内と公式ドキュメントに従うのが最も正確です。初回実行の画面の指示どおりに進めば、ほとんどの場合は問題なくログインできます。
ログインは一度しておけば、そのPCでは維持されます。毎回やり直す必要はありません。
入門者が一番不安に思うのは「お金がいくらかかるのか」です。文字を打つたびに料金が発生するのではないか、という漠然とした恐れがあります。全体像を先につかめば、この不安は小さくなります。料金の方式は大きく二つに分かれます。
| 方式 | 課金形態 | たとえ | 向いている人 |
|---|---|---|---|
| 定額サブスクリプション | 月額固定 | 通信の定額プラン | 入門者・日常利用 |
| API従量課金 | 使った分だけ(トークン単位) | 電気メーター | 大量処理・自動化・開発連携 |
定額サブスクリプションは、月単位で決まった金額を払い、上限まで使う方式です。携帯電話の定額プランに似ています。毎月同じ金額なので予測がしやすく、「1行打つたびにいくら」を気にする必要がありません。そのため、入門者は定額サブスクリプションで始めるのが気楽です(著者の推定。正確なプラン構成と上限は時期によって変わるため、公式の料金ページで確認してください)。上限を超えたら、次の周期まで待つか、上位プランに上げます。
API従量課金は、実際に使った量(トークン)に比例して課金される方式です。電気メーターのように、使った分だけ請求されます。大量処理や自動化パイプライン、ほかのプログラムとの連携に向いています。うまく使えば効率的ですが、入門の段階では、使用量の感覚がつかめるまでコストの予測が難しいことがあります。
トークンとは何か、なぜそれを単位に課金するのかは、1.2(AIモデル・トークン・ハーネス)で詳しく扱います。ここでは一つだけ覚えておけば十分です。入門者は通常、定額サブスクリプションで始めます。毎月の金額が固定なので、「使っているうちに高額請求が来るのでは」という恐れなしに練習できるからです。プラン名・価格・上限は頻繁に変わるため、本書は特定の数字を載せていません。本書の内容は2026年半ばの時点を基準に書かれており、料金プラン・モデル・機能はその後も変わり続けます。現在の値は公式の料金ページで確認するのが最も正確です。
1行まとめ:使うたびにお金が出ていくのではという恐れ → 定額サブスクリプションなら毎月固定。入門は定額で始めると安心です。
いよいよ最大の壁、黒い画面です。1.1が「点滅するカーソルの前でたじろぐ」から始まる理由がここにあります。GUIで24年働いてきた手にとって、ターミナルは不慣れなものです。しかし、最初の1行でつまずかないために必要なコマンドは、それほど多くありません。次の六つで十分です。
| コマンド | 読み方 | すること | たとえ |
|---|---|---|---|
pwd |
ピーダブリューディー | 今自分がどのフォルダーにいるかを表示 | 「ここはどこ?」 |
ls |
エルエス | 今のフォルダーの中に何があるかを一覧表示 | フォルダーウィンドウを開いて見る |
cd 폴더이름 |
シーディー | そのフォルダーの中に入る(폴더이름の部分に入りたいフォルダー名を入れます) |
フォルダーのダブルクリック |
cd .. |
シーディー ドットドット | 1段階上のフォルダーに出る | 「戻る」ボタン |
Enter |
エンター | 打ったコマンドを実行 | 「OK」ボタン |
Ctrl + C |
コントロールシー | 今動いているものを中断 | 停止ボタン |
(Windows PowerShellでもls・cd・pwdはそのまま通ります。macOS・Linuxも同じです。つまり、この六つはOSを選びません)。
この六つですることを図にすると、次のようになります。ターミナルでの移動は、結局フォルダーの中と外を行き来することであり、GUIでフォルダーをダブルクリックしたり「戻る」を押したりするのと同じ動作です。
flowchart TD
Q["pwd
ここはどこ?"] --> L["ls
ここに何がある?"]
L --> D{"入るフォルダーが
見えるか?"}
D -- "はい" --> IN["cd フォルダー名
中に入る"]
D -- "いいえ、上へ" --> UP["cd ..
外に出る"]
IN --> L
UP --> L
RUN["コマンドを打ったら"] --> ENT["Enter
実行"]
STUCK["何かが止まったように見えたら"] --> STOP["Ctrl + C
中断してカーソルを取り戻す"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
class Q,L,IN,UP,ENT,STOP code
class D human
黒い画面が怖い本当の理由は、「打ち間違えたら壊れそう」という感覚です。しかし、上の六つの中に何かを壊すコマンドはありません。pwd・ls・cdは見るか移動するだけで、ファイルを消したり変えたりしません。Enterは実行、Ctrl + Cは中断にすぎません。ですから、この六つはいつでも安心して打って大丈夫です。
画面が止まったように見えることがあります。コマンドを打ったのにしばらく反応がなかったり、カーソルが別の行で点滅して、さらに何かの入力を待っているように見えたりするときです。そんなときはCtrl + Cを一度押せば、たいてい元のカーソルに戻ります。この「停止ボタン」があると知っているだけでも、黒い画面はずっと怖くなくなります。行き詰まったらCtrl + Cで抜け出して、やり直せばいいのです。
最後に、打った文字が大量にたまって画面がごちゃごちゃしてきたら、画面を空にできます。Windows PowerShell・macOS・Linuxのいずれもclearコマンドで空にします。空にしても、やったことが消えるわけではなく、見えている文字が片付くだけです。
ここまで来たら、準備は完了です。次の五つの項目を5分以内に通過できれば、1.1の席に着く資格ができたということです。一つでもつまずいたら、該当する節(1.0.1〜1.0.4)に戻ってください。
flowchart LR
C1["① ターミナルが
開く"] --> C2["② claude --version
バージョンが表示される"]
C2 --> C3["③ claudeを実行 →
ログイン完了"]
C3 --> C4["④ pwd・lsで
自分のフォルダーが見える"]
C4 --> C5["⑤ Ctrl+Cで
抜け出せる"]
C5 --> OK["✅ 1.1へ"]
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class C1,C2,C3,C4,C5 human
class OK pass
claude --versionと打つとバージョン番号が1行表示される(インストール確認)claudeを実行するとログイン済みになっている(または案内どおりにログイン完了)pwdで現在地を、lsでフォルダーの中身を見られるCtrl + Cで抜け出せる五つの項目をすべて埋めたなら、黒い画面はもう未知の壁ではありません。ツールがインストールされ、ログインが済み、料金方式の全体像を知り、画面の中で移動し、止め方も分かっています。1.1はこの準備の上から始まります。点滅するカーソルの前に座り、初めて「このフォルダーに何があるか要約して」と打ってみる、その席に向かえば大丈夫です。
本書の前半(第1部・第2部)は、自然言語のプロンプトだけでついて来られます。ただし、第4部以降の一部の章では、小さなPythonスクリプトを直接動かします(例:pip install pyyaml、pip install pyvis)。Pythonが初めてでも大丈夫です。道は二つあります。
一つ目は、自分でインストールする道です。Pythonはpython.orgからダウンロードしてインストールし(インストール画面で[Add to PATH]に必ずチェックを入れます)、ターミナルでpython --versionと打って確認します。pipはPythonと一緒にインストールされるパッケージ管理ツールで、pip install pyyamlのように必要なパッケージを1行で取得します。
二つ目は、AIに任せる道(推奨)です。より簡単なのは、環境構築そのものをAIにやらせることです。ターミナルでこう頼めば大丈夫です。
Pythonが入っているか確認して、なければ自分のOSに合ったインストール方法を教えて。
あわせて、この章で必要なpyyamlパッケージをインストールするコマンドを1行でちょうだい。
(プロンプトの大意:「Pythonが入っているか確認して、なければ自分のOSに合ったインストール方法を教えて。あわせて、この章で必要なpyyamlパッケージをインストールするコマンドを1行でちょうだい」という依頼です)。
AIが環境を点検し、インストールコマンドを作ってくれます。行き詰まったら、その場でエラーメッセージをそのまま貼り付けて「このエラーはどう解決する?」と聞けば大丈夫です。ツールを動かす章ごとに、このパターン一つで十分です。Python・pipが負担に感じられる段階では、その章の「一人ミニ版」が、コードなしで進めるより軽い道を案内します。
setup 1. お使いのOSのターミナルを開きましょう(Windows:PowerShell、macOS:ターミナル)。 2. 「Claude Code 公式ドキュメント」を検索して、公式のインストール案内ページを開いておきましょう。 3. タイマーを5分にセットしましょう。1.0.5のチェックリストの五つの項目を通過するのが目標です。
prompt(1行ずつ、順番に打ってみましょう。これはコマンドであって、自然言語の質問ではありません)
① claude --version # バージョンが表示されればインストール成功
② pwd # 今自分がどのフォルダーにいるか
③ ls # このフォルダーに何があるか
④ cd .. # 1段階上に出る (そしてもう一度ls)
⑤ claude # Claude Code 実行 (ログイン案内が出たら従う)
(コメントの大意:① バージョンが表示されればインストール成功/② 今自分がどのフォルダーにいるか/③ このフォルダーに何があるか/④ 1段階上に出る(そしてもう一度ls)/⑤ Claude Codeを実行(ログイン案内が出たら従う))
verify
- ①でバージョン番号が1行表示されれば、インストールは完了です。「コマンドが見つかりません」と出たら、ターミナルを閉じて開き直し、もう一度試してみましょう。
- ②・③・④でフォルダーを見たり移動したりしている間、何も壊れないことを自分の目で確かめましょう。この三つは見る・移動するだけの安全なコマンドです。
- ⑤の実行中に止まったように見えたら、Ctrl + Cで抜け出しましょう。抜け出せたなら、「停止ボタンがある」ことを体で確認できたということです。
チームも会社のフォルダーもない個人なら、まずインストール(①)と、Ctrl + Cで抜け出すことだけ先に身につけておきましょう。claude --versionで「ツールが入った」ことを、Ctrl + Cで「行き詰まっても抜け出せる」ことを確認できれば、黒い画面の恐怖の半分は、一人でも5分以内に片付きます。料金はまず定額サブスクリプションで始めれば、コストの心配なく思う存分練習できます。
黒い画面が立ち上がりました。カーソルが点滅しています。その前に、24年目のゲームプランナーが座っています。PPTとExcel、WikiとFigmaで24年間働いてきた手が、キーボードの上で一瞬止まります。その昔、パソコン教室というものがあった時代のDOSのようにも見えます。それ以降、ターミナルというものはプログラマーの机の上でしか見かけない存在でした。何を打てばいいのか分からず、打ち間違えたら何かが壊れそうな気がします。このためらいが、本書の出発点です。
ほとんどの人は、ここでウィンドウを閉じます。そして会議でまた「うちも何かやってみないと」を繰り返します。本章は、そのウィンドウを閉じずに最初の30分を持ちこたえる席に、一緒に座ります。大げさな導入戦略ではなく、点滅するカーソルの前で何を打ってみれば距離感がほぐれるのかを、手に握らせることが目標です。
ゲームプランナーが初めてClaude CodeのようなAIコーディングツールの前に座ると、物珍しさと居心地の悪さが同じ場所で同時に湧き上がります。二つの感情が衝突するという事実そのものが、導入の最初の手がかりです。
物珍しさの理由ははっきりしています。半日ずつ食いつぶしていたマスターデータの整合性チェックが数分で終わり、だらだらと長くなった議事録が決定事項の表に要約され、1年前に埋もれていた仕様書を自然言語の一行で再び引っ張り出せるのです。
居心地の悪さの理由も同じくらいはっきりしています。黒い画面、点滅するカーソル、英語のコマンドが、日々の作業風景とあまりにも違います。ゲームプランナーの一日はGUIの上を流れていくのに、黒いターミナルに文字を打ち込む行為は、職務のアイデンティティとうまく結びつきません。ただし、この居心地の悪さはツールの欠陥ではなく、GUIに慣れた人間の適応コストです。この点を認めるだけで、距離感の半分はすでに片づいています。
本書は、その距離感を縮めるための本です。1.1では、最初の出会いの席に一緒に座り、何を見て、何を試し、何はいったん後回しにしてよいのかを整理します。
ゲーム企画は、ほかの職種より遅れてAIの流れに合流しました。コードを扱う人たちが先に、デザイナー・アーティストがその次に入っていきました。プランナーは「うちも何かやってみないと」を繰り返しながら先送りするパターンがよく見られました。
先送りした理由には合理性がありました。プランナーの成果物はコードのように定型化されておらず、テキスト・表・ダイアグラム・会議・口頭合意が一緒くたに混ざります。AI出力の信頼性は低く見え、もっともらしい嘘が危険であり、AIがゲームシステムを本当に理解しているのかも疑わしかったのです。
しかし2024〜2026年の間に、三つのことが変わりました。
第一に、AIモデルの推論能力が臨界点を超えました。単純な文章生成を超えて、複雑なシステム設計・整合性検証・影響分析を扱います。最新のClaude系列は、ゲーム企画ワークフローのかなりの部分を補助します。ただし、すべてを任せられるという意味ではありません。検証と責任は依然として人間の持ち分です(補助の幅は作業の種類とチームの成熟度によって大きく分かれます — 著者の推定、未検証)。
第二に、ハーネス(harness)が成熟しました。Claude Codeのようなツールは、単なるチャットではありません。ファイルを直接読み書きし、コマンドを実行し、その結果を再び入力として受け取ります。人間の働き方に似ています。
第三に、メモリー・atom・skillといった運用技法が定着しました。AIを一度使って終わりにするのではなく、チームの知識を蓄積して、時間が経つほど賢くしていく方法論が根づいたのです。本書の後半が扱う核心が、まさにこの蓄積です。
この三つがそろうと、ゲームプランナーにとってもAI導入が合理的なタイミングになります。これ以上遅くなる前に始めるほうが得です。
プランナーがよく目にするAIツールは2種類です。チャット欄に質問を投げるチャットボット型(ChatGPT、Claudeウェブアプリ)と、コードエディターの中で自動補完を行うエディター統合型(Cursor、Copilot)です。
Claude Codeは第三の部類です。CLI(ターミナル)で動作し、人間の作業環境全体にアクセスします。三つの部類がどこで分かれるのかを1枚で見ると、次のとおりです。
ゲームプランナーの作業はコードではなく、文書・表・関係です。Claude Codeの強みは、人間が働くフォルダー全体を見て、理解し、操作するという点です。チャットボットのように、毎回資料をコピーして貼り付ける必要はありません。
オフィスにたとえると、チャットボット型は受付デスクです。質問1回に回答1回、資料は毎回出し直さなければなりません。Claude Codeは隣の席の同僚に近い存在です。資料がどこにあるかを知っていて、自分の手でファイルを開き、結果を整理してまた机の上に置いてくれます。同じClaudeでも、どの机に座らせるかによって得意な仕事が変わるのです。
インストール・設定は1.0で扱います。1.1では、最初の30分に何を経験すれば距離感が縮まるのかに集中します。最初の30分は四つの区間に分かれ、区間ごとに距離感の一部が段階的に消えていきます。
flowchart LR
A["見る(5分)
このフォルダーを要約して
→『自然言語が通じる』"]
B["検索(5分)
キーワード・最近のファイル探し
→『毎日の雑務を肩代わりする』"]
C["変換(10分)
議事録→表、Excel→Markdown
→『時間を生み出してくれる』"]
D["検証(10分)
直前の出力を疑う
→『このツールは疑われるべきだ』"]
A --> B --> C --> D
D --> E["距離感80%解消"]
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class A,B,C ai
class D human
class E pass
ターミナルを開いてClaude Codeを実行すると、黒い画面が立ち上がり、カーソルが点滅します。多くの人はここで立ち止まりますが、立ち止まると、距離感がほぐれる最初の機会を逃してしまいます。自然言語の一文を入力します。
このフォルダーに何があるのか要約して
コードでもコマンドでもない、一つの文です。Claude Codeはフォルダー構造をスキャンし、どんなファイルがあるのか、何を扱うプロジェクトに見えるのかを整理して答えます。企画文書が入ったフォルダーでこの一行を打つと、おおよそ次のような応答が返ってきます。
このフォルダーに何があるのか要約して
このフォルダーをスキャンしました。ゲームデータ・企画文書の作業フォルダーと見られます。
ファイル種類 個数 内容の推定
───────────── ──── ───────────────────────────
.xlsm 12 データシート (アイテム・クエスト・NPCなど)
.md 34 企画書・議事録の草案
.png 88 UIモックアップ・リファレンス画像
サブフォルダー 6 /data /docs /ref /meeting /export /tmp
観察:
- /meeting フォルダーに直近1週間の議事録5件が溜まっています。
- item_table.xlsm と quest_table.xlsm が同じIDカラムを
共有しているように見えます(FK候補)。
- /tmp フォルダーに一時exportファイルが多数 — 整理対象かもしれません。
何から先に見てみましょうか?
この一度の応答で、「このツールはコマンドではなく自然言語を理解する」という事実が体に染み込みます。フォルダーを直接開かなくても、何がどこにあるのかが整理された見取り図を受け取る経験が、距離感の半分をその場で削り取ります。
小さな検索をさせてみます。「直近1週間に修正されたMarkdown(マークダウン)ファイルを5個教えて」や「『リワード』という単語が入ったファイルはどこにある?」のような頼み方です。プランナーが毎日Find in Filesを開き、キーワードを打ち込み、結果をスクロールしていた仕事を、Claude Codeは自然言語で受け取り、整理して返してくれます。このツールが毎日の雑務を肩代わりしてくれるという感覚が、二度目の5分で根を下ろします。
小さな変換をさせてみます。「この議事録から決定事項だけを抽出して表にして」や「このExcelシートの1〜10行をMarkdownの表に変えて」のような頼み方です。手作業なら5〜10分かかる変換が短い時間で処理される経験は、ツールが検索を肩代わりするにとどまらず、時間を生み出してくれるという感覚につながります。
ここで止まると危険なのは、AIがもっともらしい嘘をつくからです。たった今つくられた出力を、ツール自身に疑わせます。
さっき作ってくれた表が原本と一致するか、もう一度確認して。抜けている項目はある?
するとClaude Codeは原本を読み直し、自分の出力と一行ずつ突き合わせて、不一致を報告します。実際、この区間ではこんな画面がよく出てきます。
さっき作ってくれた表が原本と一致するか、もう一度確認して。抜けている項目はある?
原本(meeting_0603.md)とさっき作った表を再度照合しました。
3件の不一致を見つけました。
⚠ 欠落 「チュートリアル報酬50%下方修正」 — 原本の12行目にあるが表から漏れ
⚠ 誤記 担当者「キムOO」 → 表には「イOO」と誤って記入
⚠ 曖昧 「リワード再協議」 — 決定なのか保留なのか原文が不明確。
私が独断で「決定」に入れましたが、確認が必要です。
修正した表を作り直しましょうか? 曖昧な項目はどう処理するか
教えていただければ反映します。
ツールが自分の出力を疑えるという点、そしてその疑いを人間と一緒に行うという点が、最後の10分の核心です。三つ目の項目のように「自分が独断で判断したので確認してほしい」と問い返す態度こそが、検証を人間の手に残す安全弁です。
30分が経つころには、距離感の80%は消えています。残りの20%は、続く章で少しずつ縮んでいきます。
著者が企画ディレクターとして運営しているあるMMORPGプロジェクト(以下「プロジェクトA」)では、企画チーム(4〜5人)と約6か月間、Claude Code中心のワークフローを運用してきました(プロジェクトA全体の開発チームは中規模、10〜50人)。いくつかの風景を書き写します。
最も手応えのある1件だけを、実測として切り出してみましょう。30余りのマスターデータシートにまたがるFK(外部キー)整合性チェックです。あるシートのIDがほかのシートで正しく参照されているかを人間の目で追いかける作業で、シートが増えるほど組み合わせは幾何級数的に膨れ上がります。
半日から5分へ。この一行を一般化するつもりはありません。ほかの作業は削減幅がもっと小さかったり、レビュー時間が新たに付いたりもします。同じ6か月の間に見たほかの風景は、方向と比率だけで記します。
各ツールが一度つくられたあと、6か月間に積み上がった節約時間は、人週(person-week)ではなく人月(person-month)単位だというのが著者の体感です(正確な合算は未測定、推定値)。その時間で、より深い企画に集中できるようになりました。
こうしたツールは、一度つくられると長く働きます。ただし「長く」がそのまま「無人」ではありません。運用する人間と検証の仕組みがそろってこそ長持ちし、ツールだけを残して人間が去れば、2四半期のうちに朽ちていきます。本書の後半の部では、上記のツールそれぞれがどのようにつくられ、運用されるのかを扱います。
ゲームプランナーがAIツールの前でよく抱く不安を、正直に押さえておきましょう。目をそらさずに扱うことが、導入の第一歩です。
「AIが自分の仕事を奪う」という不安は、半分は当たっていて半分は外れています。単純な雑務(整合性チェック・文書変換・検索)はAIが置き換えますが、決定・優先順位・プレイヤー感情の設計は置き換えられません。むしろAIをうまく使うプランナーは、雑務から解放されて本質に集中します。自問してみましょう。「自分の仕事のうち、雑務と本質の比率は?」雑務が70%なら、本質の30%はそのまま本人の持ち分であり、その30%がより重要になることが核心です。
「AIが間違えたら責任は誰が取るのか」という質問もよく出ます。プランナーの決定に対する責任は、いつでもプランナーのものです。AI出力を検証なしにそのまま使うのはプランナーのミスであって、AIのミスではありません。検証手順を一緒に設計することが、導入の一部です。1.1.3の最後の10分で見た「自分の出力を疑わせる」が、その手順のいちばん小さな種です。
「コードがよく分からないから使えない」という不安は、すぐにほどけます。Claude Codeは自然言語で動くので、コードを知らないなら知らないなりに始められます。AIがつくったスクリプトを一緒に読むため、数か月もすれば簡単なスクリプトは読んで修正できるようになります。学習が自動的についてくるのです。
「ツールの変化が速すぎる」もよくある心配です。モデル・機能・トレンドをすべて追いかけようとすると疲れてしまいます。自分のワークフローに役立つ1〜2個の機能だけを深く身につけ、残りは必要なときに見れば十分です。
あらかじめ断っておきます。1.1.3の応答画面が滑らかに見えたとしても、実際の最初の30分には、的外れな答え、見当違いのファイル要約、もたつく出力が混ざって出てきます。それが正常です。本書は滑らかな成功談ではなく、外れた出力をどう要求し直して正すのかのほうを、多く扱います。
本書は24の部で構成されています。最初から最後まで順番に読む必要はありません。次の三つのパターンから、自分の状況に合うものを選んでください。
| パターン | ルート | 所要期間 |
|---|---|---|
| 導入パターン | 第1部(導入)→ 第2部(情報アーキテクチャ)→ 自分の分野を1つ | 1〜2か月 |
| 全体パターン | 第1〜2部 → 分野別(3〜15)→ プロセス(16〜19)→ 運用(20〜24) | 6か月〜1年、チーム単位向き |
| 問題解決パターン | 付録の索引 → 該当する章へ逆方向 | 約1週間、今すぐ問題があるとき |
どの道を選べばよいか見当がつかないなら、自分がどれに近いかで分ければ大丈夫です。ゲーム外のプランナー・PM・一般のオフィスワーカーなら、上の三つのパターンの代わりに「一般職務の道」(第1・2部 → 第17部 議事録 → 第16部 コラボレーション → 第18部 意思決定 → 第21・22部 自己改善・ガバナンス)をおすすめします — ゲームドメインの章を飛ばしても核心の骨格はそのまま立ち、各章の「ゲーム外への応用」ボックスが、自分の職務へ読み替える橋になります(索引は付録F.5)。時間がなければ、17.1 → 16.2 → 22.1 → 21.1の4章だけたどっても構いません。AIツールを始めたばかりの専門外の読者なら、「導入パターン」で自分の分野(または最も近い分野)を一つだけ最後までやり抜き、深い分野の部(4・8・11など)は導入部の「非専門者のための一行」だけ押さえて、必要なときに本文へ降りていけば大丈夫です。
本書のすべての章は、学術的な深さまでは行かず、運用可能な水準で止めています。中規模チームで6か月間実際に回った技法をそのまま書き写し、小さく始めて大きく育てる道を一緒に歩くことが目標です。
1.1は距離感を縮める章でした。1.2では一段階内側に入り、このツールの基本メカニズムを、ゲームプランナーに親しみやすい言葉で説明します。モデル・トークン・コンテキスト・ハーネスといった単語を怖がらなくて済むようにすることが、1.2の目標です。本格的なセットアップ(メモリー・権限・settings.json)は1.3で扱います。
setup 1. ターミナルを開きましょう(WindowsはPowerShell、macOSはターミナル)。 2. 企画文書が集まったフォルダーに移動してから、Claude Codeを実行しましょう(インストールは1.0)。 3. タイマーを30分にセットしましょう — 5分(見る)・5分(検索)・10分(変換)・10分(検証)。
prompt(1区間に1行ずつ、順番に打ってみましょう)
① このフォルダーに何があるのか要約して
② 「リワード」という単語が入ったファイルはどこにある?
③ この議事録から決定事項だけ抜き出して表にして
④ さっき作った表が原本と一致するか、もう一度確認して。抜けている項目はある?
verify - ①でフォルダー構造の要約が実際のフォルダーと合っているか、目で突き合わせましょう。 - ④の不一致報告が1件でも出れば成功です。AIが自分の出力を疑う場面を直接見たという意味です。 - 的外れな答えが出ても失敗ではありません。「さっきの答えは間違っている、このファイルだけ見直して」のように要求し直すところまでが、最初の30分の練習です。
チームも会社のフォルダーもない個人なら、自分のPCの適当な作業フォルダー一つ(例:ダウンロードフォルダー、メモフォルダー)で、上のprompt①と④だけ打ってみましょう。①で「自然言語が通じる」を、④で「出力を疑わせられる」を確認できれば、本章の核心の二つは、一人でも5分以内に体感できます。
一つの作業を終えて、使用量を見たときのことです。今週の議事録が5件フォルダーに溜まっていて、月曜午前のスタンドアップの前までに「決定されたことだけ」を1枚にまとめなければなりません。私はClaude Codeのウィンドウに一行を打ち込みます。「このフォルダーの議事録から決定事項だけを抜き出して表にして」。エンターを押してから0.4秒ほど、画面の下のほうで小さな灰色の文字が点滅します。
Reading meeting-2026-05-25.md ... (1,840 tokens)
Reading meeting-2026-05-27.md ... (2,310 tokens)
この灰色の文字が本章のテーマです。韓国語の一文を投げると、ツールはそれをトークンに刻み、ファイルをトークンとして読み込んでモデルに入れ、モデルの答えを受け取ってファイルに書き込みます。この往復が1回回るたびにコストが計上され、モデルの「視野」に資料が積み上がっていきます。本章では、その灰色の文字の裏で起きていることをゲームプランナーの言葉で分解します。モデル・トークン・コンテキスト・ハーネス、四つの言葉で十分です。
用語メモ - モデル(model):答えを作る頭脳。Opus・Sonnet・Haikuのように、サイズと性格の異なる種類があります。 - トークン(token):文章を細かく刻んだ断片。課金・速度・視野はすべてこの単位で数えます。 - コンテキストウィンドウ(context window):モデルが一度に頭の中に収められるトークンの最大量。 - ハーネス(harness):モデルを働かせる車体。Claude Codeがその例です。
先ほどの灰色の文字は、ランダムなログではなく、決まった循環の中の一つのボックスです。ハーネスがしていることは、結局のところ同じループを高速で回すことです。ファイルを読み込んでモデルに入れ、モデルが「このコマンドを実行せよ」と言えば実行し、その結果を再びモデルに入れます。このループが作業の終わりまで回り続けます。
flowchart TD
Start([人間:一行の指示]) --> Read[ハーネス:ファイル・資料の読み込み
トークンに変換]
Read --> Inject[ハーネス:コンテキストに注入
+ 累積トークンを合算]
Inject --> Model{モデル:次の行動を決定}
Model -->|コマンド実行が必要| Exec[ハーネス:シェルコマンド・スクリプトを実行]
Exec --> Result[実行結果を
トークンとして再入力]
Result --> Inject
Model -->|回答が準備できた| Write[ハーネス:ファイル書き込み・出力]
Write --> Verify{検証通過?}
Verify -->|失敗| Inject
Verify -->|通過| Done([結果を保存])
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class Start,Verify human
class Read,Inject,Exec,Write code
class Model ai
class Result data
class Done pass
この図で人間が手を触れるボックスは、いちばん上(指示)といちばん下(検証結果の確認)の二つだけで、真ん中のループはハーネスが自律的に回します。議事録5件を読み込む間に灰色の文字が5回点滅したのは、Read → Injectのボックスを5周したということです。Webチャットなら、ファイル5個を自分で開いてコピー&ペーストしなければなりません。その労働をハーネスが肩代わりしてくれること。これがチャットボットとCLI型ハーネスを別の道具にする決定的な違いです。
ループが1周するたびに、Injectのボックスで累積トークンが合算されます。だからトークンを先に理解しないと、このループのコストも限界も見えてきません。まずトークンから見ていきます。
トークンは文字ではなく、モデルが文章を刻んだ断片です。経験則として、英語はおよそ4文字で1トークン、韓国語はおよそ2文字で1トークンに近い値になります(公式の換算ではなく、あくまで運用上の目安です。実際の値はモデルのトークナイザーが決め、文章ごとに異なります)。韓国語でスペース込み20文字なら、だいたい10トークン前後です。
あの議事録作業をトークンで追いかけてみます(以下の数値は同一作業の単一測定1回分です。議事録の分量や要約の長さによって変わるため、絶対値ではなく桁数と比率で読むことをおすすめします)。
| 段階 | 内容 | トークン(入力) | トークン(出力) |
|---|---|---|---|
| 指示 | 「決定事項だけを抜き出して表に」の一行 | \~25 | — |
| 議事録の読み込み ×5 | mdファイル5個の本文 | \~10,400 | — |
| 分類ルールの注入 | 会議カテゴリーのatom 1件(JIT) | \~480 | — |
| モデルの推論・表の作成 | 決定12件を表に | — | \~1,600 |
| 検証の再入力 | Linterが見つけた漏れ1件の再質問 | \~320 | \~210 |
| 累計 | \~11,225 | \~1,810 |
二つのことが目に留まります。第一に、私が打った指示は25トークンなのに、作業全体では入力だけで1万1000トークンを超えています。コストのほとんどすべてが、私の文章ではなくツールが読み込んだ資料から発生しているのです。第二に、出力(1,810)は入力(11,225)の6分の1程度です。企画業務の自動化の大半は、このように多く読んで少なく書きます。だからコストを減らすには、出力を磨くより入力資料の量を制御するほうがはるかに効果が大きいのです。
作業が終わったあとに/contextを打つと、そのセッションがコンテキストをどれだけ占有したかが見えます。トークンは意識しなければ印刷用紙のように無意識のうちに流れ出ていきますが、一度可視化すると姿勢が変わります。この可視化が節約の出発点です。
トークンを制御する道具は、抽象的な節約精神ではなく、入力資料を細かく扱う具体的なテクニックです。
このうち1番のJIT注入は、本書の作業環境で実際に回っている仕掛けです。入力が一行入ってくると、inject_memory.pyフックがメモリーatomをスコア順にマッチングして上位数件だけを選んで注入し、失敗しても作業の流れを止めません(実装の詳細は1.3で詳述)。「必要な資料だけ、上位数件だけ、失敗しても静かに」というトークン節約の原則が、コード1ファイルにそのまま収まっています。
モデルは自動車のエンジンのようなもので、Claude Codeという同じ車体にOpus・Sonnet・Haikuという異なるエンジンを載せ替えられます。エンジンを替えると作業の性格が変わります。
企画作業に当てはめると、こう分かれます。システム設計のレビューや、複数の資料を統合するGDD(Game Design Document、詳細仕様書)ドラフトの合成のように深い推論と整合性が必要な仕事はOpus、議事録からの決定抽出や日次要約のような日常作業の大半はSonnet、マスターデータの単純なフォーマット変換のようにほとんど判断のいらない仕事はHaikuに振ります。前の節の議事録作業をSonnetで回したのもこの基準です。決定を選んで表に移す仕事には、深い推論よりバランスと速度が合っています。
導入初期に誰もがはまる罠があります。すべての作業をいちばん良いエンジン、つまりOpusで回したくなる衝動です。その衝動に従うと、コストと速度の負担がそのまま運用の負担として返ってきて、作業にモデルを合わせる感覚が身につきません。運用の本当の技術は、毎回頭の中でモデルを選ぶことではなく、パターンが固まったあとに自動化として固定しておくことです。
こうした固定は、settings.jsonやスラッシュコマンドの中にモデルを明示しておくことで行います(1.3で詳述)。一度固めれば、毎回選ぶ手間がなくなります。
モデルはおおむね半年周期で新バージョンが出て、同じ名前でも4.5と4.6は別物です。新バージョンが出たら、ワークフローの中核となる作業5個だけを同じ入力で比較します。全部テストしようとすると疲れ果てます。5個の結果の違いだけでも、乗り換えるかどうかの判断には十分です。
ループが1周するたびにInjectのボックスでトークンが累積すると述べました。その累積がぶつかる天井がコンテキストウィンドウ、つまりモデルが一度に処理できるトークンの最大量です。人間でいえばワーキングメモリー(working memory)です。
先ほどの議事録作業は累積入力が1万1000トークン台で、200Kの天井に対して6%あまりと余裕があります。しかし作業を切り替えずに同じウィンドウでセッションを長く引きずると、天井に近づいていきます。満杯になると古い内容が切り捨てられ、モデルが前半の「記憶」を失い始め、自動圧縮が発動して以前の会話が要約版に置き換えられます。
この天井を制御する習慣は四つです。
| パターン | いつ |
|---|---|
| セッション分離 | 別のテーマに移るときは新しいセッションを開く |
| 明示的な圧縮 | 一つの作業が終わったら核心だけ残して圧縮する |
| メモリーの外部化 | よく使う資料はatomに切り出し、JITでその都度注入する |
| コンテキストの可視化 | /contextで現在の使用量を目で確認する |
ゲームプランナーがよく出会う重いケースは、会議資料・仕様書・マスターデータが同時に必要な作業で、こういうときに1Mオプションが役立ちます。ただし1Mにはコストと速度の負担が伴うので、普段は200Kで十分、資料の束が本当に大きいときだけ取り出します。
ループ図のいちばん下にある검증 통과?(検証通過?)のボックスに戻ります。このボックスがなければ、モデルのもっともらしい嘘がそのままファイルに保存されます。モデルは自信満々に間違った答えを正解のように出すことがあり(ハルシネーション、hallucination)、その頻度は世代が上がるほど減りますが、0にはなりません。だから検証を常設のボックスとして置きます。
企画の現場で危険なハルシネーションは具体的です。存在しないマスターデータのカラムを引用したり、間違った数式でバランスを計算したり、会議で決定されていない事項を決定済みのように要約したりします。議事録作業でいちばん怖いのは三つ目です。「議論だけして保留になった件」が決定の表にしれっと載ってくるケースです。
検証には五つのパターンがあります。
五つを毎回すべて行うのではなく、作業のリスクに応じて1〜3個を選びます。議事録作業では、4番のLinter(「決定に主体・内容・期限がすべて揃っているか」)と3番のサンプルチェック1回を組み合わせました。先ほどのトークン表の最後の行「検証の再入力320トークン」が、まさにLinterが漏れを捕まえてモデルに問い直したその往復で、ループ図でいえば검증 실패 → Inject(検証失敗 → Inject)としてもう1周回ったということです。
毎回人がすべてを検証していては導入効果が半減するので、検証もまた自動化の対象です。議事録の決定抽出ではLinterが形式の漏れを、マスターデータ変換では行数・合計・外部キーの整合性を、GDD自動生成では主要セクションの欠落を自動検査します。通過すれば人は見なくてよく、通過しなかったものだけを見ます。キャビネットいっぱいの書類のうち、赤い印のついたフォルダーだけを手に取る風景です。人の視線が危険な場所にだけ落ちるように設計すること。それが検証自動化の目的です。
最後に、あの議事録作業を最初から最後まで、四つの言葉がどのように一本の糸に通されるのかを整理します。
| ボックス | 何が起きるか | どの概念 |
|---|---|---|
| 1 | Claude Codeを議事録フォルダーで実行 | ハーネス |
| 2 | 作業が議事録分析なのでSonnetを選択 | モデル |
| 3 | 合算約11Kトークン、200Kウィンドウの範囲内 — OK | トークン・コンテキスト |
| 4 | 会議分類ルールのatomをJITで自動注入 | トークン(節約) |
| 5 | モデルが決定12件を表として出力 | モデル・ハーネスループ |
| 6 | Linterが形式漏れ1件を検出 → 問い直して補強 | 検証(ループ1回追加) |
| 7 | weekly-decisions-2026-W21.mdとして保存・コミット |
ハーネス |
手作業なら、議事録5件を開いて読み、決定だけを選んで書き写し、形式を整えるのに30分かかります。自動化されると5分に縮み、その5分の中で人の手がすることは、検証サンプルを一度ざっと確認することだけです。人の時間が本当に必要な場所(保留の件が決定として誤って載っていないか)にだけ、視線が落ちます。節約された25分ではなく、その視線の行き先が変わったこと。それが核心です。
「Opusが常に優れている」が最もよくある誤解です。コストと速度を無視すればそのとおりですが、単純作業にOpusは無駄遣いです。作業ごとのマッチングが答えです。
「1Mコンテキストが常に必要だ」もよく出てきます。大半は200Kで十分で、1Mには負担が伴うので、本当に大きな資料の束にだけ使います。
「検証は人がやるもの」は半分だけ正しい考えです。自動検証できる部分が大半で、人は残りに集中します。
「トークンは気にしなくていい」は個人作業ではある程度通用しますが、複数人で一緒に使うと累積コストが急速に膨らみます。初期から可視化と節約のパターンを定着させるほうが安全です。
「ハーネスに違いはない」も意外と多い誤解です。同じモデルでも、チャットボットかCLIかで別の道具のように分かれます。先ほど見たコピー&ペースト労働の有無がその違いです。
小さな作業一つで、本章の四つの言葉を自分で回してみましょう。
setup
prompt
このフォルダーのノートから「決定されたこと」だけを選んで
主体・内容・期限の3列の表にして。
保留・議論中の件は除いて、表に入れた各行が
どのファイルから来たのかファイル名も一緒に書いて。
verify
/contextを打って、このセッションが使ったトークンを見てみましょう(思ったより入力が大きいことが確認できます)。一人ミニ版
ツールを始めたばかりなら、上のうち二つだけを押さえてください。第一に、ノートはフォルダーごと任せて、コピー&ペーストを自分でやらないこと(ハーネスループに任せます)。第二に、出力の表は必ずファイル名つきで受け取り、疑わしい行だけ原本を開いて見ること。モデル選択やトークンの可視化は、慣れてから足しても遅くありません。資料を手で運ばないことと、出力を原本と突き合わせること。この二つの習慣だけで、導入の半分は土台が固まります。
新しいセッションを開き、「スキルのクールタイムバランスをちょっと見よう」と入力しました。エンターを押す前、画面の下のほうに小さなグレーの文字が1行、すっと流れていきます。[memory injected: 2 atoms, 1,842 chars]。私は何のファイルも開いていないのに、先週ルール化しておいたクールタイム(クールダウン)ルールの文書が、すでにモデルの入力の前に付け加えられて入っているという意味です。これが、インフラの敷かれた作業環境の最初のシグナルです。ツールを立ち上げた瞬間、ツールが私を覚えているのです。
この場面が可能になるためには、3つのものがあらかじめ整っていなければなりません。AIが何を記憶するか(メモリー)、AIが人の承認なしに何をできるか(権限)、その2つをオン・オフする中央スイッチ(settings.json)です。最初のセットアップにかかる時間は長くても1時間で、その1時間は以降6か月間、毎日節約される時間として返ってきます。ほぼ全額回収できる投資です。
この章は、著者が実際に個人PCで動かしているsettings.jsonの1行、その1行が呼び出すinject_memory.py、そのファイルが読み込む_jit_manifest.jsonを順に開いてたどるウォークスルーです。最後まで読めば、「メモリーが自動注入される」という言葉が、どのファイルのどの行で起きていることなのか、手で指し示せるようになります。
まず結論から見ます。著者の個人PCでメモリー自動注入をオンにしているのは、settings.jsonの中のたった1つのブロックです。
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python ~/.claude/hooks/inject_memory.py"
}
]
}
]
}
}
このブロックの意味を日本語に訳すと、こうなります。「ユーザーがプロンプトを送信するイベント(UserPromptSubmit)が起きるたびに、inject_memory.pyというPythonスクリプトを1回実行せよ」。それだけです。AIが賢くて勝手に記憶しているのではなく、入力が入ってくるたびに、人が登録しておいたスクリプトが1回ずつ割り込む構造です。
settings.jsonはClaude Codeのすべての動作を制御する中央ファイルで、2つの層に分かれています。
~/.claude/settings.json — グローバル。すべてのセッションに適用。チームと共有できる設定。~/.claude/settings.local.json — ローカル。このPCだけに適用。個人PC特有の設定。2つはマージされて適用されます。そのため著者は、チームで共有すべきフックや権限はsettings.jsonに、この自宅PCだけで使う絶対パスや個人ツールのパスはsettings.local.jsonに分けて置いています。コラボレーション時のgitコンフリクトを避け、個人設定がチームのリポジトリに漏れる事故を防ぐ分離です。
よく使う項目は、フックのほかにもいくつかあります。
effortLevel — モデルの推論の深さ。low / medium / high。仕様書の設計のように深い判断が必要な作業はhighにしておきます。permissions — AIが承認なしに実行できるコマンドの範囲(1.3.4で詳述)。enabledPlugins — 有効化されたプラグインのリスト。ここで最も重要な、たった1つの運用習慣があります。settings.jsonは、小さなタイポ1つでツール自体が起動しなくなります。JSONのカンマが1つ欠けるだけでパースが壊れます。だから変更前のバックアップが必須です。著者のPCには実際に、こんなバックアップファイルが残っています。
settings.json.bak_2026-05
settings.local.json.bak_2026-05
日付のsuffixを付けて1部とっておけば、ロールバックは1秒です。gitで管理すればさらに良いでしょう。引き出しの中の古い合鍵のように、普段は使わなくても、鍵のかかったドアの前で必ず一度必要になる瞬間が来ます。
今度は、settings.jsonが呼び出したスクリプトの中に入ります。ウォークスルーの背骨です。コードは100行余りですが、核心は5つの動作です。
flowchart TD
A["セッション: ユーザーがプロンプトを送信\n例: 'スキルのクールタイムバランスを見よう'"] --> B["UserPromptSubmitフックが発動\nsettings.jsonがinject_memory.pyを実行"]
B --> C["_jit_manifest.jsonをロード\natom 17個のメタデータ"]
C --> D["scoreの降順でソート\n→ atomごとにregexマッチを試行"]
D --> E{"マッチしたatomは\nあるか?"}
E -->|なし| Z["何も注入しない\nexit 0"]
E -->|あり| F["上位max_matches(3)個のみ選択"]
F --> G["合計6,000字を超えたら\ntruncate"]
G --> H["ユーザー入力の前に\natom本文を添付"]
H --> I["モデルがatom + プロンプトを\nまとめて受信 → 応答"]
D -.->|どんな例外でも| Z2["例外は無条件に握りつぶす\nexit 0 (フローを止めない)"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
class A human
class B,C,D,E,F,G,H code
class I ai
5つの動作を順に書き下すと、こうなります。
1) manifestを読む。 スクリプトはまず~/.claude/projects/C--Users-user/memory/_jit_manifest.jsonを開きます。このファイルにはatomのメタデータ(名前・パス・マッチ用regex・スコア)が整理されています。著者の個人PCには現在17個のatomが登録されています。
2) scoreの降順でソートする。 atomごとにscore値があります。スコアの高いatomほど先にマッチを試行されます。同じキーワードに複数のatomがヒットするとき、どれが優先権を持つかをこのスコアが決めます。
3) regexでマッチする。 ユーザーの入力文字列を、各atomのregexパターンと照合します。入力に「쿨다운」(クールタイムを意味する韓国語キーワード)があれば、쿨다운|cooldown|GCDのパターンを持つatomがヒットします。大文字・小文字は区別せずに比較します。
4) 最大3個までに絞る。 マッチがどれだけ多くても、max_matches(著者の環境では3)を超えたら上位3個だけを残します。さらに、選ばれたatom本文の合計の長さが6,000字を超えたら切り詰めます(truncate)。二重の上限で、入力が肥大化するのを防ぐ安全装置です。
5) どんな例外でもexit 0で終わる。 設計の核心です。manifestが壊れていても、ファイルが消えていても、regexが間違っていても、スクリプトは例外を静かに握りつぶし、終了コード0で終わります。フックが0以外のコードで死ぬと、ユーザーのプロンプト自体がブロックされることがあるからです。「メモリー注入が失敗しても、ユーザーの作業フローは絶対に止めない」という原則が、コードのいちばん外側のtry/exceptに記録されています。
重心は4番と5番にあります。4番(上限)はメモリーがトークンを爆発させないように抑え、5番(例外の握りつぶし)はインフラが作業を妨げないように抑えます。どちらも「自動化が人の足かせにならない」という同じ哲学の2つの顔です。
1.2では「必要な資料だけ、上位数個だけ、失敗しても静かに」というトークン節約の原則を約束し、実装のディテールをこの章に持ち越しました。そのディテールが住んでいる場所が、inject_memory.pyが読むmanifestです。JIT(Just-In-Time、必要なときだけ資料を呼び込む方式)の心臓部であり、atom 1つのエントリは次のような形をしています。
{
"atoms": [
{
"name": "combat_cooldown_rule_v2",
"path": "atoms/combat/combat_cooldown_rule_v2.md",
"regex": "쿨다운|cooldown|GCD",
"score": 80
},
{
"name": "user_health",
"path": "memory/user_health.md",
"regex": "건강|복약|컨디션|약물",
"score": 95
}
],
"config": {
"max_matches": 3,
"case_insensitive": true
}
}
4つのフィールドが1つのatomを定義します。
name — atomの固有名。path — マッチしたら本文を読み込むファイルパス。regex — どのキーワードが入力に入ってきたらこのatomを呼び起こすかを定義するパターン。score — ソートの優先順位。高いほど先にマッチされ、先に枠を確保します。configブロックのmax_matches: 3は、1.3.2で見た「最大3個」上限の出どころです。manifestを手で直せば、動作は即座に変わります。
規模の感覚を1つ押さえておきます。著者の個人PCはatom 17個、manifest 1式で軽く運用されています。一方、会社の実務環境(プロジェクトA)は、2026年5月時点のバックアップでチームatom 304個、skill 48個が登録されています。Hot atomの1つはscoreが356.53まで上がっていますが(ファイル名規則を扱うview_html_filename_convention系列)、最初から高かったのではなく、繰り返し呼び出され、検証されながら積み上がった痕跡です。
個人PCの17個と会社の304個の差が語っているのは、同じJITメカニズムでも、資料が積み上がる速度と規模はプロジェクトの密度に比例するという点です。最初から304個を作る必要はありません。核心のatom 5個から始めて、毎週1〜2個ずつルール化していけば、いつの間にかmanifestは厚くなっていきます。
著者の推定(未検証): scoreがマッチ・検証の回数に応じて累積するという記述は、運用パターンに基づく解釈です。スコア算定の式自体は環境ごとのmanifest設計によって変わるため、上記の356.53のような絶対値は著者の環境の実測スナップショットにすぎず、一般的な標準ではありません。
メモリーは2つの層に分けておくという原則も、ここでもう一度押さえます。
| 区分 | 場所 | ロードされるタイミング | 用途 |
|---|---|---|---|
| グローバル | ~/.claude/memory/ |
すべてのセッション | 自分のアイデンティティ・コラボレーションルール・言語設定 |
| プロジェクト | ~/.claude/projects/<프로젝트>/memory/ |
該当プロジェクトのセッション | プロジェクトごとのatom・ルール・資料 |
グローバルは軽く保つほうが安全です。グローバルが重くなると、その重さがすべてのセッションにトークンコストとして積み重なるからです。オフィスにたとえれば、グローバルは机の上の名刺ホルダー(軽いほど毎日使いやすい)、プロジェクトメモリーは隣のキャビネットのフォルダー(プロジェクト単位で厚くなっても普段の負担はない)です。だから、自動ロードされるグローバルには核心だけを置き、豊富な資料はプロジェクトメモリーに蓄えたうえで、JITで必要なときだけ呼び起こします。
いよいよ、インフラの3つ目の軸、権限です。Claude Codeはファイルを削除し、コマンドを実行し、外部APIを呼び出すことができます。強力さはリスクとともにやって来ます。権限システムがそのリスクを管理します。
権限は2種類に分かれます。人の承認なしに自動で実行されるものと、毎回承認を受けなければならないもの。どちらに何を置くかは、settings.jsonのpermissionsブロックで定義します。
{
"permissions": {
"allow": [
"Bash(ls:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Read(*)",
"Grep(*)"
],
"deny": [
"Bash(rm -rf:*)",
"Bash(git push --force:*)"
]
}
}
ここで視点の転換が必要です。このallowリストは単なる設定値ではなく、作業の蓄積の痕跡です。最初は読み取り・検索程度だけを自動許可にして、ほとんど空のままです。ところが1〜2か月同じ作業を繰り返していると、「このコマンド、毎回承認を押すのは面倒だな」というパターンが生まれ、それを1つずつallowに移していきます。長くなったリストは、自分がこのツールで何を繰り返してきたかの指紋そのものです。
著者の会社環境(プロジェクトA)は、約80個の自動許可パターンを持っています。20個から始まり、6か月かけて60個が増えました。その60個を逆から読むと、この半年間どんな作業を繰り返したのかが浮かび上がります。マスターデータの抽出、関係図の生成、スキーマのドキュメント化。よく使うツールが、そのままよく許可した権限なのです。
権限の運用には、4つのパターンが定着しています。
rm -rfやgit push --forceのように、一度の事故が致命的になるコマンドはdenyに入れます。自動許可を広げても、この2つには手を付けません。allowを見直し、使わなくなった権限を削ります。痕跡は、積もるだけならノイズになります。毎回承認ポップアップが出ると、人は疲れます。疲労を減らす仕掛けもあります。fewer-permission-promptsのようなスラッシュコマンドで頻出パターンを一括登録する、1セッションの間だけ一時許可を与える、個人作業に限って全権自動許可モードを使う、といったやり方です。ただし、最後のオプションはチーム環境ではおすすめしません。
疲労と安全のバランスは、自分で調整します。厳しすぎると作業が回らず、緩すぎると事故が起きます。緩めに始めても、四半期の掃除サイクルを備えておけば、バランスは自然に取れていきます。
ここまで見てきた3つの軸(settings・メモリー・権限)が、1つのセッションでどのように同時に働くのかを、入力1行を起点に広げてみます。以下は、「スキルのクールタイムバランスを見よう」と入力したときに実際に起きることの断面図です。
3つのレーンが、1つの入力で出会います。settings.jsonがフックを呼び起こし、フックがメモリーを選んで入力に添え、そうして作られた応答がツールを呼び出すときに、権限が最後のゲートとして働きます。ユーザーは「クールタイムを見よう」と1行打っただけなのに、3つのインフラが見えないところで順番に仕事をしています。これが、ツールが「自分のツール」だと感じられる瞬間の内部構造です。
理論を見たので、手を動かします。初めてClaude Codeをインストールしたあと、1時間あれば上の図の全体を自分のPCに敷くことができます。5つの区間に分けます。
0〜10分、インストール・起動確認。 インストール後、ターミナルでClaude Codeを起動します。フォルダー1つの中で「このフォルダーに何がある?」と聞いて、応答を確認します。まず、ツールが生きているかどうかから見ます。
10〜25分、グローバルメモリー3つの作成。 自動ロードされるグローバルは、3ファイルで十分です。
MEMORY.md(5行) — 自分のアイデンティティ1行+ほかのファイルへのポインター。user-profile.md(20〜30行) — 名前・役割・専門分野・連絡先。feedback-collaboration-style.md(20〜30行) — 言語・口調・実行優先・説明簡潔のようなコラボレーションのルール。著者の例をそのまま写して始めても構いません。運用しながら磨いていけばよいのです。
25〜40分、settings.jsonの基本設定。 effortLevelをhighにし、権限のスターターセット(読み取り・検索は自動、書き込み・削除は承認)を入れ、バックアップを1部とります(settings.json.bak_<날짜> — <날짜>は「日付」を意味する韓国語のプレースホルダー)。バックアップは、この区間でいちばん重要な1行です。
40〜55分、最初のプロジェクトatom 5個。 自分が毎回忘れる決定、よく尋ねる情報を5個、atomにします。フォルダーは~/.claude/projects/<프로젝트>/memory/(<프로젝트>は「プロジェクト」を意味する韓国語のプレースホルダー)。形式は第5章を参照してください。5個なら、JIT manifestを今すぐ作らなくても、グローバル自動ロードだけで効果が出ます。
55〜60分、テスト1回。 新しいセッションを開き、自分の分野の質問を1つ投げます。グローバルメモリーが自動ロードされたか、応答のトーンが自分のコラボレーションルールに従っているかを確認します。
ここまでで1時間です。JIT manifestとフックは、atomが50個を超えるころ、自動ロードが重くなり始めたときに導入しても遅くありません。その時点で1.3.2のinject_memory.pyを敷けばよいのです。
導入初期に繰り返される失敗は5つにまとまり、それぞれが同じ事故原因の上に立っています。
| 失敗 | 事故原因 | 回避法 |
|---|---|---|
| グローバルに入れすぎる | すべてのセッションが重くなりトークンを浪費 | グローバルは5KB以内、詳細はプロジェクトメモリーへ移管 |
| すべての権限を自動許可 | 利便性がリスクを覆い隠す最初の場所 | 読み取り・検索のみ自動、書き込み・削除は承認(四半期の掃除を含む) |
| バックアップなしでsettingsを修正 | 壊れたsettingsがツール自体を起動不能にする | 変更前にsettings.json.bak_<날짜>を自動保存 |
| atomをメモリーフォルダーに無限に積む | 自動ロードがトークン上限を圧迫 | 約50個からJIT manifestを導入 |
| チーム・個人の設定を1ファイルに混在 | gitコンフリクト・個人設定の漏えい | チームはsettings.json、自分はsettings.local.json |
5つすべてを初日から回避する必要はありません。グローバルの肥大化とバックアップ漏れは、最初の1時間以内に回避パターンを作っておくのがよく、残りの3つは1か月ほど運用しながら、自分にとって事故の可能性が高い場所に回避装置をはめ込むほうが自然です。
1.1はツールへの距離感を縮める場所で、1.2はそのツールの最小動作メカニズムをつかむ場所で、1.3はメモリー・権限・settingsで最初のインフラを敷く場所でした。この3章が、本書の導入部にあたります。ここまで終えれば、ツールを止めずに運用するための基本骨格は据わります。
核心は、この骨格が静的な設定ではないという点です。manifestのatomは毎週増え、allowリストは作業の痕跡に沿って長くなり、scoreは検証を経ながら積み上がります。インフラは敷いた瞬間に完成するのではなく、敷かれた上でユーザーとともに育ちます。個人PCの17個が会社の304個へと開いていく差が、その成長の距離です。
第2部からは、本格的な情報アーキテクチャに入ります。第4章のYAMLフロントマター、第5章のAtom、第6章のLayer、第7章のオントロジーが順に続きます。1.3ではmanifestの1エントリとしてしか見なかったatomが、2.2では1つの章の主役になります。背骨が据わってこそ、分野別の章が同じ座標の上で自分の居場所を見つけられるのです。
setup
1. ~/.claude/settings.jsonを開き、変更前にsettings.json.bak_<오늘날짜>(<오늘날짜>は「今日の日付」の意)としてバックアップをとりましょう。
2. permissions.allowにRead(*)、Grep(*)、Bash(ls:*)、Bash(git status:*)を入れ、permissions.denyにBash(rm -rf:*)、Bash(git push --force:*)を入力しましょう。
3. (atomが50個以上のとき)hooks.UserPromptSubmitにpython ~/.claude/hooks/inject_memory.pyを登録し、_jit_manifest.jsonにatomエントリ(name・path・regex・score)とconfig.max_matches: 3を書きましょう。
prompt - 新しいセッションで、manifestのどれかのatomキーワードを意図的に含んだ質問を投げてみましょう。例:「クールタイムのルールを基準にスキルバランスを見よう」。
verify
- 入力直後に[memory injected: N atoms]のようなシグナルが表示されるか確認しましょう。
- 意図したatomが応答に反映されたかを見てみましょう。
- わざとmanifestに壊れたJSONを入れてみて、それでもプロンプトがブロックされずに動くか(exit 0保証)を確認したうえで、元に戻しましょう。
一人ミニ版
- フックもmanifestもなしで始めましょう。グローバルのMEMORY.md1ファイルにアイデンティティ3行+コラボレーションルール3行だけを書き、権限はRead(*)・Grep(*)だけを自動許可にしておきましょう。atomが手になじんで50個に近づいたとき、そのときに1.3.2のフックを載せましょう。インフラは小さく始めて痕跡に沿って育てるものであって、最初から304個をそろえるものではありません。
マイルストーンビルドを翌日に控えた夜、システムプランナーのチームメンバーAが社内メッセンジャーで尋ねてきました。「今週、報酬カーブに手を入れたドキュメントは何件ですか? レビューが終わっているのはどこまでですか?」私は答えを知りませんでした。ドキュメントはフォルダのどこかにあり、誰が最後に手を入れたのか、どのマイルストーンのものなのかは、各自の記憶とファイル名の取り決めの中に散らばっていました。その夜、私たちがやったのは、ドキュメントの先頭に6行を書き込むという取り決めを作ることでした。その6行が、次のマイルストーンからは、チームメンバーAの質問に、人がフォルダを開かなくても答えられるようにしてくれました。
ドキュメントの先頭、---の間に書く数行のYAML。これをフロントマターと呼びます。この取り決めが、本文を一文字も読まずに「このドキュメントが何なのか」を、人と機械の両方に同時に伝えてくれます。本章では、その1行がどのように情報アーキテクチャ全体の入口座標になるのかを、実際に動くスクリプトで追いかけていきます。
先に用語を1つだけ押さえておきます。本書は企画ドキュメントを5つのLayerに分けます(第6章で本格的に扱います)。L0=世界観・コンセプト、L1=システムルール、L2=コンテンツ、L3=データ、L4=実装座標です。上から下へ依存するのが正常な方向です。下の節に出てくるlayer: 2は「このドキュメントはコンテンツLayerにある」という座標の宣言です。
伝統的な企画ドキュメントは、Word、PPT、Googleドキュメントの上で生きてきました。本文は人が読むために最適化されています。ところが、ドキュメントの種類・責任・状態・位置といったメタ情報は、本文の中に溶け込んでいるか、フォルダ構造とファイル名の取り決めに依存しています。そのため「このドキュメントはどのマイルストーンのもので、誰が責任者で、最後のレビューはいつだったのか」を知るには、本文を開いてみるしかありません。
ここに2つの限界が重なります。第一に、ドキュメントが自分の正体を自分で語りません。正体は人の記憶とフォルダの取り決めの中にあり、その取り決めは時間とともに風化します。第二に、AIがコンテキストを推論する手がかりがありません。Claude Codeに「このドキュメントをレビューして」と頼むと、本文を最初から最後まで読んでトークンを浪費するうえ、責任範囲がどこまでなのかも分かりません。
YAMLフロントマターはこの2つを一度に解決します。ドキュメントの先頭にメタデータを明示的に書き込んでおけば、人も機械も本文を開かずにドキュメントを識別できます。キャビネットの引き出しの正面にラベルが貼ってあれば、引き出しを開けなくても中身が分かるのと同じです。そしてこのラベルは、単なる分類ツールにとどまりません。後で見るように、layerフィールド1つが、プロシージャル生成と自動チェックの入口座標になります。
抽象的な例の代わりに、プロジェクトAの報酬カーブのドキュメントが実際に頭の上に載せているフロントマターをそのまま見てみます(ID・実名のみ仮名処理、構造は運用そのままです)。
---
title: "メインクエスト12章 報酬カーブ"
layer: 2
status: review
owner: teammate_a
created: 2026-04-15
updated: 2026-05-20
related:
- quest_main_chapter12
- reward_curve_milestone_2
affects:
- L3_BalanceSheet_v2
ip_check: passed
---
# メインクエスト12章 報酬カーブ
(本文開始)
核心は---の上と下の分離です。上はパーサーが読むデータ、下は人が読む本文です。Markdownレンダラーは通常フロントマターを非表示にするため、読むときの邪魔にはなりません。1つのファイルがデータ(frontmatter)とコンテンツ(本文)を一緒に収め、信頼できる唯一の情報源(single source of truth)になります。
layer: 2とaffects: [L3_BalanceSheet_v2]、この2行に注目してください。「このコンテンツ(L2)のドキュメントが、データLayer(L3)のバランスシートに影響を与える」という宣言です。これだけで、ツールはL2→L3の依存関係を、本文なしにグラフとして描けます。逆に、L3のデータドキュメントがL1のシステムルールをdepends_onで参照していたら(下から上へ向かう逆方向の依存)、それは設計の臭いです。ツールがその逆参照を自動で検出します。
YAMLがJSONより手で書きやすい理由は単純です。インデントで構造を表現でき、引用符がほとんど要らず、#コメントが使えます。プランナーが自分の手で埋めるのに適しています。
_NAMING_FRONTMATTER_STANDARDフィールドは際限なく増やせます。増やすほど記入の負担が大きくなり、標準が崩れていきます。そこでプロジェクトAは2つの層に分けて運用しています。すべてのドキュメント共通の最小限のコアフィールドと、分野別のドメイン拡張フィールドです。
共通の最小コアフィールドは6つです。
| フィールド | 形式 | 用途 |
|---|---|---|
title |
文字列 | 人が読むためのタイトル。ファイル名と違っていてもよい |
layer |
0〜4 | 第6章のLayer座標 |
status |
draft / review / approved / archived | ドキュメントの状態 |
owner |
ユーザー名 | 責任者(1人) |
created |
YYYY-MM-DD | 作成日 |
updated |
YYYY-MM-DD | 最終更新日 |
この6つだけで、ドキュメントの鮮度・責任・位置が即座に分かります。もっと足したくなる衝動を、最初の1か月は我慢します。運用しているうちに、どのフィールドが本当に必要かは自然と見えてきます。
分野別の拡張フィールドはドメインごとに異なります。システム企画はdepends_on・affects、戦闘企画はcombat_phase・anim_target、ナラティブはworld_region・chapter、バランスはdata_sheet・formula_idをよく使います。これらの拡張フィールドが自由に散らばってよいわけではないので、ただ1つの標準ドキュメントが正式名称・許容値・例を明文化して固定します。それが_NAMING_FRONTMATTER_STANDARD.mdです。新しいフィールドを追加するには、このドキュメントを経由しなければなりません。そしてこの標準ドキュメント自体がatomとして登録されており、ドキュメント名の先頭にLayer番号を強制するルール(docs_layer_numeric_prefix_naming atom)と同じ系列で管理されています。
ここで重要な転換が起きます。標準が「人が読むドキュメント」でしかなければ、人はそれを破ります。標準を機械が読むデータにすれば、機械がそれを強制します。次の節が、その転換の実際のコードです。
今度は「プロジェクトAのすべてのMarkdownドキュメントがフロントマター標準を守っているかを検査するLinter」をClaude Codeに作らせました。核心となる要求は2つでした。検査項目(必須フィールドの欠落、statusの非標準値、layerの0〜4違反、reviewのまま90日以上更新されていないドキュメント)を検出すること、そして許容値をコードにハードコーディングせず、標準ドキュメントから読み込むこと。この分離が核心です。標準を直せば、コードを直さなくても検査基準が変わります。(スクリプト全文と実際に実行する手順は、本章末尾の「やってみよう」に置いています。)
ここで1つの事件がありました。Claudeが最初に出してきたコードは、STALE検査でtoday - fm["updated"]によって日付の差を計算し、コメントに「updated: 2026-05-20のように書かれていればPyYAMLがdatetime.dateとして自動パースする」と書いていました。この説明は半分しか合っていません。実際のドキュメントに対して走らせると、一部のファイルでトレースバックが出ました。
TypeError: unsupported operand type(s) for -: 'datetime.date' and 'str'
原因は人の手にありました。ある作成者はupdated: 2026-05-20と書き(dateとしてパースされます)、別の作成者はupdated: "2026-05-20"と引用符を付けていました(文字列としてパースされます)。標準が日付の形式を確定していなかった場所で人の手が分かれ、Claudeは片方だけを仮定していたのです。私はこのコードを差し戻し、「2つの表記をどちらも安全にdateへ正規化し、updatedがない場合もはじくように」ともう一度依頼しました。Claudeは入力の型を検査して、両方をdatetime.dateに正規化するヘルパーを差し込みました(修正後のブロックも「やってみよう」を参照してください)。
本当の教訓はコードのバグではありませんでした。標準が日付の表記形式を確定していなかった場所で、人の手が分かれたということです。そこで_NAMING_FRONTMATTER_STANDARD.mdにupdated: YYYY-MM-DD (따옴표 없이)という1行を追加しました。Linterがコードで検査を回すうちに、検査対象である標準そのものの穴をあらわにしてくれた、というわけです。
修正後のスクリプトの最初の出力は、きれいなものではありませんでした。実際に出た汚い結果をそのまま載せます。
[NO-FM] manuscript/legacy/old_combat_notes.md
[MISSING] manuscript/system/quest_flag_table.md: layer
[STATUS] manuscript/content/town_intro.md: WIP
[LAYER] manuscript/balance/dps_v2.md: None
[STALE] manuscript/system/inventory_rules.md: 134d
この5行が、導入初期のチームの実際の状態でした。古いドキュメントにはフロントマターがそもそもなく(NO-FM)、あるドキュメントはlayerを落としていて、誰かはstatus: WIPという非標準値を使い、バランスのドキュメント1つはlayerをNoneのまま空けてあり、システムルールのドキュメント1つは134日間review状態のまま眠っていました。標準は最初から守られるものではありません。Linterは、その事実を毎朝あらわにするだけです。
上のワークド・トランスクリプトを1枚の流れに圧縮すると、次のようになります。人が書いた1行が、どのように機械のチェックゲートまで流れていくかを示しています。
flowchart TD
A["作成者: 新規ドキュメント作成
テンプレートがフロントマター6フィールドを自動挿入"] --> B["空欄だけを埋める
title / layer / status / owner ..."]
B --> C["保存された.mdファイル
--- frontmatter --- + 本文"]
C --> D["Linterスクリプト実行
rglob('*.md')"]
D --> E["_NAMING_FRONTMATTER_STANDARD.md
許容値を読み込む"]
E --> F{"検査
必須フィールド / status / layer / stale"}
F -->|"合格"| G["関係グラフへ入力
related・affects → L座標の依存関係"]
F -->|"違反"| H["日次レポート
責任者ownerに自動通知"]
H --> B
G --> I["AIへの質問が可能
'Layer2のreviewドキュメントを集めて' → 即答"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class A,B human
class D,F code
class C,E,G data
class H fail
class I ai
核心は2つです。第一に、標準(E)がスクリプト(D)から分離されています。標準を直せば、コードを直さなくても検査基準が変わります。第二に、違反(H)は行き止まりではなく、作成段階(B)へ戻るループです。人を責めるのではなく、本人のドキュメントを本人が直すように送り返します。
著者がディレクターとして運営するプロジェクトAでは、約6か月前にフロントマターを企画チーム全体(4〜5人)に導入しました。導入は一度に済んだわけではなく、4つの節目を経ました。
導入1週目の最大の拒否反応は「これを毎回手で書けというのか?」でした。新しいドキュメントのたびに6行を覚えて書くのは面倒です。解決策はテンプレートの自動挿入でした。VSCodeのスニペット、Obsidianのテンプレート、企画ポータルの「新規ドキュメント」ボタンが、空のYAMLブロックを自動で差し込んでくれます。作成者は空欄だけを埋めます。拒否反応は1週間以内に消えました。
1か月目には標準の衝突が起きました。複数のメンバーが自由にフィールドを追加するうちに、owner・responsible・authorが同時に現れたのです。同じ概念なのに表記が3つあるため、検索も自動化も壊れました。解決策は、_NAMING_FRONTMATTER_STANDARD.mdという1つのドキュメントに、すべてのフィールドの正式名称・許容値・例を整理し、新しいフィールドの追加をこのドキュメント経由でルール化したことでした。1か月以内に標準が安定しました。
3か月目には、2.1.4で見たLinterが入りました。標準があっても人は破ります。そこで、毎朝整合性レポートが自動生成され、社内メッセンジャーの共有チャンネルに落ちてくるようにしました。責任者は自分のドキュメントだけを見ればよい仕組みです。自動化の後、標準違反は目に見えて減りました(著者の推定であり、精密な測定値ではありません — 体感でおおよそ半分以下)。
6か月目には、AIとの結合が光りました。標準が安定すると、次のような質問に即答が返ってくるようになったのです。
related・affectsのグラフをたどって自動で結局、フロントマターは人とAIの間の共通語彙になりました。人が書けばAIが理解し、AIが書けば人が検証します。どちらも同じキーを見ています。ただし、1週目の拒否反応、1か月目の衝突、3か月目のLinter、6か月目の結合 — 積み重なった6か月が作った結果であって、一度に生まれたものではありません。
導入初期に繰り返される失敗は5つにまとめられます。すべて同じ根 — 「標準を人の意志だけに委ねた場所」 — の上に立っています。
| 失敗 | 事故の原因 | 回避策 |
|---|---|---|
| フィールドを最初から多く定義しすぎる | 作成者が空欄を埋めるうちに疲れて品質が低下する | コアの6つから始め、1〜2か月後によく使うものだけ追加する |
フィールド名が変わり続ける(tag→tags→category) |
古い名前が累積したドキュメントに残り、検索・自動化が壊れる | 名前を変えるときはマイグレーションスクリプトを伴わせる。古い名前を見つけたら自動変換または警告 |
| 人が毎回手で書く | 誤字・フィールド欠落・日付表記の分岐(2.1.4のあのバグ)が日常化する | テンプレート・スニペット・「新規ドキュメント」の自動化を優先する。人の手は意味のある値だけに |
| 標準だけ置いて検証なしに放置する | 標準があっても誰が破ったか分からず、自然に風化する | Linter+日次自動レポートで、違反者本人が直すようにする |
layerフィールドを忘れる |
Layer座標がないと、分野間の可視性もチェックゲートもどちらも生まれない | layerを必須フィールドとして強制する。Linterが欠落を検出 |
5つの失敗を初日からすべて防ぐ必要はありません。1番と3番は導入1週目に回避パターンを押さえておき、2・4・5番は運用しながら、自分のチームが最もよくぶつかる場所から順に組み込んでいくのが自然です。
フロントマターの導入は、意外と軽い作業です。3週間あれば、1つのチームの中に根を下ろします。
1週目はコアの6フィールドを定義し、テンプレートを作って新規ドキュメントだけに適用し、記入の負担を最小化します。2週目はよく見る上位20件のドキュメントに手動で適用し、実際の使用の中でどのフィールドが足りないかを点検します。3週目にLinterと日次レポートを稼働させれば、そこからは標準が、人の意志ではなくツールの力で維持されます。
既存ドキュメント全体を一度にマイグレーションすることはしません。よく見るものから、新規ドキュメントから適用します。6か月もすれば、ほぼすべてのドキュメントにフロントマターが付きます。とはいえ、100%が目標ではありません。一度も開いたことのない古いドキュメントまでマイグレーションするために時間を使うのは無駄です。
最小の単位で、1回のサイクルを自分の手で回してみましょう。
setup
- 作業フォルダに、検査対象の.mdドキュメントを2〜3個置きます。一部はわざとlayerを抜いたり、status: WIPのような非標準値を入れたりしておきます。
- 同じフォルダに、標準ドキュメントとして次の行を置きます。
status: allowed = ["draft", "review", "approved", "archived"]
updated: YYYY-MM-DD (引用符なし)
prompt(Claude Codeに入力)
このフォルダ以下のすべての.mdのYAMLフロントマターを検査するPythonスクリプトを書いて。必須フィールドtitle・layer・status・ownerの欠落、statusの許容値違反(標準ドキュメントから読み込むこと)、layerの0〜4整数違反、reviewなのにupdatedが90日を超えているものを検出して。
updatedが文字列で来てもdateで来ても安全に処理して、違反をファイル別に出力して。
verify
- スクリプトを実行し、わざと仕込んだ違反がすべて検出されるか確認します。
- 標準ドキュメントのallowedリストにWIPを追加してからもう一度実行し、コードを1行も直していないのにstatus: WIPが合格に変わるか確認します。標準とコードが分離されている証拠です。
- updatedに引用符を付けたドキュメントと付けていないドキュメントを両方入れて、2.1.4で見たTypeErrorが出ないことを確認します。
参考: Linterスクリプト全文
2.1.4でClaudeが最初に出してきたコードです。STALE検査の行(age = (today - fm["updated"]).days)に、datetimeバグがそのまま入っています。
import sys, datetime, pathlib, re
import yaml # PyYAML
ROOT = pathlib.Path("manuscript")
STANDARD = pathlib.Path("_NAMING_FRONTMATTER_STANDARD.md")
REQUIRED = ["title", "layer", "status", "owner"]
def load_allowed_status(standard_path):
# 標準ドキュメントから `status` の許容値を抽出
text = standard_path.read_text(encoding="utf-8")
m = re.search(r"status:\s*allowed\s*=\s*\[(.*?)\]", text)
if not m:
return ["draft", "review", "approved", "archived"]
return [s.strip().strip('"').strip("'") for s in m.group(1).split(",")]
def parse_frontmatter(md_path):
text = md_path.read_text(encoding="utf-8")
if not text.startswith("---"):
return None
end = text.find("---", 3)
block = text[3:end]
return yaml.safe_load(block)
def main():
allowed = load_allowed_status(STANDARD)
today = datetime.date.today()
violations = 0
for md in ROOT.rglob("*.md"):
fm = parse_frontmatter(md)
if fm is None:
print(f"[NO-FM] {md}")
violations += 1
continue
for field in REQUIRED:
if field not in fm:
print(f"[MISSING] {md}: {field}")
violations += 1
if fm.get("status") not in allowed:
print(f"[STATUS] {md}: {fm.get('status')}")
violations += 1
if not isinstance(fm.get("layer"), int) or not (0 <= fm.get("layer") <= 4):
print(f"[LAYER] {md}: {fm.get('layer')}")
violations += 1
if fm.get("status") == "review":
age = (today - fm["updated"]).days # ← ここが壊れる
if age > 90:
print(f"[STALE] {md}: {age}d")
violations += 1
sys.exit(violations)
再依頼の後に直してきた核心ブロックです。updatedが文字列で来てもdateで来ても、安全に正規化します。
def as_date(v):
if isinstance(v, datetime.date):
return v
if isinstance(v, str):
return datetime.date.fromisoformat(v.strip())
return None
# main() 内の STALE 検査の差し替え分
if fm.get("status") == "review":
upd = as_date(fm.get("updated"))
if upd is None:
print(f"[MISSING] {md}: updated")
violations += 1
elif (today - upd).days > 90:
print(f"[STALE] {md}: {(today - upd).days}d")
violations += 1
チームがなくても大丈夫です。1人で使うメモフォルダで、コアフィールドをtitle・status・updatedの3つに減らし、Linterは「statusがreviewなのにupdatedが30日を超えたドキュメント」だけを検出するようにします。これだけでも、「レビューの途中のまま忘れてしまったドキュメント」が週に1回、水面に浮かび上がってきます。標準・テンプレート・検査の三角形は、1人の規模でもそのまま機能します。
layerという1つのフィールドが、プロシージャル生成と自動チェックの入口座標になります新人が入って最初の週、彼がチャットで尋ねてきました。「戦闘のクールタイム(クールダウン)は0.6秒で合っていますか?どのドキュメントに書いてありますか?」。私は「スキルシステムのGDD(Game Design Document、詳細仕様書)にありますよ」と答えました。彼はまた尋ねました。「そのGDDのどのセクションですか?クラス設計の次にダメージカーブ、その後ろにUI表示方式まで220行あるんですが」。ファイルを開いて直接探してあげました。137行目でした。彼は最後に尋ねました。「ところで、なぜ0.6秒なんですか?0.5ではだめだったんですか?」。その答えはどのドキュメントにもありませんでした。6か月前の会議で決めたことは覚えているのに、理由は議事録のどこかに埋もれていました。
この5分間の会話の中に、220行の統合ドキュメントが抱える3つの失敗がすべて詰まっています。位置を見つけられない(検索の失敗)、理由がない(文脈の喪失)、毎回人が仲介しなければならない(自動化の不可)。AIに同じ質問を投げると事情はさらに悪くなります。AIは220行をすべて読んだうえで、クールタイムとは無関係なダメージカーブの話まで混ぜて答えます。
本章の処方箋はシンプルです。1つのドキュメントには1つの決定だけを入れる。この原則で細かく分割した決定単位のドキュメントをatomと呼びます。220行のGDDを分割すると「クールタイムは0.6秒」が1つのatomになり、そのatomの中に位置・内容・理由・例外・関係が1か所に集まります。本章では抽象論の代わりに、実際のatom1つを最後まで解剖します。どう命名するか、どんなfrontmatterを記入するか、関係をどう明示するか、そしてその結果、AIがどうやってそのatom1つだけを正確に拾い上げるのか — そこまでを見ていきます。
combat_cooldown_rule_v2解剖する検体は、プロジェクトAで実際に運用中のatom1つです。名前はcombat_cooldown_rule_v2。ファイルの全文は次のとおりです。長くはありません。1つの決定だけを入れたのですから。
---
name: combat_cooldown_rule_v2
title: "戦闘クールダウン規則 — v2"
type: rule
layer: 1
status: approved
owner: 이민수
created: 2026-03-10
updated: 2026-05-12
applies_to: [skill_system, item_system]
---
# 戦闘クールダウン規則 v2
Why (なぜ): 同時に使用可能なスキル数を制限して瞬間的な意思決定の負担を
減らし、コンボ入力の意味を保存するため。
Rule (規則): すべてのアクティブスキルはグローバルクールダウン0.6秒 + 個別
クールダウン(スキル別定義)を持つ。グローバルクールダウンの進行中は、いかなる
アクティブスキルも発動不可。
How to apply (適用):
- 新規スキル定義時に個別クールダウンを必ず明記
- L3_SkillSheetのcooldownカラムが0であれば本規則違反
- ビルド段階の整合性検査が違反を自動検出
Exceptions (例外):
- パッシブスキルは本規則の適用外
- 究極技は別途ゲージシステム (See: [[ultimate_gauge_system]])
Relations (関係):
- affects: [[combat_dps_calculation_v3]], [[balance_curve_v3]]
- derives_from: [[principle_decision_load_reduction]]
- conflicts_with: [[skill_cancel_rule_legacy_v1]]
- requires: [[combat_input_buffer_system]], [[skill_system_v2]]
- is_a: rule
- part_of: combat_system_master
このファイル1枚を、5つの部位に切り分けてみます。命名、frontmatter、単一の決定、関係、トレーサビリティ(追跡可能性)。5つの部位がすべてそろってはじめて、AIはこのatomを「単独でも意味が通る単位」として読みます。
ファイル名はcombat_cooldown_rule_v2です。なんとなく付けた名前ではなく、3つのパーツからなる構造を持っています。
combat_ cooldown_rule _v2
└ prefix └ 決定本文 └ バージョン
(どのドメイン) (何についての決定) (何回目の改訂)
prefixのcombat_は「これは戦闘ドメインの決定」という座標です。プロジェクトAのルールatomは、prefixでドメインが分かれます。quest_(クエスト)、data_(データ運用)、docs_(ドキュメント運用)、meeting_(議事録)、portal_(企画ビューアー)。prefixを見るだけで、この決定が誰の責任領域なのか、どこから影響を受けるのかがつかめます。
命名が揺らげば、すべてが揺らぎます。同じ決定がskill-cooldown.mdとcooldown_skill_v2.mdとして二重に存在すると、検索も壊れ、後で出てくるJITマッチングも壊れます。だからプロジェクトAは、命名規則そのものを1つのatomとして先に固定しました。それがatom_naming_convention_v1で、snake_case・prefix必須・バージョンsuffixを強制します。そしてこのルールは、人の意志ではなくLinterが守ります。prefixのないファイル名がコミットされると、ビルド段階で弾かれます。
命名には、本書全体を貫くより大きな設計が敷かれています。frontmatterのlayer: 1がその2つ目の座標です。prefixが「どのドメインか」を語るなら、Layerは「どの抽象階層か」を語ります。2つの座標が組み合わさってはじめて、atomの位置が平面上の1点として確定します。ここでのLayerは座標にすぎません(0〜4の階層定義の詳細は2.3で扱います)。クールタイムルールは「生成を統制する入力ルール」なので、Layer 1に座ります。このLayer座標をドキュメント名の先頭に数字prefixとして強制するルールも別にあります — docs_layer_numeric_prefix_namingです。名前1つに、2つの座標軸が明示されているわけです。
この設計の本質は整理癖ではありません。私がチームに繰り返し言ってきた言葉があります。「プロシージャル生成のために分けたLayerだったわけで」。atomごとにドメイン座標(prefix)と階層座標(Layer)が明示されていれば、後でAIが「Layer 1のcombatルール全部を入力として受け取り、Layer 2のコンテンツを自動生成する」ことが可能になります。名前は、その自動化のアドレス体系なのです。
本文の上、---の間にあるYAMLブロックがfrontmatterです。2.1で扱った標準をatomにそのまま適用したもので、人ではなく機械(ビルドスクリプト・JITフック・関係図ジェネレーター)が読むラベルです。
| フィールド | 値 | 機械がこれで行うこと |
|---|---|---|
name |
combat_cooldown_rule_v2 | ほかのatomのlink対象になる一意のID |
type |
rule | カテゴリー別の統計・フィルター(rule / concept / decision …) |
layer |
1 | Layer別の色分け・整列、逆参照検出の基準軸 |
status |
approved | draft・approved・archivedのうちapprovedだけをビルドに含める |
applies_to |
[skill_system, item_system] | 影響範囲 — このルールが及ぶシステム |
created/updated |
2026-03-10 / 2026-05-12 | 変更の追跡、古くなったatom点検の基準日 |
これらのラベルが記入されていれば、自動検査が可能になります。たとえばlayer: 1と宣言されたシステムルールが、本文で[[L3_SkillSheet_row_0042]]のようなデータatom(Layer 3)を直接参照していれば、それは上位階層が下位階層の具体値に縛られる逆参照(L3→L1)です。プロジェクトAはこのパターンをビルド段階で自動検出します。ルールはデータの1行ではなく、データの形式を参照すべきだからです。frontmatterのlayerという1行がなければ、この検査自体が成立しません。
status: archivedの処理もfrontmatterの仕事です。決定が変わってもatomは削除されず、status: archivedとarchived_atの日付を受け取ります。ビルドとJITはarchivedなatomを除外します。記録は残しつつ、現役からは外れるのです。プロジェクトAの6か月の運用で、廃止率は約15%でした(著者の実測)。この比率が0%に近いなら、廃止のワークフローが機能していないサインだと読みます。
atom解剖の核心は、本文が決定を1つだけ入れているかの確認です。検査法はシンプルです。このatomの決定を一文で要約してみてください。
「すべてのアクティブスキルは、グローバルクールダウン(GCD)0.6秒を持つ。」
一文で終わります。合格です。もし要約が「クールタイムは0.6秒で、コンボ中は50%短縮される」のように二文になるなら、それは2つの決定です。combat_cooldown_rule_v2(基本クールタイム)とcombat_combo_cooldown_reduction_v1(コンボ短縮)に分割します。
単一性を見る補助検査が、さらに2つあります。
独立廃止検査。このatom1つだけを廃止しても、システムが崩れないか?クールタイムルールを廃止すれば戦闘バランスは揺らぎますが、システムは回ります。単位として適切です。逆に、廃止すると別の5つが一緒に崩れるなら、その5つは実は1つの決定の5つの断片です。より大きなatomに統合すべきです。
単一参照検査。ほかの場所から[[combat_cooldown_rule_v2]]の1つだけをlinkとして張っても、意味が通るか?通るなら単位として適切です。この1行を参照するために本文のあちこちを全部読まなければならないなら、まだ分割が足りていません。
これらの検査を通過した本文は、自然と5つのセクションに整列します — Why、Rule、How、Exceptions、Relations。とくにWhyを消さないでください。冒頭で新人が最後に尋ねた「なぜ0.6秒なんですか?」の答えがここにあります — 「瞬間的な意思決定の負荷を減らし、コンボ入力の意味を保つため」。6か月後に誰かが「0.5秒に縮めよう」と提案するとき、この1行が議論の出発点になります。Whyが消えたatomは、誰も手を付けられない化石になります。
atomの一番下にあるRelationsセクションが、この検体を孤立したメモではなく、グラフの1ノードにします。核心は、単なる「関連ドキュメント」ではなく、関係の種類を明示するという点です。
flowchart TD
P["原則: 意思決定負荷の軽減"] -->|derives_from| C["combat_cooldown_rule_v2"]
C -->|affects| A1["combat_dps_calculation_v3"]
C -->|affects| A2["balance_curve_v3"]
C -->|requires| R1["combat_input_buffer"]
C -->|requires| R2["skill_system_v2"]
C -.->|conflicts_with| X["skill_cancel_legacy_v1
(廃止予定の衝突)"]
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class P,C,A1,A2,R1,R2 data
class X fail
6種類の関係が、それぞれ違う仕事をします。
derives_from: この決定がどの上位原則から派生したのか。クールタイム0.6秒は「意思決定負荷の軽減」という原則の具体化です。affects: このatomが変わると、何が影響を受けるのか。0.6秒を0.5秒に変えると、DPS計算とバランスカーブが揺らぎます。変更の前に、影響範囲を自動で抽出できます。requires: この決定が成立するには、何が先になければならないのか。先行入力(入力バッファ)システムがなければ、GCDが入力を食ってしまいます。conflicts_with: 何と矛盾するのか。旧バージョンのスキルキャンセルルールと衝突しており、このリンクが「どちらか一方は廃止されるべきだ」というシグナルです。is_a / part_of: 分類(rule)と所属(combat_system_master)。グラフの骨格です。単純な「Related: [ドキュメントA], [ドキュメントB]」というリンクだったら、人がいちいち調べなければなりません。関係タイプがenumとして記入されていれば、機械が調べます。「このatomを変えたら影響を受けるものを全部見せて」はaffectsをたどる自動クエリになり、「いま互いに矛盾しているルールを全部探して」はconflicts_withをスキャンする自動検査になります。この6つのenumの本格的なオントロジー設計は2.4で扱い、2.2では、atom標準がそのenumを先取りして適用した形だという点だけを押さえておきます。
関係の矢印は、関係図生成ツールの入力でもあります。プロジェクトAのgen_relation_map.pyは、すべてのatomのfrontmatterのlayerとRelationsセクションを読み、Layerごとに色分けしたインタラクティブな関係図HTMLを自動で描きます。atomの1つひとつが座標(Layer)と矢印(Relations)を持っているからこそ可能なことです。
5つの部位がすべてそろったatomは、トレース可能です。誰が・いつ・なぜこの決定をし、何を違反として検出するのかが1か所にあります。トレーサビリティの価値は、統計ではなく、実際に防いだ事件として見えるときにもっとも鮮明になります。
プロジェクトAのmeeting_image_caption_standardというatomは、議事録の添付画像に「どの画面か・なぜ添付したか・何の決定か」をキャプションとして必ず明記せよというルールです。このatomがなかった時代、ある議事録にスクリーンショットがキャプションなしで貼られ、1週間後にそれを見たチームメンバーが「これは何の画面?」を作成者に確認するのに30分かかりました。atomができた後、同じ漏れが再発したときは、ビルド段階のLinterがキャプションのない画像を自動で検出しました。修正まで5分。30分が5分になったのです。
もう1つの検体skill_listing_budget_wrapper_only_policyは、グローバルスラッシュコマンドのスロットを12個に制限し、本体のスキルは別のディレクトリに置き、グローバルにはwrapperを12個だけ公開せよというルールです。ルール化される前は、グローバルスラッシュコマンドが40個近くまで膨れ上がり、セッション開始のたびにトークン予算を食いつぶしていました。atomを定義してからは、自動整理ツールが毎セッション開始時に超過分を整理します。ルールが、人の記憶ではなくツールで執行されるのです。
こうしたatomが、プロジェクトAには約304個積み上がっています(著者の実測、6か月運用時点)。分布の大きな枝だけ見ると、再発防止ルール(rule)がもっとも大きな割合を占め、その次が一回限りの意思決定の固定化(decision)・ドメイン概念(concept)・コラボレーション矯正(feedback)の順です。1つのatomが防ぐ時間は分単位ですが、304個積み上がれば、累積の節約は日単位に乗ります。これが、atomを「整理」ではなく「資産」と呼ぶ理由です。
ここまで、atom1つを静的に解剖してきました。今度は、生きて動く瞬間を見ます。1.3のJIT(Just-In-Time)フックは、入力キーワードにマッチするatomだけを選び、その場でコンテキストに注入します。JIT manifestは、各atomにマッチング用キーワードとスコアをマッピングしたJSONです。
{
"name": "combat_cooldown_rule_v2",
"path": "atoms/combat/combat_cooldown_rule_v2.md",
"regex": "쿨다운|cooldown|글로벌 쿨다운|GCD",
"score": 75
}
実際の注入は、こう流れます。
flowchart TD
A["ユーザー入力:
スキルのクールタイムを0.5秒に縮めたら?"] --> B["JIT hook:
manifestのregexをスキャン"]
B --> C{"クールタイムにマッチ?"}
C -->|"はい score=75"| D["combat_cooldown_rule_v2
全文を注入"]
C -->|"いいえ"| E["注入しない"]
D --> F["AIがWhy・Rule・Exceptionまで
読んで応答"]
F --> G["回答: 0.6秒は意思決定負荷の
軽減が根拠。0.5秒に縮めるなら
affects対象のDPS・バランスカーブの
再検討が必要"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class A human
class B,C code
class D data
class F,G ai
核心は最後のマスです。AIは単に「0.6秒でした」と答えません。atomのWhyを読んだので根拠を示し、Relationsのaffectsを読んだので、変更時に揺らぐ対象(DPS計算・バランスカーブ)まで先回りして指摘します。細かく分割し、理由を書き、関係を明示しておいた5つの部位が、すべて応答の中でよみがえるのです。
ここで、1文書1決定の原則が自動化の前提であることが明らかになります。もしこのatomが220行の統合GDDだったら、「クールタイム」という1単語がマッチした瞬間に、クラス設計・ダメージカーブ・UIまで丸ごと注入されてトークン予算が削られ、AIは5つの決定のどれに答えるべきか焦点を失います。atomが小さく明確であるほど、JITの精度は上がります。細かく分割された状態は、整理の美徳ではなく自動注入の前提条件なのです。
scoreは、コンテキスト予算を守る装置です。1つの入力に複数のatomがマッチしたら、scoreの上位N個だけを注入します(デフォルトは3個)。スコア付けの基準は、運用しながら決めていきます。
解剖した検体combat_cooldown_rule_v2は、status: approvedを受けたチーム共有atomです。すべてのatomが最初からこの場所に来るわけではありません。プロジェクトAは、atomを2つの層に分けています。
分離する理由は心理的なものです。個人atomが自由であってこそ、検証前の仮説を気負わずに書き、1週間後に廃棄できます。最初からチームに公開されると、「これが間違っていたらどうしよう」と思って、そもそも書かなくなります。逆に、チーム共有atomは厳格であってこそ、全員が信頼して参照します。
combat_cooldown_rule_v2も最初は、個人atomの「クールタイム0.6秒をテストしてみよう」という1行メモだったはずです。アルファビルドで検証された後、変更リクエストの形でチーム共有に昇格し、ほかのプランナーのレビューを経てapprovedになりました。この個人→チームの昇格フローそのものが、atomシステムが時間とともに賢くなっていくself-improvingループの一軸です。
atom運用の初期に繰り返される失敗は、5つに整理できます。どれも「atomを資産ではなく使い捨てのメモとして扱った」という同じ根から生まれます。
| 失敗 | 何が壊れるか | 回避法 |
|---|---|---|
| 最初の週に作りすぎる | 未検証のatomが積み上がり、運用が崩れる | 検証済みの1〜2個から始め、自然な増加に任せる |
| 廃止をしない | 古いatomがJITにマッチし続けて誤答を生む | 四半期点検、status: archivedとarchived_at |
| 抽象的すぎる/具体的すぎる | 「良いデザインをする」は検証不能、雑感の1行は無意味 | 「射程は0.5/1.5/3.0/5.0のみ」の水準で |
| 名前に一貫性がない | 検索・JITマッチングが丸ごと壊れる | 命名規則atomを先に作り、Linterで強制する |
| Whyを書かない | 時間が経つと誰も触れない化石になる | Why・Rule・How・Exception・Relationsの5セクションを強制する |
5つを最初の月から完璧に避ける必要はありません。1番と4番は命名規則atom1つでまとめて解決し、2・3・5番は運用3か月の時点で四半期点検を一度回せば、自然に整列します。
本章では、atom1つを5つの部位に切り分けてみました。名前(座標)、frontmatter(機械のラベル)、単一の決定(一文検査)、関係(影響分析)、トレーサビリティ(防いだ30分)。そしてその5つの部位が、JITの自動注入の中で丸ごとよみがえる様子を確認しました。
名前に明示された2つの座標のうちの1つ、layer: 1を、2.2では軽く触れるにとどめました。2.3が、そのLayerを正面から扱います。atomごとにLayer座標を与えると、分野が違っても、互いの成果物がどこに座っているのかが見え始めます。そして2.4は、本章でenum名だけ借りて使った6つの関係(affects・derives_from・conflicts_with・requires・is_a・part_of)を、オントロジーとして正式化します。YAML(2.1)→ Atom(2.2)→ Layer(2.3)→ Ontology(2.4)と続く情報アーキテクチャの背骨のうち、本章はその2つ目の継ぎ目でした。
setup. 作業フォルダーにatoms/ディレクトリを作り、命名規則atom(atom_naming_convention_v1)を一番最初に書いてみましょう。snake_case・prefix必須・バージョンsuffixの3行だけでもかまいません。JITを使うなら、_jit_manifest.jsonに空の配列を1つ置いておきます。
prompt. 自分が毎回忘れてしまう決定を1つ選び、次のプロンプトでatomのドラフトをもらいましょう。
「次の決定をatomの標準形式で作ってください。決定:『アクティブスキルはグローバルクールダウン0.6秒を持つ』。セクションはWhy・Rule・How to apply・Exceptions・Relationsの5つ。frontmatterにはname(snake_case+prefix)、type、layer、status: draft、owner、createdを入れてください。決定が一文で要約できるかも、最後に確認してください。」
verify. 受け取ったatomを、3つの観点で検査しましょう。①決定が一文で要約できるか(できなければ分割します)。②Whyが空になっていないか。③manifestに{"name", "path", "regex", "score"}の1行を追加し、そのregexのキーワードを実際の入力として投げたとき、atomが注入されるか。3つを通過すれば、最初のatomの完成です。
チームもLinterもビルドパイプラインもない1人開発者なら、本章全体をノートアプリのフォルダー1つに縮められます。
domain_decision_v1という1つのルールで統一しましょう。Linterの代わりに、自分の目で守ります。→ 영향:(影響)、↑ 근거:(根拠)、✕ 충돌:(衝突)の3つの印を書くだけでも、影響追跡の9割は生きます。核心はツールではなく、5つの部位の習慣です。最初の10枚のノートがいちばん難しく、その山を越えれば、次の100枚は手が勝手に作ってくれます。
分野が3つから8つに増えつつあった四半期のことです。戦闘プランナーがスキルの射程を8mに確定しました。同じ週、レベルデザイナーはダンジョンの通路幅を6mで固定しました。どちらも自分の分野の中では完璧に合理的な決定でした。問題は3週間後のビルドで明らかになりました。範囲スキルが通路の壁を突き抜け、敵が見えもしない場所で死んでいったのです。誰のミスでもありません。二人には、互いの決定をのぞき込む窓がなかっただけです。
本章は、その窓を作る話です。各分野が自分の部屋をそのまま持ったまま、隣の部屋で何が起きているかを座標一つで分かるようにすること。その座標系をLayerと呼びます。
ゲーム企画は分野が細かく分化しています。システム・戦闘・ナラティブ・コンテンツ・レベル・バランス・UX・QA。各分野はそれぞれの道具・成果物・会議を持ちます。規模が大きくなるほど、それぞれが自分の領域に深く入り込み、他の分野が何をしているか分からない状態になります。これをサイロ(silo)化と呼びます。
サイロ化のコストは、時間が経ってからようやく表面化します。
実力不足が原因ではありません。それぞれが自分の分野で合理的に決定し、他分野の決定を認知する経路がなかっただけです。会議で埋めようとすれば会議が爆発的に増え、グループチャットで埋めようとすればシグナルがノイズに埋もれます。会議やグループチャットに価値がないという話ではなく、埋められる部分と埋められない部分の境界を明確にすることが核心です。
解決策は、領域を狭めずに(分野の分化は維持)、互いの流れが分かるように(統合された可視性)することです。相反して見える二つの要求は、同じ座標系の上に整列させれば同時に達成できます。その座標系がLayerです。オフィスにたとえれば、各自が自分の机を持ったまま、同じ壁掛け時計とカレンダーを見るようなものです。
本書で使うLayerは、0〜4の5階層の抽象化です。上に行くほど抽象的で変更がまれになり、下に行くほど具体的で変更が頻繁になります。
5つの階層それぞれがプロシージャル生成・自動化パイプラインで担う役割は、上の図の右側のラベルにあります。このマッピングが本章の背骨です。Layerを「よく整理されたフォルダ」としてだけ見るなら、半分しか見ていません。各階層は生成パイプラインの一段階(アンカー→ルール→本文→数値→ゲート)に正確に対応します。
| Layer | 何を入れるか | 変更頻度 |
|---|---|---|
| Layer 0 | ゲームがプレイヤーに与えようとする核心体験。一文に圧縮できる | 非常に低い(プロジェクトの全生涯) |
| Layer 1 | ゲームシステムの大きな構造と世界観の骨格 | 低い(マイルストーン単位) |
| Layer 2 | プレイの流れ、クエストライン、進行段階、レベルカーブ | 中程度(スプリント単位) |
| Layer 3 | 実際のデータ値、パラメーター、数式、変数 | 高い(日単位) |
| Layer 4 | ビルドで確認された結果、バグレポート、プレイ映像 | 非常に高い(リアルタイム) |
この5階層はゲーム専用の概念ではありません。同じ背骨を一般のITプロダクト開発にそのまま移せます。ゲームを作ったことのない読者は、下の職務翻訳表で各階層を自分の成果物に対応させてみてください(左はゲーム企画のLayer、右はSaaS・アプリ・社内システムなどで同じ位置に置かれる成果物です)。
| Layer | ゲーム企画 | 一般ITプロダクト | 同じ問い |
|---|---|---|---|
| L0 核心体験 | プレイヤーに与えようとする核心体験(一文) | プロダクトビジョン — 誰のどんな問題をどう解くか | 「これをなぜ作るのか」 |
| L1 システムルール | システム構造・世界観の骨格 | 業務・機能ルール — ドメインルール、権限モデル、中核ワークフロー | 「何がどう動くべきか」 |
| L2 コンテンツ | クエストライン・進行段階・レベルカーブ | リリース・ロードマップ — 機能のまとまり、リリース順序、マイルストーン | 「何をいつ出すのか」 |
| L3 データ | データ値・パラメーター・数式 | スペックシート — API仕様、フィールド定義、設定値、しきい値 | 「正確な値と定義は何か」 |
| L4 ビルド・QA | ビルド結果・バグ・プレイ映像 | デプロイ・QA — デプロイ成果物、バグレポート、モニタリングログ | 「実際に出たものが正しく動いているか」 |
読み方はゲームと同じです。上に行くほど変更がまれで(プロダクトビジョンは四半期に一度)、下に行くほど頻繁です(設定値は毎日)。先ほどのサイロ事故 — 射程と通路幅が衝突したあの場面 — は、一般ITの「バックエンドのフィールド定義(L3)とフロントの画面ルール(L1)がずれてリリース直前に爆発する」事態と正確に同じ構造です。分野の名前が違うだけで、背骨は一つです。
この5階層は絶対ではありません。規模とドメインによっては4階層が適切なことも、6階層が必要なこともあります。核心は数字が5であることではなく、階層を明示的に定義するという行為そのものです。
一つの成果物が二つのLayerにまたがることもあります。「スキルシステムGDD(Game Design Document、詳細仕様書)」はシステム設計(Layer 1)と具体データ(Layer 3)を同時に含みます。この場合は文書を分割するか、主Layerを1に置いてデータセクションを別シートに分離します。どちらの方式でも、各部分がどのLayerに住んでいるかを明示します。
分野は横に広がり、Layerは縦に積み上がります。一つの分野の作業は複数のLayerにまたがります。下のマトリクスは、11の分野(横)×Layer 0〜4(縦)の分布の重心を、セルの色の濃さで表現しています。濃いセルがその分野の重心Layerです。
縦に読めば一つの分野がどのLayerにまたがるか、横に読めば一つのLayerにどの分野が集まるかが見えます。L0(ビジョン)の行はナラティブとアートディレクションが最も濃くなっています — ビジョンに最も近い二つの分野です。L3(データ)の行にはシステム・戦闘・レベル・バランス・キャラクターが濃く集まります — マスターデータの上でこれらが互いにぶつかるというシグナルです。
この分布を明示的に持っていれば、他の分野が「戦闘のLayer 2を見ればいい」と即座に位置を把握できます。サイロの壁が崩れるのではなく、壁に窓が開くのです。
マトリクス全体を一文に縮めるとこうなります。縦軸のLayerは生成を自動化するために、横軸の分野は専門性を生かすために分けました。両者が格子の一マスで出会います。
著者がデザインディレクターとして運営しているMMORPGプロジェクトAでは、企画チーム(4〜5人)とともにLayerシステムを約6か月運用してきました(開発チーム全体は中規模、10〜50人)。具体的な事例を見てみましょう。
まず、ナラティブの5階層です。ナラティブ企画のフォルダ自体がLayerで分割されています。
ナラティブライターがLayer 2でメインストーリーの一分岐を変えると、Layer 3の台詞シートに影響が及び、すでに録音済みのLayer 4のボイスには不可逆な影響が及ぶ可能性があります。Layerを明示しているからこそ、影響範囲を即座に追跡できます。
関係図の自動生成ツールgen_relation_map.pyも併せて運用しています。マスターデータ間の外部キー関係を分析してインタラクティブなHTML関係図を作り、Layerをノードの色で表現します(赤=L1システム、黄=L2コンテンツ、緑=L3データ)。どのLayerからどのLayerへ依存が流れているかが一目で分かります。依存が逆に流れたら — L3がL1に向かって矢印を放ったら — ほぼ間違いなく設計上の欠陥です。
プロシージャルレベル生成のマスター文書は、Layer座標をfrontmatterに明示しています。
---
title: プロシージャルレベルデザインマスター v0.1
layer_inputs: [L1.World, L2.StoryLine]
layer_outputs: [L3.LevelData, L4.PlayCapture]
---
この2行で「このパイプラインはLayer 1・2を入力として受け取り、Layer 3・4を作る」と宣言され、変更時の影響範囲計算が自動化されます。L0のビジョンは明示しなくても常に入力です — どんな生成であれ、ビジョンというアンカーは毎回付いて回るからです。
文書名にLayerのprefixを強制するatomルールもあります。チーム共有atomの一つはこうです。
docs_layer_numeric_prefix_naming:マスターデータのファイル名は必ずLayer番号のprefix(L1_、L2_、L3_)を持たなければならない。prefixのないシートは整合性チェックで警告。
ルールは単純なほど強力です。名前順に並べ替えるだけでLayerごとにまとまり、AIツールもファイル名だけでLayerを把握します。人が忘れても整合性チェックが捕まえます。
前の節(2.3.4)で「L3がL1に向かって矢印を放ったら、ほぼ間違いなく設計上の欠陥」と述べました。この検出を人の目ではなくAIに任せるとどうなるでしょうか。実際に回してみた一連のやり取りを、整形せずそのまま掲載します(会社の識別情報のみ匿名化しています)。
[プロンプト全文]
添付した二つのfrontmatterを見て、Layerの依存方向が正常か確認して。
参照は高い番号→低い番号にだけ流れていれば正常。逆なら警告。
--- 文書 A (L1_combat_system.md) ---
title: 戦闘システム骨格
layer: L1
refs: [L3_skill_damage_sheet, L2_combat_flow]
--- 文書 B (L3_skill_damage_sheet.md) ---
title: スキルダメージシート
layer: L3
refs: [L1_combat_system]
[Claudeの生の出力 — 要約なしでそのまま]
二つの文書を検査しました。
文書 B (L3_skill_damage_sheet, L3):
refs → [L1_combat_system]
方向: L3 → L1 (具体 → 抽象)
判定: 正常。データシートが自分の従うシステム骨格を
参照するのは自然な方向です。
文書 A (L1_combat_system, L1):
refs → [L3_skill_damage_sheet, L2_combat_flow]
- L1 → L2 : 正常 (設計が下位の流れを指す)
- L1 → L3 : ⚠ 逆方向警告
L1のシステム骨格がL3のデータシートを直接参照して
います。これは「設計文書が特定のデータ値に
従属する」という意味で、データが変わるたびに設計
文書に手を入れる結合を生みます。
勧告: 文書 A の refs から L3_skill_damage_sheet を除去。
システム骨格が具体的な数値を知る必要があるなら、その数値を
L1に抽象ルールとして移すか(例: 「基本ダメージは
クラス係数 × レベル」)、L2のフロー文書を経由してください。
要約: 逆方向参照 1件 検出 (A: L1 → L3)。
[人による検証/拒否]
AIの判定は正しいです。ただし「L2のフロー文書を経由せよ」という勧告は、このケースでは行きすぎでした。L1が知るべきだったのはダメージ式の形であって、シートの特定のセルではありませんでした。そこで二つ目の勧告(抽象ルールとして引き上げる)だけを採択し、経由の提案は拒否しました。
[再依頼]
経由の提案は外して、「ダメージ公式の形だけL1に抽象ルールとして
上げる」という方向で L1_combat_system.md の修正diffだけ見せて。
refsも整理して。
この1サイクルが逆参照検出の本拠地です。AIが方向違反を捕まえ(自動)、人が勧告の適正ラインを削り(レビュー)、絞り込んだ作業だけを再び任せる(再依頼)。プロジェクトAではgen_relation_map.pyがグラフ単位で、portal_layer_change_impact_checkというatomが変更検知のタイミングで発動し、影響範囲の点検を強制します。
この突き合わせを人が手作業でやれば、二つの文書を開いてrefsを照合し、方向を判定するのに数分かかります。文書が数百に増えれば事実上不可能です。逆参照はいつも一つ二つとひそかに入り込み、ずっと後のビルドで初めて爆発します。
Layer統合の表面的な目的は、サイロの解消と協業言語の統一です(2.3.1〜2.3.5)。本質的な目的はもう一段深いところにあります。Layer分解が定着すると、プロシージャル生成・自動化の前提条件が整います。
前の二つの節の運営事例は、人が決定し、AIが検証・注入を手伝う段階でした。その次は、分野そのものの量産をAIが候補として作り、人が採択する段階に入ります。この移行の前提がLayer分解である理由は三つです。① AIによる候補生成は「どのLayerの何を生成するか」を明示できなければなりません。② 自動整合性チェックは、Layer間の依存方向が標準化されて初めて機能します(2.3.5の逆参照検出)。③ 変更影響の自動計算は、変更がどのLayerで起きたかという座標があって初めて可能です。三つとも「Layer分解がなければ自動化そのものが詰まる」に集約されます。座標を分けた手つきの先には、最初からプロシージャル生成が置かれていたのです。
分野別の部をまだ読んでいない段階で深入りする必要はないので、適用の二段階だけ輪郭をつかんでおきましょう。保守的適用では、人が決定し、AIが整合性チェック・変更影響計算・JIT注入を自動で支えます — 2.3.4・2.3.5の運営事例がここに当たります。ツールのコストは小さく、累積効果は運用6か月目あたりで表れ、大半の中規模(10〜50人)チームが到達できます。進歩的適用はさらに一歩進み、分野の量産そのものをAIが候補として作り(ナラティブのPersona、PCGルールブック、プロシージャルレベル、バランス変更候補、アートアセットなど)、人は「どの候補を採択するか」だけを決定します。分野ごとの形とツールの成熟度は、該当する分野の部で扱います。
進歩的適用に分野共通で必要な3要素は、① Layer分離・ラベリングのインフラ(frontmatter・atom・ファイル名prefix)、② 候補の生成・評価サイクル(AIが候補をN個 → 自動評価 → 順位・根拠レポート)、③ 人によるレビューゲート(採択された結果だけが次のLayerへ)です。ただし、どの時点でも決定論コア(シミュレーション・物理・法的制約)は人と決定論的なコードが担当し、すべてのレビューは不可逆段階(録音・キャスティング・ライブ公開など)に入る前の可逆段階で完結させます — この可逆/不可逆の境界は分野共通の原則です。
最後に時点の話を一つ。保守的適用は2010年代にも部分的に可能でしたが、進歩的適用は、AIによる候補生成の表現力、自動評価の自然言語解釈、人のレビュー負担という三つの限界に阻まれていました。LLMの発展以降、三つとも実用域に入り、進歩的適用が紙の上のビジョンから実務の段階へ降りてきました。AIの発展がプロシージャル生成・自動化の実現可能性を引き上げたという、この本全体を貫くメタメッセージがここにあります。
本書の分野別の部は、各分野が主にどのLayerに分布するかを導入部で明示し、章の中でもLayer座標を頻繁に使います。先に整理しておきます(2.3.3のマトリクスの重心を表に移したものです)。
| 分野 | 主なLayer | 備考 |
|---|---|---|
| システム企画 | L1〜L3 | 設計骨格からマスターデータまで幅広く |
| 戦闘企画 | L1〜L3、一部L4 | コンボ骨格〜ダメージシート、ビルド計測 |
| ナラティブ企画 | L0〜L4 | 5層構造をフォルダで運用 |
| コンテンツ企画 | L2中心 | 進行フロー・クエストライン |
| レベルデザイン | L2〜L3 | プロシージャル生成パイプラインを含む |
| バランス企画 | L3中心、L4で計測 | データ値・カーブ・検証計測 |
| UX/UIデザイン | L1〜L3 | インタラクション骨格〜画面データ |
| QA設計 | L4中心、L0〜L3を検証 | すべてのLayerがビルドに反映されたかを検証 |
| キャラクター・ペット・マウント | L1〜L3 | システム・世界・データ |
| アートディレクション | L0〜L1 + L4成果物 | ビジョン・世界ガイド + ビルドのチェック |
| 運営(ライブオプス) | L2〜L4 | 運営サイクル・リアルタイムデータ |
各分野は他のLayerにも触れますが、重心が分かれば協業の通路が見えます。バランス(L3)と運営(L2〜L4)はL3で出会うため常に近くで協業すべきですし、ビジョン(L0)に最も近い二つの分野はナラティブとアートディレクションです。こうした隣接関係が、座標系の上で自然に浮かび上がります。
Layerシステムを最初から完璧に導入しようとすると、スタートすら切れません。段階的に取り入れるのが正解です。
flowchart LR
S1["第1段階
単一分野で導入
(推奨:ナラティブ)"] --> S2["第2段階
隣接分野へ拡張
(ナラティブ+コンテンツ)"]
S2 --> S3["第3段階
全分野の標準化
(ファイル名prefix・整合性チェック)"]
S3 --> S4["第4段階
AIツール統合
(JIT・変更影響・振り返り分類)"]
S1 -.->|"小規模(〜10人)チーム"| E1["ここまでで十分"]
S3 -.->|"中規模(10〜50人)チーム"| E2["ここまでを推奨"]
S4 -.->|"大規模(100+)チーム"| E3["ここまで行ってこそ効果"]
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class S1,S2,S3,S4 human
class E1,E2,E3 pass
L1_・L2_・L3_のprefix)を導入し、関係図・整合性チェックを自動化します。各段階は最短でも1か月、長ければ四半期単位です。無理をすると人が疲弊します。運用負担が導入価値を超えないよう速度を調節するのがディレクターの仕事です。
小規模(〜10人)は第1〜2段階、中規模(10〜50人)は第3段階、大規模(100+)は第4段階まで行って初めて効果が出ます。小さいチームには使えないという意味ではありません。深さが違うだけで、核心的な価値は第1段階ですでに始まっています。
Layerは単なるフォルダ整理の技法ではありません。分化したゲーム企画を一つの座標系で束ね、AIが推論できるようにするメタ原則であり、さらには分野別のプロシージャル生成・自動化に共通する前提条件です。
本書の残りのすべての部は、本章を前提にしています。分野別の部は各分野がLayerで占める座標を導入部で明示し、プロセスの部はLayerを横断する運営システムを、運営の部はLayerシステム自体のself-improvingサイクルを扱います。
次の章(ゲームオントロジーと知識グラフ)は、Layerの上に意味の関係を加えます。Layerが座標なら、オントロジーはその座標の上の意味の矢印です。両者が合わさって初めて、AIは「この文書があの文書に影響を与える」を自律的に推論します。
はっきりさせておきたいことがあります。本章のどの自動化も、決定を代行してはいません。逆参照の検出で機械は違反候補を並べただけで、何をどこまで受け入れるかを選んだのは人の手でした。Layerは、人がより良い決定をより速く下せるよう助ける座標系であって、決定を丸投げする装置ではありません。
layer_unified_design_philosophy — 本章の母体となるatomdocs_layer_numeric_prefix_naming — ファイル名prefixの強制ルールdead_table_5layer_cleanup — 5階層外のシート整理ルールportal_layer_change_impact_check — 変更影響の自動チェックsetup — 一つの分野(推奨:ナラティブ)のフォルダを選び、下位フォルダをLayer0_Vision/からLayer4_BuildVO/まで5つに分けてみましょう。既存のファイルを該当するLayerへ移してください。名前が曖昧なファイルは「この文書が変わる頻度」を基準に配置します(頻繁に変わるなら下のLayer)。
prompt — マスターデータのfrontmatterを二つ選び、2.3.5のプロンプト全文をそのまま貼り付けて、Layerの依存方向を判定させてみましょう。核心ルールを一行だけ正確に与えれば十分です。「参照は具体→抽象(高い番号→低い番号)へのみ流れていれば正常」。
verify — AIが逆方向の参照を捕まえたら、その勧告をそのまま受け取らず、適正ラインを自分で削りましょう(2.3.5の「人による検証/拒否」)。採択した方向だけをdiffで再依頼します。名前順に並べ替えたときにLayerが上から下へまとまって見えれば、prefixルールが根付いた証拠です。
一人で作業していてもLayerは機能します。チームがないので「分野間のサイロ」はありませんが、「時点間のサイロ」があります。3週間前の自分と今日の自分は、互いの決定を忘れます。フォルダをLayer 0〜4に分けるだけでも — ビジョン1枚、システム骨格数枚、進行フロー、マスターデータ、ビルドメモ — 過去の自分がどのマスに何を置いたか即座に見つかります。AIに「いまL2の作業中」と一行添えるだけで、無関係なLayerの資料を引っ張ってこなくなります。4階層・3階層に減らしても構いません。数字ではなく「階層を明示する」という行為が核心です。
月曜の午前、変更リクエストが1件上がってきました。戦闘チームのメンバーAが社内メッセンジャーに一行を書き込みます。「グローバルクールダウン(GCD)を0.5秒 → 0.3秒に変えます。影響を受けるところはありますか?」普段ならここから30分の会議が始まります。ダメージ計算式の担当が手を挙げ、コンボキャンセルルールの担当が割り込み、誰かが「ボスパターンにも影響があるのでは」と尋ねます。誰も全体像を頭の中にすべて持ってはいないので、会議は記憶をたどる作業で埋まっていきます。
ところが今回は違います。リクエストが上がった1秒後、ボットが自動でコメントを付けます。「このatomを変えると4個のatomが影響を受けます。skill_dps_calculation、combat_combo_cancel_v3、refgame_boss_pattern_phase2、balance_curve_v3。担当:メンバーB、メンバーA、メンバーC」。会議は開かれませんでした。4人がそれぞれ自分のatomだけを確認して終わりました(このボットは後ほど自分たちの手で作ります — 2.4.3)。
このコメントは魔法ではありません。2.3ですべてのatomにLayer座標を与え、その上に本章で意味の矢印 — どの決定がどの決定に影響を与えるのか — を加えたからです。座標は「ここに何がある」までしか語りません。「これがあれに影響を与える」「あれが先にないと成立しない」「この2つは同時に有効にしてはいけない」といった関係は、座標の上に描かれる矢印です。本章では、その矢印をどう表記し、壊れた矢印をどう自動で捕まえるかを扱います。
用語メモ - オントロジー(ontology):概念とその間の関係を明示的に定義した体系。本書では6〜12個の関係に単純化した軽量版を使います。 - wikilink:
[[atom_name]]形式の文書間リンク。Obsidian・Roamなどで使われる表記を借用しました。 - バックリンク(backlink):「このatomを指しているatom」の一覧。順方向参照の逆方向。 - 孤立ノード(orphan):どこからも参照されていないatom。廃止候補のシグナル。 - リンク切れ(broken link):存在しないatomを指すwikilink。タイポ・名前変更の痕跡。
2.1でYAMLフロントマターとしてメタデータを付け、atomの本文にはwikilinkをちりばめました。それだけで文書はすでに網の目のようにつながります。問題は、そのつながりが何を意味するのか書かれていないという点です。
この決定は [[skill_cooldown_rule_v2]] の上で成立する。
この一行は「skill_cooldown_rule_v2に言及している」までしか語りません。なぜ言及するのでしょうか。この決定があの規則を必要とする(requires)のか、あの規則から派生した(derives_from)のか、それともあの規則と競合する(conflicts_with)のか。人は文章を読めば分かりますが、機械には分かりません。AIに「この決定を有効にすると壊れるものはあるか」と尋ねても、意味のないリンクだけでは答えられません。
そこでwikilinkに関係タイプを着せます。ゲーム企画で実際に使われる関係は意外と少ないものです。次の6つが90%以上をカバーします。
この6つをenumとして固定するatomがontology_relation_enum_v1です。新しい関係タイプを追加するには、変更リクエストのレビューを通すようにします。増えても10〜12個が適正ラインで、最初はaffects・derives_from・requiresの3つで始めても十分です。関係を書く場所はatomのYAMLフロントマターです。
---
name: combat_combo_cancel_v3
layer: 1
affects: [skill_dps_calculation, refgame_boss_pattern_phase2]
derives_from: [vision_taste_focused_combat]
requires: [combat_input_buffer_system, skill_cooldown_rule_v2]
conflicts_with: [skill_cancel_rule_legacy_v1]
---
人は順方向の1行だけを書きます。逆方向(「自分を誰がaffectsしているか」)は、ツールが全体をスキャンして計算します。affectsを両側に書くと同期の負担が2倍になり、片側だけ直してもう片側を忘れた瞬間、グラフが嘘をつき始めます。一方向だけ手で、逆方向はツールで — これが本章全体を貫く原則です。
ここからは、実際にあった1つのセッションを丸ごと載せます。状況はこうです。コンボキャンセル規則をv2からv3へ作り直すにあたり、skill_cancel_rule_legacy_v1を廃止することにしました。ただし廃止する前に、このatomを指している他のatomがないか(バックリンク)、そして廃止後にリンク切れや競合が残らないか(グラフ整合性)を確認しなければなりません。手でgrepすると見落とします。Claudeにやらせます。
添付した atom フォルダ(decisions/)には atom があり、フロントマターに関係
(affects/derives_from/requires/conflicts_with/is_a/part_of)が書かれて
いるはず。本文には wikilink もある。いま skill_cancel_rule_legacy_v1
を廃棄しようとしているが、廃棄前に3つを検査して: 1) 逆参照 2) 壊れたリンク
3) 競合。推測せず実際にファイルを読んで答えて。見つからなければ見つからないと言って。
// 逆参照=YAML 関係+本文 wikilink の両方 / 壊れたリンク=廃棄で stale・未存在になるリンク
// 競合=conflicts_with の相手のうち status: approved が生きているか
decisions/フォルダの47個のファイルを読みました。
skill_cancel_rule_legacy_v1に対する3つの検査結果です。1. バックリンク(このatomを指しているatom)
YAML関係で指しているatom: -
combat_combo_cancel_v3—conflicts_with: [skill_cancel_rule_legacy_v1]-skill_cancel_rule_v2—derives_from: [skill_cancel_rule_legacy_v1]本文のwikilinkで指しているatom: -
combat_combo_cancel_v3の本文:「旧バージョン[[skill_cancel_rule_legacy_v1]]の先行入力の問題を解決する」 -qa_combat_regression_checklistの本文:「リグレッション項目3番は[[skill_cancel_rule_legacy_v1]]を基準に作成」つまり4個のatomがこのatomを参照しています(YAML 2 + 本文wikilink 2。ただしcombat_combo_cancel_v3は両方なので、実際のatom数は3個です)。
2. リンク切れ(廃止で壊れる箇所)
廃止そのものではwikilinkは「壊れ」ません — ファイルは残り、statusがdeprecatedになるだけだからです。ただし: -
qa_combat_regression_checklistは廃止されたatomを基準にリグレッション項目を定義しているため、内容として無効になります。これはリンク切れではありませんが、staleリンクです。要検討。 - もしファイルを削除までされるなら、上の4個の参照がすべてリンク切れになります。⚠️ 1つ曖昧な点があります:
balance_curve_v3の本文に[[skill_cancel_rule_legacy]](末尾に_v1がない)というリンクがあります。これがタイポで_v1を指そうとしたものなのか、それとも別のatomを意図したものなのか、ファイルだけでは確信できません。skill_cancel_rule_legacyという名前のファイルはフォルダにありません。つまり、これはすでにリンク切れである可能性が高いです。確認が必要です。3. 競合の残存(conflicts_with相手が生きているか)
skill_cancel_rule_legacy_v1とconflicts_withで結ばれたatomはcombat_combo_cancel_v3の1つです。 -combat_combo_cancel_v3のstatus:approved(生きている)→ 廃止対象と競合していた新規規則が正常に承認された状態です。これは正常です。旧バージョンを廃止して新バージョンが生き残る、自然な置き換えです。競合残存の警告ではありません。
要約:廃止は安全ですが、2つを先に処理してください。 (a)
qa_combat_regression_checklistのstaleリンク — リグレッション項目3番の更新。 (b)balance_curve_v3の[[skill_cancel_rule_legacy]]— タイポの疑い。すでにリンク切れの可能性。
ここで私は、出力をそのまま受け取りませんでした。(a)は正しい — リグレッションチェックリストは私が更新すべきものです。しかし(b)で立ち止まりました。Claudeはbalance_curve_v3の[[skill_cancel_rule_legacy]]を「タイポの疑い」としましたが、本当にリンク切れなのかは、私が直接フォルダを見る必要がありました。AIの「確信できない」は正直なシグナルであって、結論ではありません。
フォルダを開いて確認すると、skill_cancel_rule_legacyというファイルは実際にありませんでした。ところがgit logを見ると、6か月前にskill_cancel_rule_legacy → skill_cancel_rule_legacy_v1へと名前だけを変えたコミットがありました。そのときbalance_curve_v3の本文リンクを更新しそびれて、そのまま流してしまったのです。つまり、これはタイポではなくリネーム後に取り残されたリンク切れでした。Claudeの「タイポの疑い」という推測は半分だけ当たっていました。原因はタイポではなくリネーム漏れです。この違いを私が捕まえていなければ、リグレッションチェックリストだけを直して、リンク切れはそのまま埋もれていたでしょう。
確認した。[[skill_cancel_rule_legacy]] はタイポではなく、6か月前の
rename(_legacy → _legacy_v1) のとき本文リンクを直しそびれた壊れたリンクだった。
ではこの機会にフォルダ全体から「存在しない atom を指す wikilink」を
全部探して。本文 wikilink 基準で、対象ファイルがフォルダにないもの全部。
ファイル名とその中の壊れたリンクを表で。
47個のファイルのすべての
[[...]]本文wikilinkを収集し、フォルダのファイル一覧と照合しました。対象ファイルが存在しないリンク切れは3件です。
リンクを持つファイル リンク切れのwikilink 推定原因 balance_curve_v3[[skill_cancel_rule_legacy]]リネーム漏れ(→ _v1)quest_reward_table_v2[[concept_reward_currency]]対象atomが未作成(計画だけあってファイルなし) refgame_boss_pattern_phase2[[skill_global_cooldown]]リネーム漏れ(→ skill_global_cooldown_rule)3件のうち2件(リネーム漏れ)は、対象のatom名を
_v1/_ruleに直せば済みます。1件(concept_reward_currency)は指す先のatom自体がないため、作成するかリンクを削除する必要があります。自動では直していません — どちらを意図されたのか、確認のうえ進めてください。
3件とも事実でした。手でgrepしていたら、quest_reward_table_v2のリンク切れはほぼ確実に見逃していたでしょう。あのリンクは「まだ作っていないatomをあらかじめ指しておいた」意図された未来参照でしたが、6か月間誰もそのatomを作らず、事実上、死んだ約束になっていました。
このセッションが示すことは単純です。バックリンクの検出とリンク切れの検出は、フォルダ全体を読んで照合する作業に強いAIが担い、原因の判定と意図の確認は人が担う。AIは「ここが壊れている」まで、人は「なぜ壊れ、どう直すか」まで、です。
前節の検査を毎回プロンプトで回すこともできますが、同じ検査をコードに固めると、グラフの上で一目で見えるようになります。プロジェクトAでは、2.3で紹介したgen_relation_map.pyを拡張したグラフツールがR&Dとして動いています。核心は、フォルダのatomを読んでnetworkxの有向グラフとしてビルドした後、4つの検査関数を載せることです。
import networkx as nx
# build_graph(folder): atom フォルダを読んでノード(=atom)と
# YAML 関係エッジで DiGraph を作る。(全文は「やってみよう」)
def find_cycles(G): # 循環依存
return list(nx.simple_cycles(G))
def find_orphans(G): # インバウンド 0 = 孤立候補
return [n for n in G.nodes if G.in_degree(n) == 0]
核心は2行です。simple_cyclesが循環依存(A requires B requires C requires A)を、in_degree(n) == 0が孤立ノードを捕まえます — 自分でDFSを書く必要はありません。残りの2つの関数も同じ調子の1行ものです。find_broken_wikilinksは本文の[[...]]を正規表現で収集してノード一覧にないものを選り分け、バックリンクはグラフを逆向きにたどれば出てきます(全文は「やってみよう」)。可視化は、ノードの色をLayerで、エッジの色を関係タイプで塗り、よく参照されるノード(インバウンドのエッジが多いノード)は大きく描いて、ハブが浮かび上がるようにします。キャビネットに色ラベルを貼って整理したフォルダのように、視野の中でパターンが先に浮かんでくるのです。
以下は、2.4.2のセッションで扱ったatomの実際の関係を写したグラフです。矢印の向きは「出発atomが到着atomへ関係を張る」という意味です。
graph LR
combo[combat_combo_cancel_v3
L1·approved]
legacy[skill_cancel_rule_legacy_v1
L1·deprecated]
v2[skill_cancel_rule_v2
L1]
dps[skill_dps_calculation
L3]
vision[vision_taste_focused_combat
L0]
buffer[combat_input_buffer_system
L1]
boss[refgame_boss_pattern_phase2
L2]
qa[qa_combat_regression_checklist
L4]
broken[skill_cancel_rule_legacy
存在しない]
combo -->|affects| dps
combo -->|affects| boss
combo -->|derives_from| vision
combo -->|requires| buffer
combo -->|conflicts_with| legacy
v2 -->|derives_from| legacy
qa -.stale.-> legacy
balance[balance_curve_v3] -.broken.-> broken
classDef dep fill:#eee,stroke:#999,stroke-dasharray:4
classDef miss fill:#fff,stroke:#cc2222,stroke-dasharray:4
class legacy dep
class broken miss
点線で描かれた2本のエッジが、2.4.2で人による検証が捕まえた問題です。qa → legacyは廃止atom基準のstaleリンク、balance_curve_v3 → skill_cancel_rule_legacyは存在しないノードを指すリンク切れ。グラフに描けば、この2本の点線が実線の間で際立ちます。テキストだけで運用していたら、47個のファイルのどこかに埋もれて、永遠に見えなかったでしょう。
検証ゲート(Layer 4)で自動で回る規則は4つです。
requiresの連鎖が自分自身に戻ってきたら警告。simple_cyclesで検出。conflicts_withで結ばれた2つのatomが両方ともstatus: approvedなら警告(2.4.2のケースは片方がdeprecatedのため通過)。graph_orphan_detection_quarterlyがこの周期を明示しています。docs_layer_numeric_prefix_namingが文書名にLayer番号のprefixを強制しているため、逆行はファイル名を見るだけでも一次的にふるい落とせます。この4つの規則がコードに固まれば、2.4.2のように毎回プロンプトを組む必要はありません。変更リクエストが上がった瞬間にボットがグラフを再ビルドし、影響を受けるatomの一覧とリンク切れ・循環・競合を自動コメントとして付けます。章の冒頭の「4個のatomが影響を受けます」というコメントがまさにこれです — このボットが、先ほど予告したあのボットです。
ここで、2.3と2.4がなぜひとまとまりなのかを押さえておく必要があります。Layer座標と関係の矢印は別々に導入したものではなく、同じ目的の2つの面です。
表面的には、Layerはコラボレーションの言語を統一します — 「これはL1のシステム決定」「あれはL3のデータ」と呼べば、分野が違っても同じ座標を共有できます。しかし本質的な目的は別のところにあります。Layerはプロシージャル生成のために分けた座標でした。
L0のビジョンはコンテキストのアンカーです — 不変であり、AIに毎回注入されます。L1のシステムは生成の入力規則です — ルールブック・関係・タグがここに住みます。L2のコンテンツは生成された本文が積み上がる場所、L3のデータは数値・ID・関係としてシミュレーションの入力、L4のビルド・QAは検証ゲートです。関係の矢印は、この座標の上で生成の制約条件として働きます。AIが新しいコンテンツを作るとき、requiresの矢印は「これが先にないといけない」という前提になり、conflicts_withの矢印は「これは一緒に有効にしてはいけない」という禁止になります。
分野は分化しますが(戦闘・クエスト・経済がそれぞれ専門性を持つ)、すべての成果物がLayer座標を持つため互いを認知します。分化と統合が1つの座標系の上で同時に成立するのです。このグラフが十分に育てば、AIは候補を生成しながら関係の矢印を自動の制約として読み、人はレビューのゲートで違反の有無だけを確認すればよくなります。2.4.2のワークド・トランスクリプトがその縮小版です — AIがグラフを読んで制約違反(リンク切れ・競合)を見つけ、人がゲートで判定しました。
関係の矢印の出発点・到着点になるノードは、決定atomだけではありません。「スキル」「クエスト」「報酬」のようなドメイン概念を定義したatomも、グラフの第一級市民です。concept_skill_definition_v1はこんな形をしています。
# スキル (Skill)
Definition: キャラクターが戦闘中に発動する単位アクション。入力・クールダウン・リソース・効果を含む。
Required Properties: input / cooldown / cost / effects
Subtypes: active_skill (is_a) / passive_skill (is_a) / ultimate_skill (is_a)
Not a skill: 自動攻撃 [[concept_auto_attack]] / 変身 [[concept_transformation]]
boss_skill is_a skillと書けば、上位概念の規則が自動的に継承されます。プロジェクトAにはこうした概念定義atomが19個ほどあり、すべての決定atomがこれらを参照します。語彙が1つの辞書に統一されると、会議・文書・コードの間の翻訳負担が消えます。机ごとに別の辞書を置かず、同じ辞書を共有するようなものです。先ほどのワークド・トランスクリプトでリンク切れとして捕まったconcept_reward_currencyが、まさに「辞書に載せると決めておきながら、まだ作っていない空の項目」でした。
ここまで読むと、「これはOWL/RDFのような正式なオントロジーではないのか」という質問が出てきます。違います。そして、わざと違うように作りました。
学術オントロジー(OWL・RDF・SKOS)は強力ですが、関係タイプが数十〜数百個あり、専用の推論エンジンが必要で、運用するにはオントロジーの専門家が付かなければなりません。検索エンジン・医療・法律のように精密な推論が生死を分ける領域では必須です。しかしゲーム企画で実際に必要な推論は「変更の影響範囲」「先行依存関係」「競合検出」だけで、すべてグラフを1マスずつたどって調べる単純探索(BFS・DFS)のレベルで済みます。networkxの1行で循環を捕まえた前節がその証拠です。正式な推論エンジンは過剰です。
基準は1つです。プランナーが手で運用できるか。YAMLの6個のenum、networkxでの処理、pyvisやD3.jsでの可視化。この線を越えた瞬間、ツールはツールであることをやめ、もう1つの負担になります。軽さは妥協ではなく、設計の意図です。
この罠を避けるうえで、繰り返される失敗が5つあります。どれも「オントロジーを強制標準として扱った場面」という同じ根から育ちます。
| 失敗 | 回避法 |
|---|---|
| 関係タイプを最初から定義しすぎる | 3つ(affects・derives_from・requires)で始め、必要なときだけ追加 |
| すべての決定に関係を強制する | 関係のないatomも正常と認める — 空の関係がグラフを汚す |
| affectsを双方向に書く | 一方向だけ手で、逆方向はツールで自動計算 |
| OWL・RDFへのこだわり | 運用できる水準(YAML + enum)にとどまる |
| 可視化なしでテキストだけ運用する | 単純なHTMLビューでも最初から提供する — 2.4.3の点線のように、見えなければ直されない |
1・2・3番は導入1か月以内にパターンを押さえ、4・5番は3か月目の振り返りで点検すれば、自然と整っていきます。
最初のひと月は赤字です。関係を書く負担だけが積み上がり、目に見える効果はありません。だから小さく始めます。最初の週はaffects・derives_from・requiresの3つの関係だけ、2〜4週目は中核のatom 20個に適用しながら、グラフが育っていく様子を見守ります。1か月目に前節レベルのHTMLグラフビューを1つ作れば、2か月目から可視化が価値を見せ始め、3か月目には自動検証(循環・競合・孤立・リンク切れ)が会議時間を直接減らします。赤字のひと月に耐えることが、導入の成否の分岐点です。
次の第8章Wikilinkでは、本章で矢印として扱った[[...]]表記を運用の次元で深く掘ります — バックリンクパネルを毎日どう使うか、名前変更時にリンクをまとめてどう更新するか(2.4.2のリネーム漏れをそもそも防ぐ方法)、Obsidianのようなツールのグラフビューを実務にどう溶かし込むか。YAML(第4章)→ Atom(第5章)→ Layer(第6章)→ Ontology(第7章)→ Wikilink(第8章)が、情報アーキテクチャの完成された五角形です。
setup. atomが集まったフォルダ(例:decisions/)を決め、各atomのYAMLに関係キーを書く準備をしましょう。最初はaffects・derives_from・requiresの3つだけ。本文のリンクは[[atom_name]]形式に統一します。
prompt. 廃止・名前変更の前に、以下を投げてみましょう。
このフォルダのマークダウン atom を読んで。私が [対象_atom] を廃棄/変更しようとしているが
(1) 逆参照: この atom を YAML 関係と本文 wikilink で指す atom 全部
(2) 壊れたリンク: 変更/削除時に壊れるか stale になるリンク
(3) 競合の残存: conflicts_with の相手のうち status: approved のもの
を検査して。推測せず実際にファイルを読んで、見つからなければ見つからないと言って。
verify. AIが「タイポの疑い」「確信できない」と付けた箇所は、人が直接開いて確かめましょう。リンク切れの原因(タイポか、リネーム漏れか、未作成か)は、git logとフォルダの実物で人が判定します。自動修正はさせず、意図を確認してから自分の手で直します。
本文(2.4.3)では核心の2つの関数だけを見せました。フォルダ全体をグラフとしてビルドし、4つの検査をかける全文は次のとおりです。
import networkx as nx
import re, yaml, glob, os
REL_TYPES = ["affects", "derives_from", "requires",
"conflicts_with", "is_a", "part_of"]
WIKILINK = re.compile(r"\[\[([a-zA-Z0-9_]+)\]\]")
def build_graph(folder):
G = nx.DiGraph()
files = {}
for path in glob.glob(os.path.join(folder, "*.md")):
name = os.path.splitext(os.path.basename(path))[0]
text = open(path, encoding="utf-8").read()
fm = yaml.safe_load(text.split("---")[1]) or {}
files[name] = fm
G.add_node(name, layer=fm.get("layer"), status=fm.get("status"))
# YAML 関係エッジ
for name, fm in files.items():
for rel in REL_TYPES:
for tgt in (fm.get(rel) or []):
G.add_edge(name, tgt, type=rel)
return G, files
def find_broken_wikilinks(folder, known_nodes):
broken = []
for path in glob.glob(os.path.join(folder, "*.md")):
text = open(path, encoding="utf-8").read()
for m in WIKILINK.findall(text):
if m not in known_nodes:
broken.append((os.path.basename(path), m))
return broken
def find_orphans(G):
# インバウンド 0 かつ part_of/is_a の親もないノード
return [n for n in G.nodes if G.in_degree(n) == 0]
def find_cycles(G):
return list(nx.simple_cycles(G))
ツールがなくても大丈夫です。atomフォルダが1つ、Claudeが1つあれば十分です。新しい決定を書くときにYAMLへrequires・affectsの2行だけ追加し、廃止・名前変更が発生するたびに上のpromptを1回回してみましょう。グラフの可視化は後回しで構いません — リンク切れと競合を変更の直前に1回ざっと見る習慣、その1回が、一人運用で最大の赤字を防ぎます。
木曜日の午後4時50分。バランス担当が埋めたスキルシートが、ちょうど上がってきました。スキルは312個。各スキルはeffect_idという欄に効果番号を記入することになっており、その番号は別の効果シートにある行を指します。両者が噛み合って初めてゲームは動きます。合わなければ、クライアントが空の効果を呼び出すか、静かに落ちます。
以前の私は、これを目視でチェックしていました。スキルシートのセルを1つ見て、効果シートへジャンプし、番号を確認して、また戻る。これを312回。速くても2時間です。目がかすんでくる最後の50個で必ず1〜2個を見落とし、その1〜2個がQAビルドで火を噴きました。
本章は、その2時間がどこへ消えたのか、そしてあの整合性チェックがシステムプランナーの作業地図のどこに打たれる座標なのか、という話です。先に座標を定めないと、AIをどこに組み込むべきかを、いつまでも勘だけで決め続けることになります。
システムプランナーは、抽象と具体のあいだを最も広く行き来する人です。ビジョンという霧を受け取り、マスターデータの最後のセルという固い数字まで引き下ろしていきます。その道のりで生まれる成果物は4種類です。
(1)ビジョンを構造に翻訳する。 ディレクターが「打撃感が生きたアクション戦闘」と言えば、システムプランナーはそれをスキル・コンボ・キャンセル・ヒットストップという骨格に置き換えます。「成長の自己決定権」はクラス・スキルツリー・装備システムになります。霧が構造物になる最初の瞬間です。
(2)システム間のインターフェースを仕様化する。 戦闘・移動・インベントリ・ショップ・クエスト・ギルドが同時に動きます。戦闘中にインベントリを開いたら無敵が付くのか? 強化の最中にPvPの申請が届いたら? こうしたケースの答えが集まって、「よくできている」という手触りを作ります。答えが抜けた場所ごとに、ユーザーはストレスを感じます。
(3)マスターデータとそのスキーマに責任を持つ。 スキル312個の係数、数百個のアイテムの効果、数十種のモンスターの行動。値は自分で埋めるか、バランスやコンテンツの担当へ渡します。しかしシートの カラム定義(スキーマ) だけは、システムプランナーが握ります。ラベルの付いた引き出しを用意してあげる仕事です。引き出しが雑だと、人によって違う入れ方をしてしまい、整合性が壊れます。
(4)行動ロジックを設計する。 キャラクターやモンスターのAIは、ステートマシン(FSM、Finite State Machine、有限状態機械)、ビヘイビアツリー(Behavior Tree、以下BT)、決定テーブル、プロシージャル規則といった形で出てきます。この資料がプログラマーに渡り、コードになります。
この4つがすべて1人の机の上で出会う、という点が核心です。だからこそ「今日は何に時間を使うか」が、システムプランナーにとって最大の運営判断になります。
2.3で私たちは、ゲームの制作物全体をL0(ビジョン)からL4(ビルド)までの座標軸に載せました。今度は3.1.1の4つの成果物を、その軸の上にそのまま打ってみます。システム企画ほど、1つの分野の成果物が複数のLayerに広く散らばる例はまれです。
次の図は、成果物がLayer上のどこに住んでいて、各座標で誰と出会うのかを1枚に描いた地図です。
この地図が語ることは2つです。第一に、システムプランナーはL0を受け取ってL4まで届かせるという長い距離に責任を持ちます。第二に、自分の手で直接作る区間はL1〜L3で、その3つのマスごとに協業相手が変わります。マスが変わるたびに協業の言語も変わるため、座標を意識しないと会議が空回りし続けます。
ただし、1人がL1〜L3をすべて触るという意味ではありません。チームが大きければ、L1〜L2の担当とL3の担当は分かれます。チームが小さければ1人で全部見ます。座標は役割分担の地図であって、1人に全部押し付けろという命令ではありません。
地図が描けたので、今度は色を塗ります。どの座標がAI導入の効果が大きいのか。やみくもに「全部自動化」ではなく、座標の性質を見て選びます。
flowchart TD
L1["L1 システムの骨格
(クラス数、戦闘モデル)"]
L2["L2 インターフェース
(相互作用ルール)"]
L3["L3 スキーマ + データ
(312行のシート)"]
L1 -->|"ゲームのアイデンティティに直結
人が決定"| H1["AI = 人の決定の変形/検証を補助"]
L2 -->|"ケースが爆発
影響範囲が大きい"| H2["AI = 変更の影響範囲を自動抽出"]
L3 -->|"定型・反復
整合性チェック"| H3["AI = 生成・検証・変換を専任"]
H1 --> R["人は核心の決定、
AIはディテール・整合性・反復"]
H2 --> R
H3 --> R
style L3 fill:#fff8e1,stroke:#f9a825
style H3 fill:#e8f5e9,stroke:#2e7d32
style R fill:#e3f2fd,stroke:#1565c0
核心は、座標が下に降りるほどAIに専任させる比重が大きくなるということです。L1の「クラスをいくつにするか」はゲームのアイデンティティに直結するため、人が握るべきです。逆にL3の「312行の外部キーがすべて合っているか」は定型・反復なので、AIに丸ごと任せるべきです。L2はその中間です。決定は人がするものの、「このルールを変えたらどこまで揺れるのか」という影響範囲の抽出をAIが支えます。
この図が、3.1.4以降のすべての実習がなぜL3の近くから始まるのかを説明しています。効果が最も大きく、リスクが最も小さい場所だからです。スキーマツールが誤作動しても事故にはなりませんし、関係マップは図を描くだけですし、整合性チェックは人が拒否できます。
理論はここまでです。3.1の冒頭のあの木曜日に戻ります。スキルシートにある312個のeffect_idが効果シートと合っているか、AIにやらせてみます。要約せず、実際にやり取りしたままを載せます。
データ入力はschema-firstの原則に従います。つまり、人が先に仕様書シートを定義し、そこからEnumと.protoが生成され(VBA(Excelのマクロ言語)によるExport)、最後にcsvデータが埋められます。だから検証プロンプトも「スキーマから読め」で始まります。正本はスキーマドキュメントではなく 実際にexportされたJSON だという点を、先に明言しておきます(json_over_schema_doc_as_source_of_truth)。ドキュメントは嘘をつけても、exportされたJSONは嘘をつけないからです。
あなたは私たちのプロジェクトのデータ整合性チェッカーです。 入力は2つのexport JSONです。 1)
skill.json— 各スキルオブジェクトにskill_id、name、effect_idフィールドがあります。 2)effect.json— 各効果オブジェクトにeffect_id、effect_typeがあります。次を検査してください。 (A)
skill.jsonのすべてのeffect_idがeffect.jsonに存在するか(外部キー整合性)。 (B)effect.jsonにはあるが、どのスキルからも参照されていない孤児(orphan)効果。 (C)skill.jsonの中でeffect_idが空、または0になっている行。ルール:推測しないこと。JSONにないフィールドは「フィールドなし」と報告すること。 出力は(A)(B)(C)の3セクションに分け、違反1件ごとに
skill_id(またはeffect_id)と理由を1行で。 違反がなければ、該当セクションに「違反なし」とだけ書くこと。
入力のJSON 2つを受け取っていません。現在のメッセージにはファイルの内容が添付されていません。 次のいずれかの形で送り直してください。 -
skill.jsonとeffect.jsonの実際の内容(貼り付けまたはファイル添付) - または2つのファイルのパス内容を受け取ったら(A)(B)(C)の順に検査します。ファイルなしで結果をでっち上げることはしません。
ここで止まったことが、むしろ信頼のシグナルでした。入力が空なのに、もっともらしいレポートが返ってきたとしたら、その綺麗さこそ危険信号です。手ぶらで検査結果を作り出さず、「まずファイルをください」と聞き返してきたほうが信頼できます。私は2つのJSONを実際に貼り付けて再依頼しました(分量の都合で、本文には一部のみ抜粋します)。
// skill.json (抜粋)
[
{"skill_id": 1001, "name": "強打", "effect_id": 5001},
{"skill_id": 1002, "name": "回転斬り", "effect_id": 5002},
{"skill_id": 1003, "name": "盾上げ","effect_id": 0},
{"skill_id": 1004, "name": "毒塗り", "effect_id": 5099}
]
// effect.json (抜粋)
[
{"effect_id": 5001, "effect_type": "DAMAGE"},
{"effect_id": 5002, "effect_type": "DAMAGE"},
{"effect_id": 5003, "effect_type": "BUFF"}
]
(A) 外部キー整合性 — 違反1件 -
skill_id: 1004→effect_id: 5099がeffect.jsonに存在しない(dangling reference)(B) 孤児効果 — 違反1件 -
effect_id: 5003(BUFF) → どのスキルからも参照されていない(C) 空/0のeffect_id — 違反1件 -
skill_id: 1003→effect_id: 0。意図された「効果なし」の可能性もあるため、0が有効値かどうかスキーマでの確認が必要。
3件のうち2件は明白なバグでした。5099は、バランス担当が効果シートに行を追加する前に、スキル側へ先に書き込んでおいたものでした。作業の順序がずれた典型的なケースです。これで1つ捕まえました。
ところが(C)は拒否しました。私たちのプロジェクトでは、effect_id: 0は「パッシブスロット、効果なし」を意味する有効な約束値です。AIはそれを知らないので「確認が必要」と報告してきました。その判断自体は正しいものの、違反として数えるものではありません。そこで私は次の一手に進みました。この約束をatomとして固定化し、次のチェックからはAIが0を有効値として知っている状態を作ることです。
追加ルール:
effect_id == 0は「パッシブ、効果なし」を意味する、私たちのプロジェクトの有効な約束値です。 したがって(C)では、effect_id == 0は違反ではありません。空文字列・null・負数のみを(C)の違反として報告してください。
(C) 空/0のeffect_id — 違反なし (
effect_id: 0は有効な約束値として処理。空/null/負数はなし。)
このたった1行の約束こそが、ドメイン知識の蓄積です。システム企画の決定は比較的恒久的なので、一度固定化すれば、1年後も同じチェックが同じ基準で回ります。キャビネットに決定カードが1枚積まれました。
このトランスクリプトで人がやったことは 3つだけ です。(1)スキーマから読めという入力順序の指定、(2)5099が本物のバグであることの確認、(3)0が有効値だと知っていて、AIの判断を拒否・矯正したこと。残りの、312行をセル1つずつジャンプしながら見ていた2時間は消えました。自動化されたのはジャンプと照合という労働であり、残った3行分の判断こそが核心です。
上のトランスクリプトのチェックを毎回手作業で依頼することもできますが、L3で繰り返される仕事はツールとして固めるのがシステム企画の定石です。著者が運用している2つを引用します。抽象的な「プロジェクトAのツール」ではなく、実際に机の上で回っているものです。
gen_relation_map.pyは、シートのカラム名と値を分析して外部キーの関係を自動検出し、インタラクティブなHTML関係マップを出力します。3.1.4でskill.effect_id → effect.effect_idという矢印を人が頭の中に描いたとすれば、このスクリプトはその矢印を、シート全体について図として描いてくれます。依存が逆行している場所(L3のデータがL1の骨格を逆参照する危険)が、図の上で即座に浮かび上がります。
schema-docスキルは、xlsmの $スキーマシート をパースして、Markdown(マークダウン)形式のスキーマドキュメントを自動生成します。3.1.4の(C)で「0が有効値かどうかスキーマでの確認が必要」という質問が出てきた、あのスキーマです。人がほかのファイルを漁らなくても、最新のスキーマをすぐ読めるようにします。シートが変わればドキュメントも追従して変わるため、ドキュメントと実データが食い違うという持病が減ります。
2つのツールの持ち場を座標で言い直すと、こうなります。schema-docはL3のカラム定義を守り、gen_relation_map.pyはL2〜L3のあいだの関係を守ります。AI補助プロンプト(3.1.4のような検証)は、その上で回ります。3つはバラバラに動くのではなく、同じ座標軸の異なる高さを受け持ちます。
これらのツールの実際の使い方は、3.2・3.3・3.4で手を動かしながらたどります。3.1は「どこに組み込むか」を決める地図であり、続く3つの章が、組み込む作業そのものです。
3つのツールを一度に稼働させると、運用の負担が効果より先に届きます。著者の経験では、安全な順序はリスクの小さい座標(下側)からです。
| 時期(推奨) | 導入 | 座標 | 失敗しても起きること |
|---|---|---|---|
| 1か月 | スキーマ優先(3.2) | L3 | ドキュメントが1回更新されないだけ |
| 2〜3か月 | 関係マップの可視化(3.3) | L2〜L3 | 図が不正確になるだけ |
| 3〜6か月 | AI補助プロンプト(3.4) | L1〜L3 | 検証を通すため、人が拒否できる |
期間は絶対的な基準ではありません。チームの規模や既存のインフラ次第で、倍かかることも、半分で終わることもあります(著者の推定、未検証)。変わらないのは順序です。リスクの大きい「決定の補助」を最後に置けば、先行する2つのツールでチームがすでに検証の習慣を身につけたあとに、最も敏感な場所へ触れることになります。
数値を美化せずに記します。以下は、著者がディレクターとして運営するMMORPGプロジェクト(以下「プロジェクトA」)の企画チーム(人数4〜5人、開発チーム全体は中規模の10〜50人、運用約6か月)で観察したものです。正確な自動計測ではなく、作業ログと振り返りの記録に基づく 著者の観察 であり、方向性とおおよその比率としてだけ読むことをおすすめします。
核心は、節約された時間が「ゲームを作らない時間」ではないという点です。その時間は、L1の骨格のような、AIに任せられない深い決定へと戻っていきます。労働を減らして判断に使うこと。それが本章のすすめる1行です。
setup. シートを2つ(例:スキル、効果)csvでexportしましょう。余裕があればJSONに変換しておきます(ドキュメントではなくexportの成果物が正本だという原則です)。2つのシートのあいだで外部キーを1組選びます(例:skill.effect_id → effect.effect_id)。
prompt. 3.1.4のプロンプト全文をそのまま使いましょう。核心の3行を抜かさないでください。(1)「スキーマ/構造から読むこと」、(2)「推測せず、ないものは『ない』と報告すること」、(3)「違反がなければ『違反なし』とだけ書くこと」の3つです。
verify. AIが挙げた違反リストを、人が1行ずつ確認します。本物のバグは直し、ドメインの約束値(例:0 = 효과 없음、つまり「0は効果なし」)のせいで生じた誤検知は拒否して、その約束をプロンプト(またはatom)に追加します。次のチェックから同じ誤検知が消えれば、資産が1枚積まれたということです。
チームもシートもない1人開発者なら、Googleスプレッドシートのタブ2つで十分です。1つのタブは「スキル」、もう1つのタブは「効果」。effect_idカラム1本で両者をつなぎましょう。タブをcsvでダウンロードして3.1.4のプロンプトに貼り付ければ、312行ではなく30行のシートでも、まったく同じようにdangling referenceと孤児効果が捕まります。規模が違うだけで、座標は同じです。L3から始めて、手になじんだら関係マップと影響範囲へ、1段ずつ上に登っていきましょう。
月曜の午前、新人プランナーが埋めたスキルシート120行をcsvへビルドしたところ、クライアントのログに赤い行が28個出ました。class_idが47番を参照しているのに、クラスシートには47番が存在しません。elementの欄には、ある人はFireと書き、別の人はfireと書き、さらに1行は화염(韓国語で「炎」)とハングルで書かれています。赤い行28個を1行ずつ手でたどるうちに、午後の半分が消えていきます。
この事故の原因は、データが間違っていたからではありません。データを作る前に、そのデータが従うべき規則を明示しなかったからです。規則が頭の中にしかなければ、人が替わった瞬間に規則も変わります。本章では、規則(スキーマ)をデータより先に作るワークフローを扱います。そして、その規則を人の手ではなくツールがドキュメントとして強制する形に変えていきます。
用語メモ - スキーマ(schema):マスターデータシートのカラム定義。名前・型・範囲・外部キー・説明。 -
$스키마:Excelのマスターデータ(xlsm)の中に置く、カラム定義専用のシート。データ行ではなく、カラムの規則だけを収めます。 - FK(外部キー):他のシートのPK(主キー)を参照するカラム。class_idがClassシートの行を指す、という形です。 - proto:Protocol Buffersの定義(.proto)。クライアントとサーバーが共有するデータ構造・Enumの契約です。 - 信頼できる唯一の情報源(single source of truth):同じ情報を1か所だけで管理し、全員がそこを参照する運用原則です。
スキーマファーストを「カラムを事前に定義すること」とだけ理解しているなら、半分しか捉えていません。核心は何を先に入力するかという順序にあります。データを埋める手がどの順序で動くかが、整合性が守られるか崩れるかを決めます。
本書が推奨する入力順序は、4つのマスからなるパイプラインです。
flowchart LR
A["$스키마シート
(カラム規則の定義)"] --> B["Enum / *.proto
(VBA Exportでコード契約を生成)"]
B --> C["csvデータ
(規則の中で行を埋める)"]
A -.->|schema-doc| D["スキーマドキュメント
(.md自動生成)"]
C -.->|gen_relation_map.py| E["FK関係図
(HTML自動生成)"]
D -.-> F(("AI / 人
同じ定義を読む"))
E -.-> F
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class A,B,C,D,E data
左から右へ流れる実線が強制される入力順序です。$스키마を先に定義し、そこからEnumとprotoをVBA(Excelのマクロ言語)のExportで生成し、その契約の中でだけcsvデータを埋めます。点線は、その入力から自動的に派生する成果物(スキーマドキュメントschema-docとFK関係図gen_relation_map.py)で、人とAIはこの派生物を通じて同じ定義を見ます。
この順序が強制されている限り、本章の冒頭で見た赤い行28個の大半は、データを埋める前に塞がれます。elementがfire/ice/lightning/noneの4つのうちの1つであるという事実がprotoのEnumとして固定されていれば、Fireも화염も入力段階で弾かれます。class_idがClassシートのPKを参照するという事実が$스키마に明示されていれば、47番の欠落はビルドではなくチェックの段階で先に捕まります。
順序を逆にすると、つまりデータを先に埋めてスキーマを後で整理すると、スキーマは事後の掃除になります。1000行がすでに積み上がった場所でカラム規則に手を入れると、規則がデータを追いかける形になり、その瞬間、情報源の主従が逆転します。
$스키마からcsvまで一気に言葉で説明する代わりに、実際に1つのシートを最初から最後まで通してみます。スキルシートを新しく作るとしましょう。以下は、AIの補助を入れて進めた全記録です。要約はせず、食い違った箇所と人が拒否した箇所をそのまま残します。
$스키마を先に手で書くツールもAIもまだ呼びません。カラム規則は人が直接定義します。このステップだけは委任しません。
# Skill シート $스키마 (人が作成)
カラム タイプ 範囲/制約 FK 説明
skill_id int 1000~9999 (PK) スキル固有ID
name string 1~30字 - 表示名
class_id int(FK) - Class.class_id 使用可能クラス
cooldown float 0.0~120.0 - 再使用待機(秒)
damage_coef float 0.0~10.0 - 攻撃力乗算係数
element enum fire/ice/lightning/none - 属性
is_passive bool true/false - パッシブかどうか
$스키마のelement enumとFK構造をprotoの契約に移す作業です。形式変換なので、AIに任せられる仕事です。
プロンプト(全文): 以下はゲームのマスターデータシートのカラム仕様である。この仕様をProtocol Buffers(proto3)のメッセージとEnumに変換せよ。規則:(1)enum型のカラムは独立したenumとして定義し、最初の値は必ず
_UNSPECIFIED = 0で始める。(2)FKカラムは参照先をコメントで残す。(3)範囲制約はprotoでは表現できないため、コメントとしてのみ残す。仕様にないカラムは追加しないこと。skill_id int PK / name string / class_id int FK→Class.class_id / cooldown float 0~120 / damage_coef float 0~10 / element enum{fire,ice,lightning,none} / is_passive bool
syntax = "proto3";
// Skill シートのデータ契約
// 注意: 範囲制約(cooldown 0~120, damage_coef 0~10)は
// protoでは強制不可。Linterで検査すること。
enum SkillElement {
SKILL_ELEMENT_UNSPECIFIED = 0;
SKILL_ELEMENT_FIRE = 1;
SKILL_ELEMENT_ICE = 2;
SKILL_ELEMENT_LIGHTNING = 3;
SKILL_ELEMENT_NONE = 4; // ← 人によるレビューの印
}
message Skill {
int32 skill_id = 1; // PK
string name = 2;
int32 class_id = 3; // FK → Class.class_id
float cooldown = 4;
float damage_coef = 5;
SkillElement element = 6;
bool is_passive = 7;
}
ここで人が1か所を拒否します。AIはnoneをSKILL_ELEMENT_NONE = 4としてenumの末尾に配置しましたが、$스키마においてnoneは「属性なし」という既定の状態です。争点はprotoの0番の値です。proto3ではenumの0番は「値が入力されなかったときに自動的に入る場所」なので、0番を_UNSPECIFIED(値未入力)として空けておくか、none(意図的な無属性)のような意味のある値で埋めるかで判断が分かれます。両者を1つの場所に統合すると、欄を空けたままにした行(ミス)と無属性をあえて選んだ行(意図)が同じ0として入ってきて、永遠に区別できなくなります。AIは_UNSPECIFIED = 0とnoneを別個に置いてはいたものの、noneを4番という末尾に送ったため、最もよく使う既定状態が検索・デバッグから遠ざかってしまいました。
人が下した決定は次のとおりです。
- _UNSPECIFIED = 0は維持します(欠落検出用)。
- noneはそのまま残しつつ、csvの作成規則に「無属性は必ずnoneを明示、空欄禁止」を追加します。空欄=0=UNSPECIFIEDは入力エラーと見なします。
- この決定を$스키마のelement行の説明に1行追加します:「無属性も明示(none)。空欄はエラー」。
AIの出力をそのまま使ったわけではありません。形式は受け取り、意味の境界線は人が引きました。
検証済みのproto定義は、ExcelのExportマクロ($스키마シートのボタン)が一貫して出力します。人がIDEで再度タイピングすることはありません。シートが唯一の情報源なので、protoはシートから生まれます。この順序は、3.3で扱う「出力が正本」(json_over_schema_doc_as_source_of_truth)原則の延長です。ドキュメントがコードを説明するのではなく、シートがコードを生むのです。
プロンプト(全文): 上のSkill protoと$스키마を満たすcsv行を5つ作れ。制約:class_idは[1,2,3]のいずれか(現在Classシートに存在するPK)、damage_coefはパッシブ(is_passive=true)なら0.0、elementはfire/ice/lightning/noneの文字列そのまま、空欄禁止。
Claudeの最初の出力では、1行が規則から外れていました。
skill_id,name,class_id,cooldown,damage_coef,element,is_passive
1001,화염베기,1,3.5,2.4,fire,false
1002,빙결의손,2,8.0,3.1,ice,false
1003,체력회복,1,0.0,1.2,none,true ← 拒否: パッシブなのにdamage_coef≠0
1004,번개창,3,5.0,2.8,lightning,false
1005,방어태세,2,0.0,0.0,none,true
1003行が規則違反(is_passive=trueならdamage_coef=0.0でなければならない)です。人が拒否して再リクエストしました。
再リクエスト(全文): 1003行が規則違反。is_passive=trueなのにdamage_coefが1.2だ。パッシブは0.0でなければならない。1003だけ直してもう一度出せ。
Claudeの再出力:
1003,체력회복,1,0.0,0.0,none,true
AIが最初の試行ですべてを正しく出せないのは、欠陥ではなく、ただ普通に起きることです。重要なのは、スキーマが敷かれていたおかげで、その外れた1行を目で特定し、1行の指摘で元に戻せたという点です。スキーマがなければ、1003はビルド後のゲーム内で「パッシブなのにダメージが出る」バグとして発見されていたはずです。
このトランスクリプト全体の教訓はシンプルです。入力順序が$스키마 → proto → csvに固定されていれば、AIは形式を素早く埋め、人は意味と違反だけをレビューします。順序が崩れると、人が形式から意味まで全部を抱え込むことになります。
$스키마をExcelの中に置くとプランナーにとっては快適ですが、AIやgit、外部ツールにとっては閉ざされた場所です。そこで、$스키마をMarkdown(マークダウン)に自動変換するツールを運用しています。スラッシュスキルschema-docがこの仕事を担います。
動作は4ステップです。
$스키마シートをパースする(python-calamine、Rustによる高速化)<시트명>_schema.md(「シート名_schema.md」の意)を生成する核心は、人がスキーマを二度書かないことです。Excelで一度定義すれば、Markdownはツールが作ります。両者が食い違うことはあり得ません。3.3で扱う「スキーマドキュメントを正本に据えると実際の出力とずれる」という落とし穴を、ここでは「Excelが正本、ドキュメントは派生」とひっくり返すことで回避します。
schema-docが生成した結果(先ほどのトランスクリプトのSkillシート基準)です。
# Skill シート スキーマ (自動生成 — 直接修正禁止)
| カラム | タイプ | 範囲/制約 | FK | 説明 |
|---|---|---|---|---|
| skill_id | int | 1000~9999 | (PK) | スキル固有ID |
| name | string | 1~30字 | - | 表示名 |
| class_id | int(FK) | - | Class.class_id | 使用可能クラス |
| cooldown | float | 0.0~120.0 | - | 再使用待機(秒) |
| damage_coef | float | 0.0~10.0 | - | 攻撃力乗算係数 |
| element | enum | fire/ice/lightning/none | - | 属性。無属性も明示(none)、空欄はエラー |
| is_passive | bool | true/false | - | パッシブかどうか。trueならdamage_coef=0 |
_source: Skill.xlsm / generated by schema-doc_
elementとis_passiveの説明欄に、3.2.2のステップ4・6で人が引いた境界線がそのまま入り込んでいる点に注目してください。人が$스키마に1行書いただけで、ドキュメント・proto・検証のすべてが同じ規則を共有するようになりました。これが、信頼できる唯一の情報源が実際に機能している姿です。
Markdownに落ちたスキーマは、3つの場所でただちに使われます。
スキーマがシートの内側の規則だとすれば、FKはシート間の規則です。class_idがClassシートを参照するという定義は$스키마に書かれていますが、その参照が今この瞬間に実際に生きているかどうかは、別途のチェックが必要です。
gen_relation_map.pyは、各マスターデータシートのFK関係を自動検出し、インタラクティブなHTML関係図として描きます。Skillのclass_id→Class、Itemのset_id→ItemSetのような矢印が1つの画面に集まると、「参照先が消えたFK」が切れた矢印として目に留まります。本章冒頭の47番欠落のような事故が、ビルドログの赤い行ではなく関係図の切れた線として、データを埋めている最中に見えるのです。
このツールの実際の使用例と可視化は3.3で本格的に扱います。本章で覚えておくべきことは1つです。$스키마がFKを明示しなければ、関係図にも整合性チェックにも、描くべきグラフがありません。FKの明示は選択肢ではなく、スキーマファーストの前提です。
3.2.2のトランスクリプトを一般化すると、5つのステップになります。各ステップの主体と成果物を分けて見ると、何を人が握り、何をツールに渡すのかが明確になります。
5つのステップを最初の月にすべてそろえる必要はありません。ステップ1・2(スキーマ設計+自動ドキュメント化)を回すだけでも、価値の半分はつかめます。ステップ3〜5は、運用に慣れてから段階的に追加します。最初から5ステップを強制すると、書き手の負担が定着前に運用を止めてしまいます。
著者がディレクターとして運営しているあるMMORPGプロジェクト(以下「プロジェクトA」)で、約6か月間このワークフローを回しました。以下の数値のうち、マスターデータのカラム一貫性と新規シートのドラフト時間はツールのログと作業記録から集計した実測値で、FK破損の頻度はビルド失敗のイシューから逆算した著者の推定(未検証)です。
| 項目 | 導入前 | 導入後 | 根拠 |
|---|---|---|---|
| カラム名の一貫性 | 約60% | 約95% | schema-doc照合の実測 |
| FK破損の頻度 | 週2〜3件 | 月1件以下 | ビルドイシューからの逆算(著者の推定) |
| 新規シートのドラフト時間 | 4〜8時間 | 1〜2時間 | 作業記録の実測 |
| 新人プランナーのシート理解 | 会議3回 | ドキュメント1回+会議1回 | オンボーディング事例(方向性のみ) |
導入コストは、ツールの初期開発約3日+運用定着約1か月です。6か月の累積効果に対して導入コストは小さかった、というのが運用上の結論です。ただし、上記の比率は1チーム・1プロジェクトの単一事例なので、他のチームにそのまま持ち込める保証はありません。
スキーマが敷かれると、AIによるデータ生成の信頼度は飛躍的に上がります。理由は、ハルシネーションの口実になる曖昧な入力範囲を、スキーマがあらかじめ塞いでくれるからです。「スキルを20個作って」というリクエストにスキーマがなければ、AIはもっともらしいカラムを発明し、手元のシートと互換性のない値を埋めます。スキーマがあれば、同じリクエストが、定義された7つのカラム・各制約・FKを守った行として返ってきます。3.2.2の1003行の事例のように違反が出ても、1行を指摘して再リクエストすれば終わりです。
その代わり、境界は明確です。バランスの値はAIに任せません。damage_coefをAIが「適当に」決めると、ゲームの意図と衝突します。形式の正しい候補を素早く並べるところまでがAIの仕事で、「このスキルの係数は2.4で正しいのか」には人が答えます。だからといって、AIがバランスに無用だという意味ではありません。曲線の滑らかさ・外れ値・範囲の統計は、AIが素早く捉えます。数字を測るのはツールに任せ、その数字が正しいかどうかは人が見極めます。
| 失敗 | 回避策 |
|---|---|
| 1000行積み上げた後にスキーマを導入する | 新規シートは必ず$스키마を先に作る |
$스키마とcsvの同期が崩れる |
schema-docの自動化で両者を1つの情報源に束ねる |
| FKを明示しない | FKを明示しなければ関係図も整合性チェックも無意味になる |
| proto Enumの0番を意味のある値に使う | 0は_UNSPECIFIED(欠落検出用)、意味のある値は1から |
| スキーマドキュメントを人だけが読む | Markdownの表とメタ情報の統一でAIにも読ませる |
setup
1. ご自身の担当分野で最も中心となるシートを1つ選んでください(スキル・アイテム・モンスターのいずれか)。
2. そのExcelファイルに$스키마という名前のシートを追加し、カラムごとに5要素(名前・型・範囲・FK・説明)を1行ずつ書いてください。このステップは人が直接行います。
prompt(proto/csvのドラフトにだけAIを使います)
以下の$스키마をproto3のメッセージとEnumに変換せよ。enumの最初の値は
_UNSPECIFIED = 0。FKは参照先をコメントで。範囲制約はコメントのみ。仕様にないカラムの追加は禁止。 (ここにご自身の$스키마を貼り付け)
続けて:
上のprotoと$스키마を満たすcsv行を5つ。制約違反の行は作るな。is_passive=trueならdamage_coef=0。
verify
1. AIが出した5行を1行ずつスキーマと照合してください。違反行があれば「N行目が違反、その行だけ直して」と再リクエストします(拒否と再リクエストは正常なプロセスです)。
2. schema-doc(または同等の簡単なPythonスクリプト)で$스키마を.mdに書き出し、Excelの定義とドキュメントが一致しているか確認してください。
3. FKがあれば、参照先のPKが実際に存在するかを一度照合してください。
ツールもチームもなく一人で始めるなら、Excelファイル1つとテキストエディター1つで十分です。
$스키마を作り、カラム規則を5要素で書きます(15分)。$스키마のテキストをメモ帳でskill_schema.mdとして保存しておきます。これがあなたにとって最初の、信頼できる唯一の情報源です。次のシートに進むときも、同じ4ステップを繰り返してください。四半期のうちに中心となるシート5〜10個が同じ順序でそろったら、そのときはじめてschema-docのような自動化を付ける価値が生まれます。
$스키마→Enum/proto→csvに強制すれば、違反はデータを埋める前に塞がれる新人プランナーが入社初週に私の席へやって来ました。「クエスト報酬テーブルに手を入れようと思うのですが、ここを触るとどこが壊れますか?」私はモニターを指して答えようとして、止まりました。頭の中には絵がありました。RewardTableがItemTableを参照し、ItemTableがItemEffectTableを参照し、その上でQuestTableが報酬を参照して…ところが、その絵を言葉に置き換えた瞬間、聞き手の頭の中では形が崩れていきました。ホワイトボードにボックスを7つ描きました。矢印が絡まり始めました。30分後、彼はうなずいて席に戻り、翌日まったく同じ質問を再び持ってきました。
この場面が、本章を書かせました。システムプランナーの頭の中には依存関係のグラフがあります。問題は、それが頭の中にしかないことです。人が変われば絵も消えます。絵を外部化するツールが必要で、そうして作ったのがgen_relation_map.pyです。
マスターデータが5〜10個のうちは頭の中だけで足ります。30個を超えると、人のワーキングメモリーでは抱えきれません。1つのプロジェクトのシートフォルダーは、たいていその線を早々に超えます。どこからどこへ依存しているかを文章で書き並べた表は、読んでも絵が浮かびません。本章では、外部キーの関係をインタラクティブHTMLの関係図として自動生成する過程を、ワークド・トランスクリプトとして最初から最後までたどります。
ツールを作る前に、関係図がないときに実際に何が詰まるのかから押さえます。繰り返し起きたのは4つの場面でした。
新人プランナーのオンボーディング。 新しいプランナーがシステム構造を覚えるために会議を設定します。冒頭のあの場面です。言葉で伝えられた依存関係は、聞き手の頭の中で数日と持ちません。関係図1枚を一緒にクリックすれば、最初の会議で半分以上の絵が描けます。ホワイトボードの手描きと決定的に違うのは、図が消えずにその場に残ることです。
変更の影響範囲の議論。 システム変更のリクエストが上がってきます。「これ、どこに影響しますか?」会議が設定され、長々と議論しても、漏れた領域が1つ2つ出てきます。関係図があれば、変更対象のノードをクリックしてインバウンドエッジをたどるだけで、影響範囲が目に入ります。議論は「この影響が本当に正しいか」と優先順位を決めるだけで済みます。
依存の逆行の検出。 L3のマスターデータがL1のシステム文書を参照するのは正常です。逆方向(上位Layerが下位のマスターデータを直接参照する)は、ほぼ例外なく設計上の欠陥です。文章として並べたFKの一覧では、この逆行を人は捕まえられません。図であれば、Layerの色が食い違う矢印1本として即座に現れます。
孤立したシートの発見。 どこからも参照されないシートが時々見つかります。古い企画の名残か、廃棄すると決めたのにファイルだけが残ったケースです。オフィスの片隅にラベルのない箱が転がっている風景と同じです。図があってこそ、その孤島を発見できます。
4つの問題に共通するのは、どれも「構造を目で見なければ解けない」という点です。文章と表では行き詰まります。
ここからは実際にたどります。入力はマスターデータが入ったフォルダー1つ、出力はブラウザーで開く1枚のインタラクティブHTMLです。その間でAIがやったことと、人が検証・却下したポイントを漏れなく記します。
flowchart TD
A[マスターデータのフォルダー
xlsm/xlsxが多数] --> B[1. スキャン:シート・カラムヘッダー収集]
B --> C[2. FK候補の抽出
*_id / *Id / 仕様書のFK表記]
C --> D[3. 参照先のマッチング
カラム名 → 対象シート]
D --> E{人による検証}
E -->|誤検出を却下| C
E -->|通過| F[4. グラフ構築
ノード=シート、エッジ=FK]
F --> G[5. Layerメタ付与
schema-doc出力を参照]
G --> H[6. pyvisでHTMLレンダリング]
H --> I[relation_map.html
ブラウザーでインタラクティブ]
I --> J{人による診断}
J -->|逆行・孤立・循環を発見| K[設計修正を依頼]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class A,I data
class B,C,D,F,G,H code
class E,J,K human
肝心なのは、3番と5番の間にある人による検証ループです。FK候補の抽出は機械がたたき台を敷き、人がそこから誤検出を間引きます。このループを省くと、関係図はもっともらしく見えて間違った絵になります。
このツールの精度は、入力をどこから引いてくるかで決まります。3.2で定めたschema-first原則がそのまま適用されます。FK情報を「正」とする優先順位は次のとおりです。
$스키마シート — 各マスターデータの第一の「正」です。カラムごとに型・Enum・FKの参照先が明記されています。ここにFKが書かれていれば、それが第1順位です。*.proto / Enum定義 — VBA(Excelマクロ言語)のExportで吐き出されたスキーマです。仕様書が空のとき、型を補強します。csv出力 — シートが書き出した実データです。仕様書にない関係も、データのパターンから現れます(例:npc_idカラムの値がすべてNPCTableのキー範囲内に収まっていれば、事実上FKです)。ここで1つの原則をはっきりさせておきます。スキーマ文書ではなく、実際のJSON/csv出力が「正」です。 仕様書にreward_idがFKだと書かれていても、実データでそのカラムが空だったり、見当違いの値を指していたりすれば、間違っているのは仕様書のほうです。ツールは両者が食い違うときデータ側を信頼し、食い違いそのものをレポートに残します。これが、schema-docを「正」に据えない理由です。
ツールの最初の動作は、フォルダー内のすべてのxlsm/xlsxを開いてシート・カラムヘッダーを収集することです。このステップ自体は機械的で、面白みはありません。面白いのは、FK候補を拾うルールです。最初は単純でした。カラム名が*_idまたは*Idで終わればFK候補と見なす。ところが、このルール1つでは誤検出と漏れの両方が出ました。
ここでAIをつなぎました。カラムヘッダーの一覧を丸ごと渡し、FKの参照先シートを推論させました。以下は、実際に投げたプロンプトの全文と生の出力です。
プロンプト: 以下はゲームのマスターデータのシート名とカラムヘッダーの一覧である。各カラムが外部キー(FK)かどうか、FKの場合はどのシートのどのカラムを参照するかを推論せよ。カラム名の規則は通常
<대상시트단수형>_idである。ただし、確信がなければ「不確実」と表示し、理由を書くこと。推測で埋めないこと。QuestTable: quest_id, title, npc_id, reward_id, prev_quest_id, region_id NPCTable: npc_id, name, region_id, faction_id RewardTable: reward_id, item_id, count, currency_id ItemTable: item_id, name, item_effect_id, grade ItemEffectTable: item_effect_id, effect_type, value RegionTable: region_id, name, parent_region_id
Claudeの生の出力(PK行は省略、FK判断のみ抜粋):
QuestTable -
npc_id→ NPCTable.npc_id(クエストを与えるNPCと推定) -reward_id→ RewardTable.reward_id -prev_quest_id→ QuestTable.quest_id(先行クエスト、自己参照) -region_id→ RegionTable.region_idNPCTable -
region_id→ RegionTable.region_id -faction_id→ 不確実:FactionTableが入力一覧にありません。カラム名の規則上はFactionTable.faction_idを参照すると見られますが、該当シートが提供されていないため参照先を確定できません。RewardTable -
item_id→ ItemTable.item_id -currency_id→ 不確実:CurrencyTableが一覧にありません。通貨の種類がEnumで管理されている可能性もあり、FKではないかもしれません。仕様書の確認が必要です。ItemTable -
item_effect_id→ ItemEffectTable.item_effect_id -grade→ FKではない(Enumと推定:等級の値)ItemEffectTable -
effect_type→ FKではない(Enumと推定)RegionTable -
parent_region_id→ RegionTable.region_id(自己参照、地域の階層構造)注意:上記の推論はカラム名の規則に基づいたものであり、実際のデータ値は確認していません。特に
countやvalueのように_idで終わらないカラムはFK検査の対象から除外しており、隠れた参照があれば漏れます。
この出力に対して人がやったことは3つです。
faction_id → AIはFactionTableが存在しないとして不確実扱いにしました。正しい判断です。ただし、実際にはfaction_idはEnumでした。ファクションが6種で固定のため、別シートを作らずEnumで管理しています。AIが存在しないシートをでっち上げずに判断を止めてくれたおかげで、人が仕様書を見てEnumと確定できました。FKから除外。currency_id → AIは両方の可能性を開けたままにしました。実データを見るとCurrencyTableは存在していました(入力一覧から私が漏らしていました)。FKとして確定。 AIを責められない、人間側の入力漏れでした。prev_quest_idとparent_region_idの自己参照の検出。これは単純な正規表現ルールだったら見逃していたはずです。AIが「先行クエスト」「地域の階層」という意味付けまで添えてくれたことが、検証を速くしました。ここで得た教訓は明確です。AIがもっとも役に立った場面は、速い推論ではなく、わからない箇所を「不確実」のまま空けておいた抑制でした。空欄を無理に埋めていたら、faction_idは見当違いのシートにつながり、その誤検出は関係図に偽の矢印として残って、新人プランナーを誤った方向へ導いていたはずです。
検証済みのFK一覧ができると、gen_relation_map.pyがグラフを作ります。シートはノード、FKは有向エッジです。インバウンドエッジ数(ほかのシートが自分をどれだけ参照しているか)を数えてノードの大きさを決めます。多く参照されるほどノードは大きく、すなわちシステムのハブです。
Layerメタデータは、schema-docスキルが作ったMarkdownのスキーマ文書から引いてきます。3.1で定義したLayer座標(L0〜L4)が各シートにラベルとして付いており、ツールはそれを読んでノードの色を塗ります。この連結が重要です。関係図がLayerを知らなければただのボックスと矢印にすぎず、Layerを知ってはじめて「逆行」を色で診断できます。
ツールの内部構造をコードの骨格で示すと、次のとおりです(中核の流れのみ抜粋)。
# gen_relation_map.py (中核の流れの抜粋)
from pyvis.network import Network
LAYER_COLORS = { # Layer パレット — atom 1個に標準化
"L0": "#2c3e50", # メタ/共用
"L1": "#2980b9", # システム
"L2": "#27ae60", # コンテンツ
"L3": "#f39c12", # データインスタンス
"L4": "#c0392b", # 派生/キャッシュ
}
def build_graph(fk_list, layer_map):
net = Network(directed=True, height="900px")
inbound = count_inbound(fk_list) # インバウンドエッジ集計
for sheet in all_sheets(fk_list):
layer = layer_map.get(sheet, "L0")
size = 10 + inbound[sheet] * 3 # ハブほど大きいノード
net.add_node(sheet, color=LAYER_COLORS[layer],
size=size, title=sheet_tooltip(sheet))
for src, dst, col in fk_list:
# Layer 逆行検知: 上位Layerが下位を参照したら警告色
edge_color = "#e74c3c" if is_reverse(src, dst, layer_map) else "#888"
net.add_edge(src, dst, title=col, color=edge_color)
return net
is_reverseが、このツールの小さな核心です。エッジの出発シートが到着シートより上位のLayerなら(例:L1 → L3)逆行と見なし、エッジを赤く塗ります。人が図を開いたとき赤い矢印が見えたら、それはほぼ例外なく手を入れるべき場所です。
最後は、pyvisがインタラクティブHTMLを吐き出すステップです。ノードをクリックすると該当シートのカラム・Layer・インバウンド数がツールチップで表示され、検索ボックスでシート名によるフィルタリングができます。静的PNGではなくHTMLでなければならない理由がここにあります — ノード数が数十を超えると、静的画像では矢印が絡まって何も見えません。マウスでドラッグして広げ、関心のある領域だけをクリックで絞り込んではじめて、パターンがつかめます。
上の例のデータで作った関係図の構造をSVGに写すと、次のとおりです。色はLayerを、赤い矢印は(この例にはありませんが)逆行の位置を意味します。
ノードの大きさを見ると、RegionTableがもっとも多く参照されています(Quest・NPCの両方が指しています)。これがハブです。ItemEffectTableは葉のノードなので小さく表示されます。新人プランナーの「このシステムを理解するにはどこから見ればいいか」という質問の答えは、ノードの大きさの順序として、図の中にすでに入っています。
3.1でLayer座標を定義しました。本章の関係図がその座標を視覚へ引き上げると、文章や表では不可能だった4つの診断が、1つの画面で可能になります。
ただし、図がすべての問題を捕まえるという意味ではありません。図が捕まえるのは構造上の欠陥です。このFKが本当に意味として正しい関係なのか(例:npc_idが本当に「クエストを与えるNPC」なのか、それとも「クエストに登場するNPC」なのか)は、図では解けません。それは人のドメイン判断の領分です。ツールは、人の判断が働く舞台を整えてくれるだけです。
関係図は、一度作って終わりではありません。シートは毎週追加・変更されます。手動更新に任せた関係図は1〜2か月で実際の構造とずれ、ずれた地図は道を誤って案内するため、かえってないほうがましな状態になります。一度間違った図に痛い目を見たチームメンバーは、次から図を見ません — これがもっとも高くつく失敗です。
そこで、更新を自動トリガーに掛けます。
/relation-mapスラッシュコマンドで即時生成します。会議中にその場で表示するときに使います。生成されたHTMLは、社内の静的ホスティング(企画ポータル)へ自動デプロイします。別途ツールのインストールなしに、ブラウザーさえあれば誰もが同じ地図を見られます。机の横に常に広げてある地図と同じです。誰に聞かれても、同じ図を一緒に指さしながら答えます。
| 失敗 | なぜ起きるか | 回避策 |
|---|---|---|
| ノードが100個を超えて図が絡まる | 全分野を1画面に詰め込む | ドメイン別フィルタリング、グループ別の分割ビュー |
| Layerの色がツールごとに異なる | パレットをコードごとに新しく定義 | パレットを1個のatomに標準化(LAYER_COLORS) |
FK検出が*_idだけを拾い、漏れ・誤検出が出る |
正規表現1行に依存 | 仕様書でのFK明記+実データの値検証を併用 |
| 図は作ったのに誰も見ない | ワークフローに接続していない | 変更リクエスト・会議への図の添付を強制 |
| 作ったまま更新せず陳腐化 | 手動更新に依存 | 自動トリガー必須、手動では1か月で無用化 |
gen_relation_map.pyの運用でもっとも頻繁に痛い目を見たのは、3行目です。*_idルールだけを信じると、countやvalueのような隠れた参照を見逃し(3.3.2.3のAIもこの限界を自ら警告していました)、EnumであるgradeをFKと誤検出します。仕様書と実データの両方を見る検証ループが、この行の答えです。
会社全体のマスターデータを一度に扱おうとすると重く、価値を見せる前に疲れてしまいます。自分の担当分野のフォルダー1つから小さく始めます。
setup.
1. 自分が担当するマスターデータが5〜10個入ったフォルダーを1つ選んでみましょう。
2. pip install pyvis openpyxlで依存パッケージを入れましょう(Excelの読み取りはexcel-readerスキルまたはopenpyxl)。
3. 各シートの$스키마シートにFKが明記されているか、まず確認しましょう。なければカラムヘッダーだけを集めます。
prompt. カラムヘッダーの一覧を集め、3.3.2.3のプロンプトをそのまま投げてみましょう。肝心なのは最後の1行です — 「確信がなければ不確実と表示し、推測で埋めないこと」。この一文が偽の矢印を防ぎます。
verify.
1. AIが出したFK候補を1行ずつ見ましょう。「不確実」と表示された行を、仕様書・実データで確定します。
2. Enumと疑われるカラム(gradeやeffect_typeのように_idが付かないのにFKに見えるもの)はFKから外しましょう。
3. 自己参照(prev_*_id、parent_*_id)が正しく拾えているか確認しましょう。
4. 検証済みの一覧でグラフを描き、ブラウザーで開いて、赤い矢印(逆行)と孤島(孤立)を目で探してみましょう。
ツールを作る時間がなければ、最初の週は手で描いたmermaid 1枚から始めても構いません。シート5個のFKを3.3.2.3の形式でmermaidに直接書いてみましょう。この1枚を会議に持っていき、「これがうちのシステムの依存関係です」と見せれば、その場で価値が証明されます。価値が見えれば、自動化ツールは自然と後から付いてきます。最初から動くツールを出さなければ、という負担は下ろしてしまって大丈夫です。
拡張は次の順序で自然に流れていきます — 1週目は自分のシートのmermaid手描き → 2週目はLayerの色とクリックの追加 → 1か月で自動更新(gitフックまたは夜間バッチ)→ 3か月で社内ポータルへの配備 → 6か月で全シートの統合関係図。
3.2ではシートの内側(スキーマ)を、3.3ではシートの外側(関係)を扱いました。3.4は、この上にAI補助のプロンプトパターンを載せます。スキーマと関係が固まったシステムの上で、AIが整合性チェックと影響範囲の抽出をどう補助するのか、実用パターンへと続きます。
アルファビルドを目前に控えた週のことでした。スキルのマスターデータ(シート)に新しいクラスを1行追加して保存しました。ところが、そのクラスが参照するバフIDが前日に誰かが削除した行だったことを、私は翌朝ビルドが壊れてから初めて知りました。壊れたビルドをさかのぼって原因を突き止めるのに2時間かかりました。行を1つ削除する前に「これ、消しても大丈夫?」と聞けてさえいれば、使わずに済んだ2時間です。
この章は、その質問をAIにさせる方法についてのものです。核心は、プロンプトを上手に書くコツではありません。同じ質問を毎回ゼロから書き直さずに済むよう、ルール化することです。3.2でスキーマを、3.3で関係図を敷きました。どちらもデータの骨組みでした。この章では、その骨組みの上で人間がAIに投げる質問そのものを資産として固めます。
まず1つ、くぎを刺しておきます。AIが作るのは答えではなく候補です。この章に出てくるすべてのパターンで、最後の決定の手は最後まで人間の側に残ります。
AIを使い始めたばかりの頃は、毎回自然言語でその場で打ち込みます。こんな具合です。
スキルシートをちょっと見て。外部キーが壊れてないか確認して、
変なのがあったら教えて。あ、あとクールダウンがマイナスのも。
このプロンプトは2か所で漏れます。
第一に、検査項目が毎回変わります。今日は「クールタイム(クールダウン)がマイナス」を思い出しても、明日は忘れます。昨日は一度検出した「PKの重複(Primary Key、主キー)」が、今日のプロンプトからは抜け落ちます。人間の記憶に頼る検査は、人間のコンディションの分だけ漏れます。
第二に、結果の形式が毎回変わります。同じ意図を「確認して」「チェックして」「ざっと見て」と違う言い方で書けば、AIはある日は表で、ある日は文章で答えます。形式がばらばらだと、その結果を再び自動処理することができません。
解決策は、プロンプトを手から離して引き出しにしまうことです。毎回手で書いていたメモを、ラベル付きのカードに変えて同じ引き出しから取り出します。そのカードが、本書で言うスラッシュコマンド(skill)でありatomです。
ルール化には3つの器があります。何をどこに収めるかは、呼び出し頻度と安定性で分かれます。
よく使い、定義が固まった作業はスラッシュコマンドへ。よく使うものの「忘れてはならない制約」はatomのJITで自動注入へ。たまにしか行わないものの規模が大きい作業はテンプレートファイルへ。そして、まだ定義が揺れている作業はルール化せず、即興のままにしておきます。3つを最初からすべてそろえる必要はありません。スラッシュコマンド1〜2個から始め、価値が見えてきたら増やします。
言葉で説明する代わりに、1つのパターンを最初から最後までたどってみます。空の行を1つ削除する前に「これ、消しても大丈夫?」を自動で問うパターンです。名前は/check-sheet。中には検査項目と出力形式がルール化されて入っています。
根拠となる資産は、本書の随所に埋め込まれた実測の作業記録にあります。データ入力はschema-first原則(atom data_entry_schema_first)に従います。入力順序は$스키마シート → Enum/*.proto(VBA(Excelのマクロ言語)でのExport) → csv。そして正本はスキーマ文書ではなく、実際のJSON出力です(atom json_over_schema_doc_as_source_of_truth)。整合性チェックは、この2つの原則をそのまま検査ルールへ移したものです。
/check-sheetを開くと、中にはこんなプロンプト本文が入っています。これが、毎回手で打たなくて済む部分です。
役割: あなたはゲームデータシートの整合性検査器だ。
検査するシート: {{sheet_name}}
参照可能なスキーマ: $스키마 シート (カラム別タイプ・範囲・FK対象)
参照可能な正本: 同じシートのexport JSON (スキーマ文書より優先)
検査項目 (この順序で):
1. 外部キー有効性 — FKカラムの値が参照対象シートに実在するか
2. 範囲違反 — $스키마に定義されたmin/maxを外れた値
3. 必須カラム欠落 — required=trueのカラムが空の行
4. 重複PK — 同じPKが2つ以上
出力形式 (必ずこの表のまま):
| # | 行 | カラム | 違反種類 | 現在値 | 修正候補 | 確信度 |
ルール:
- 修正候補は提案だけにする。絶対にシートを直接修正しない。
- 確信度は高/中/低の3つのうち1つ。推測なら低。
- 違反がなければ "違反0件" だけ出力して終える。
この本文で重要なのは、最後のルール3行です。「直接修正しない」「確信度を付ける」「推測なら低と自白する」。この3行が、AIを答えを出す席ではなく、候補を差し出す席に縛り付けます。
/check-sheet skill_table
これで終わりです。渡すのはシート名1つだけ。検査項目4つも、出力の表形式も書き直しません。それがルール化の価値です。
以下は、実際に実行したときに返ってきた出力です。要約はせず、曖昧だった部分やAIがためらった箇所まで、そのまま掲載します。
skill_table 整合性検査の結果です。計4件の違反を発見しました。
| # | 行 | カラム | 違反種類 | 現在値 | 修正候補 | 確信度 |
|---|-----|------------|------------|--------|----------------------|------|
| 1 | 142 | buff_id | 外部キー違反 | 9007 | 9070 (誤記推定、1文字) | 中 |
| 2 | 142 | buff_id | 外部キー違反 | 9007 | 9012 (名前"맹독"が類似) | 低 |
| 3 | 88 | cooldown | 範囲違反 | -1 | 0 (負数不可) | 高 |
| 4 | 215 | combo_next | 必須欠落 | (空欄) | 判断保留 | 低 |
注意事項をいくつか付け加えます。
- 1番と2番は同じ行(142)の同じカラムに対する互いに異なる候補です。
9007というIDはbuffシートに存在しません。9070は数字1桁の違い
なので誤記の可能性があり、9012は名前が"맹독"で類似しているため
候補に挙げました。どちらも確信は低いです。人間が選ぶべきです。
- 4番(215行 combo_next 空欄)は曖昧です。$스키마にはrequired=trueと
なっていますが、export JSONを見るとこのカラムが空の行がさらに12個
あります。仕様書が最新でないか、「コンボ締めのスキルはcombo_nextを
空にする」というルールがあるのに仕様書へ未反映の可能性があります。
私には断定できないため「判断保留」としました。
- cooldown以外に範囲検査をさらに回したい場合は$스키마の他のカラムの
min/maxを確認する必要がありますが、受け取った情報のrange_checkカラムが
一部空のため、cast_timeカラムは検査できませんでした。
この出力が良い出力である理由は、きれいだからではなく、正直だからです。4番を「必須なのだから埋めろ」と断定せず、「仕様書が最新ではないかもしれない」と自己疑念をさらけ出しました。cast_timeを見られなかったという限界も自白しました。もしAIが4番を自信満々に「0で埋めてください」と言っていたら、それはコンボの締めスキルを壊す誤答だったかもしれません。
ここからは人間が1行ずつ判定します。検証が自動で同伴するというのは、この表が自動で出てくるという意味であって、人間が見ないという意味ではありません。
最後の4番が核心です。AIの出力をそのまま信じていたら正常なデータを壊していたはずの場所で、人間の拒否が機能しました。同時に4番は、検査ルール自体の欠陥もあらわにしました。仕様書のrequired=trueが間違っていたのです。
ここで終わりにはしません。拒否は、次の検査をより賢くする入力です。人間が続けて打ち込みます。
4番は違反じゃない。combo_finisher=trueのスキルはcombo_nextが空なのが
正常なんだ。この例外ルールを検査項目に入れて、今後はfinisher行の
combo_next空欄は違反として検出しないで。それと仕様書のrequired定義が
間違っていると見える項目は、別に "仕様書疑い" セクションへ分けて教えて。
理解しました。/check-sheetの検査ルールに例外を反映します。
追加されたルール:
- combo_next必須検査の際、同じ行のcombo_finisher値を先に確認。
combo_finisher=trueならcombo_next空欄は正常として処理(違反除外)。
新設セクション:
- "仕様書疑い" — export JSONの実際の分布が$스키마定義と食い違うとき、
データではなく仕様書を直すべき候補として分離して報告。
(正本はJSONという原則に従い、多数のデータが仕様書に反すれば
仕様書側を疑います。)
この変更を/check-sheetコマンド定義atomに記録しておきましょうか?
最後の1行が、このパターンの完成です。一度の拒否が一回限りの修正で終わらず、コマンド定義のatomに記録され、次の人・次の検査へと伝播していきます。これがルール化の複利です。同じ間違いを二度ふるいにかけることはありません。
2つ目のパターンは、冒頭の場面のあの2時間をなくすものです。行1つ、決定1つを変える前に、「これはどこまで波及するのか」を先に問います。名前は/impact。3.3で作った関係図(gen_relation_map.pyが出力するFK関係のHTML)が、このパターンの燃料です。
/impactの中には、「どこを探せば影響を漏れなく見つけられるか」が4つの経路としてルール化されています。
役割: あなたは変更影響範囲の抽出器だ。
変更対象: {{target}} (シート名・カラム・atom_id・システム決定のいずれか)
探索経路 (4つすべて回ること):
1. 関係図インバウンド — gen_relation_map.py 出力で {{target}} を
FKで参照するシート群 (このシートを指す矢印)
2. 本文言及 — sources/manuscript 内で {{target}} のID・名前が
直接登場する文書
3. atom affects 関係 — {{target}} と affects で連結された atom 群
4. 逆参照 — {{target}} を [[wikilink]] で指すノート
出力形式:
| 種類 | 名前 | 連結根拠 | 影響強度 | 担当 |
ルール:
- 影響強度は直接/間接/弱の3つだ。直接FKは '直接'、本文言及は '間接'、推定は '弱'。
- 欠落が怖ければ弱としてでも載せる。漏らすよりましだ。
- 優先順位や作業指示はしない。リストだけ作る。
/impact buff_id=9012
「猛毒バフ(9012)を別の効果に差し替えたいが、どこが揺れるのか?」を1行で問います。
buff_id=9012(맹독)の変更影響範囲です。4つの経路をすべて探索しました。
| 種類 | 名前 | 連結根拠 | 影響強度 | 担当 |
|--------|--------------------|----------------------|---------|----------|
| シート | skill_table | buff_id FK 直接参照 | 直接 | teammate_a |
| シート | monster_drop | reward_buff FK 参照 | 直接 | teammate_b |
| シート | set_item_effect | proc_buff FK 参照 | 直接 | teammate_a |
| 文書 | 독계열_밸런스_노트.md | 本文に "9012" を直接言及 | 間接 | (著者) |
| atom | poison_stack_rule | affects: 9012 | 間接 | (著者) |
| ノート | 상태이상_설계.md | [[맹독]] 逆参照 | 弱 | (著者) |
いくつか付け加えます。
- skill_tableで9012を参照する行は3つです(アサシンクラスのスキル)。
差し替えるとこの3つのスキルの実際の効果が変わります。最も強い影響です。
- monster_dropのreward_buffは、私は確信がやや弱いです。カラム名は
reward_buffですが、一部の行はbuffではなくitemを指しているような
値が混ざっているためです。このシートは直接一度開いて確認するようお勧めします。
- "독계열_밸런스_노트.md" は9012をテキストで言及するだけでFKではないため
間接としました。ただしバランス意図が書かれた文書なので、実際の影響は
強い可能性があります。強度(間接)と重要度が異なりうる点にご留意ください。
- set_item_effectはセットアイテム4種でproc_buffとして9012を使います。
맹독を抜くとこれらのセットのコンセプトが変わります。
ここでもAIの正直さが検証を助けました。monster_dropを「直接」と断定していたら、影響が水増しされて不要な調整会議が組まれていたでしょう。AIが「直接開いて確認することをお勧めします」とボールを人間へ渡してくれたおかげで、強度を正すことができました。
リストの検証が済んだら、次の手は、これを人に届ける仕事です。
直接影響を受ける担当者ごとにまとめて、teammate_a / teammate_b に
送る短い共有メモの草案を作って。"9012 맹독をXに差し替え検討中、
あなたのシートN行が影響" の形式で。決定はまだ出ていないので通知
ではなく意見要請のトーンで。
担当者別の共有メモ草案です。通知ではなく意見要請のトーンにしました。
[teammate_a 宛]
맹독バフ(9012)を別の効果に差し替える案を検討中です。確定では
なく、まず意見を伺いたいです。影響範囲:
- skill_table: アサシンスキル3つの行が9012を参照 (直接影響)
- set_item_effect: セットアイテム4種がproc_buffとして9012を使用
差し替えるとこれらのスキル・セットのコンセプトが変わります。反対や補完の意見が
あれば今週中に教えてください。
[teammate_b 宛]
monster_dropのreward_buffについて確認をお願いします。맹독バフ(9012)の
差し替えを検討中ですが、影響する行は1つに見えます(reward_buffカラムに
item IDが混ざっていて最初はもっと多く見えました)。実際にこの1行だけで
合っているか確認していただけますか?
2つのメモはどちらも「検討中・意見要請」とし、確定通知の表現は
入れていません。送る前に差し替え候補の効果(Xの箇所)を埋める必要があります。
冒頭の場面の2時間が、ここで消えます。変更の前に影響を問い、担当者へ前もって共有し、翌日ビルドが壊れる前に調整が終わります。そしてAIが作ったのは最後までメモの草案にすぎず、送信する指は人間のものです。
上の2つのトランスクリプトは、同じ骨格に従っています。ほかのすべてのパターンも、この骨格の上に載ります。
flowchart LR
A[ルール化されたコマンド
検査項目・出力形式・禁止事項] --> B[人間: 1行で呼び出し
対象だけ渡す]
B --> C[Claude: 生の出力
候補+確信度+自己疑念]
C --> D{人間による検証}
D -->|採用| E[反映]
D -->|訂正| F[強度・値を修正]
D -->|拒否| G[ルールの欠陥を発見]
G --> H[コマンド定義atomを更新]
H -.次回の呼び出しに反映.-> A
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class A,H data
class B,D,F human
class C ai
class E pass
class G fail
核心は右下の点線です。拒否は終わりではなく、コマンド自体を直す入力として戻っていきます。整合性チェックで拒否された4番がfinisherの例外ルールになり、そのルールがatomに記録されて次の検査へ伝播しました。このフィードバックがなければ、同じ誤答を毎週ふるいにかけ直すことになります。
人間の手が残るのは3か所です。呼び出し(対象の選択)、検証(採用・訂正・拒否)、ルール改善(拒否の還流)。AIはその間で、候補を差し出す仕事だけをします。
同じ骨格を別の作業へ移せば、パターンは増えていきます。トランスクリプトなしで、位置づけだけ押さえます。どれも3.4.5の骨格をそのまま踏襲するので、作るときの核心は「ルール化された検査項目」と「人間による検証の席」を抜かさないことです。
| パターン | 1行呼び出し | AIが差し出す候補 | 人間が握る決定 |
|---|---|---|---|
| GDD草案の合成 | /gdd-new <시스템> |
標準9セクションの草案、未定は[TBD] | ビジョン・優先順位・削除 |
| ステートマシン/BT変換 | /diagram-state |
自然言語 → mermaid + 到達性検証 | 状態の定義・遷移条件 |
| インターフェース衝突チェック | /check-interface <GDD> |
入出力・時間ウィンドウの衝突ケース | 優先順位ルール |
| バランス計算 | /balance-calc <시트> <수식atom> |
曲線の計算値 + 既存とのdiff | 数式・ゲームの意図 |
| 振り返り作業の分類 | /retro-classify <기간> |
Layer×分野の分布 + 異常シグナル | 分類の補正・解釈 |
バランス計算について、1つだけくぎを刺しておきます。曲線が数値の上ではなめらかに収まっていても、そのなめらかさがゲームの意図と合っているかは別の問題です。ボス直前の区間をあえて急勾配にしておきたかったのに、AIが「外れ値」だとして平らに削ってしまうことがあります。だからバランス計算は、曲線の検証が自動で付いていても、最後の1行は人間が意図と突き合わせてからでなければ閉じません。
パターンが増えると、運用の規律が必要になります。次の5原則は暗記しておくルールではなく、ツール自体に入れておく設計原則です。
| 原則 | なぜ |
|---|---|
| 1コマンド = 1作業 | 小さいほど再利用・デバッグが容易。/checkに検査・修正・共有を全部詰め込まない |
| コマンドに検証を自動で同伴させる | 出力の表自体に確信度・根拠の欄を入れて、人間の検証負担を減らす |
| コマンド定義をatomへ | 3.4.3の拒否→ルール還流のように、理由・例・変更履歴をatomに残す |
| 使用頻度の測定 | 月1回未満のコマンドは廃棄候補。データで切る |
| 人間の手は決定だけに | コマンドは候補の生成まで。自動決定は禁止 |
最後に、収束点を1つ。著者が携わったあるMMORPGプロジェクトでは、システム企画に安定的に残るスラッシュコマンドは、時間とともに12個前後へ収束しました。公開された標準ではなく、1つのプロジェクトの観察値です(著者の経験、未検証)。ただし、方向ははっきりしています。コマンドは無限に増やすものではなく、月に1〜2個を足し引きしながら、頭の中に収まる数で止めます。ラベルが100個付いた引き出しは、ラベルのない引き出しと同じです。
導入は一度に全部やりません。最初の月は、毎週繰り返す作業を1つスラッシュコマンドにルール化するだけで十分です。その1つが価値を見せれば、翌月には2つ、3つへと自然に広がっていきます。
3.1でシステム企画のLayer座標を、3.2でスキーマを、3.3で関係図を敷き、3.4でその上にAI補助のプロンプトを載せました。4つの章を通過したシステムプランナーの1週間は、このように変わります。
flowchart LR
M[月: GDD草案の合成
4h → 1h] --> T[火: 整合性チェック
半日 → 5分]
T --> W[水: オンボーディング、関係図1枚
会議5回 → 1枚]
W --> Th[木: 影響範囲の抽出
会議1h → 10分]
Th --> F[金: 振り返りの自動分類
手動 → データ駆動]
F --> R[浮いた時間を
設計・レビュー・プレイヤー体験に再投資]
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class M,T,W,Th,F ai
class R pass
雑務の時間が減り、その時間が深い設計とプレイヤー体験の検討へ戻っていきます。浮いた時間をまた雑務で埋めないこと、それがツールを導入した本当の理由です。
次のPart 4は戦闘企画です。システム企画に最も近い兄弟であり、3.1〜3.4のツールとパターンがそのまま引き継がれます。
setup. 毎週繰り返している検査作業を1つ選んでみましょう(例: シートの整合性)。その作業の検査項目4つと出力の表形式を書き出し、スラッシュコマンド1つにルール化します。ルール3行(「直接修正しない / 確信度を付ける / 推測は自白する」)を必ずコマンド本文に入れましょう。
prompt. 対象だけを1行で渡して呼び出しましょう。
/check-sheet skill_table
verify. 返ってきた表を1行ずつ、採用・訂正・拒否で判定しましょう。拒否が出たら、それは運ではなくルールの欠陥です。その例外をコマンド定義に追加する1行をもう一度送り、次の呼び出しが同じ間違いを二度ふるいにかけないようにしましょう。
チームもatomシステムもないなら、スラッシュコマンドの代わりに、メモアプリにテキストブロックを1つ置いてみましょう。タイトルは「シート検査プロンプト」。内容は上のsetupの検査項目4つ+ルール3行です。検査のたびにこのブロックをコピーし、シート名だけ変えてAIに貼り付けます。拒否することが出てきたら、そのメモブロックに例外を1行直接追加します。ツールがスラッシュコマンドでもメモ1枚でも、サイクル(ルール化 → 呼び出し → 検証・拒否 → ルール更新)は同じように回ります。
本章の学習目標(難易度🟡実務・前提:四則演算・表計算):打撃感のような抽象的な形容詞を測定可能なシグナルへ分解し、戦闘プランナーの5つの成果物がLayerのどのマスに座るのかを、座標で指定できるようになります。
ビルド会議室。プログラマーが、たったいま組み込んだ新規スキルをモニターに映します。キャラクターが剣を振り、敵が後ろへ押し出されます。5人が見ています。誰かが言います。
「うーん……なんだか打撃感がちょっと弱いな」
隣の人がうなずきます。「ですよね、ちょっと物足りないですね」
プログラマーが尋ねます。「どこをどう変えればいいでしょうか?」
沈黙。会議室の5人のうち、誰もその質問に数字で答えられません。「打撃感が弱い」は5人全員が感じたのに、「ヒットストップを3フレームから5フレームに」と言える人はいません。会議は40分間、「もっとずっしりと」「インパクトが足りない」のような形容詞をやり取りした末、「ひとまず次のビルドでまた見ましょう」で終わります。
この場面が、戦闘企画のすべての問題を圧縮しています。プレイヤーが最も直接体感する分野なのに、その体感を言葉に移した瞬間、形容詞しか残りません。形容詞は測定できず、測定できなければ調整もできません。戦闘プランナーの最初の仕事は、この形容詞を数字へ引き下ろすことです。
本章は、その数字がどのマスに入るのかを定めます。戦闘プランナーが作る5つの成果物がそれぞれLayerのどこに座るのか、そしてその座標がなぜ自動化の前提条件になるのか。4.2・4.3・4.4の実戦ツールは、この座標の上で動きます。
専門外の読者のための一行。 この部で戦闘の数値やフレーム単位を覚える必要はありません。持ち帰っていただくのはただ一つ、これです — 「形容詞でやり取りされる依頼は、測定も調整もできない」。「もっとずっしりと」を「何をいくつに」へ引き下ろした瞬間に協業が回り始めるという発想は、ゲーム外のどんな職務の曖昧なフィードバックにもそのまま適用できます。4.1.1の5つの成果物は軽く流し読みして、この一点だけ手に握って先へ進んでいただいて構いません。
戦闘プランナーが責任を持つ成果物を一行でまとめると、「プレイヤーの入力が画面上のアクションへ変換される全過程」です。これを5つの塊に分けます。
第一に、戦闘Look & Feel仕様。 打撃感・応答性・重量感のような抽象を、測定可能な数値に翻訳した文書です。これがこの分野で最も難しい成果物であり、残りの4つを評価する基準になります。
Look & Feelは、さらに4つのシグナルへ分解されます。
この仕様がなければ、会議室の場面が繰り返されます。仕様があれば、「ヒットストップ3→5フレーム、カメラシェイク振幅+20%」という調整指示が出ます。
第二に、スキル・コンボ・キャンセルのシステム。 入力がアクションへ変換されるルールです。
第三に、キャラクター・モンスターAI。 NPCの行動ロジック — ビヘイビアツリー(Behavior Tree、以下BT)、ステートマシン(FSM(Finite State Machine、有限状態機械)/HFSM)、デシジョンテーブル。モンスターの行動パターン、ボスのフェーズ移行、味方NPCの連携、群衆シミュレーションがここに入ります。
第四に、ダメージ・リソース・クールタイムの数式。 プレイヤーの選択が結果へ変換される数学です。ダメージ係数・防御減算・クリティカル・属性補正、リソース(MP/気力/スタミナ)の消費・回復曲線、クールタイム(クールダウン)の分布。
第五に、アニメーション制御仕様。 企画意図が実際のビルドでどう見えるかを定める図面 — アニメーショングラフ・BT・IKの接続です。これは通常プログラマー・アニメーターとの協業ですが、プランナーが意図の仕様を提供しなければ、ビルドで意図が壊れます。資材だけ渡して図面を渡さなければ、別の家が建ちます。
ここでの核心は、5つが同じ机の上で出会うという点です。コンボのルール(第二)が変わればダメージ数式(第四)のDPSが変わり、それがまたLook & Feel(第一)の体感の重さを変えます。どの成果物がどの成果物の入力なのかが明示されていなければ、一度の変更が5か所を揺らします。だから座標が必要なのです。
2.3で定めたL0〜L4の座標の上に、戦闘の成果物5つを載せます。このマッピングが本章の背骨です。
flowchart TD
L0["L0・ビジョン
'打撃感が生きているアクション戦闘'"]
L1["L1・システム骨格
コンボ・キャンセル構造 / Look&Feel仕様 / クラス骨格"]
L2["L2・コンテンツの流れ
チャプター別の敵集団曲線 / スキル解放順序"]
L3["L3・マスターデータ
ダメージ係数・クールタイム値・リソース消費"]
L4["L4・ビルド計測
実測DPS / 実際のコンボ経路 / プレイヤーフィードバック"]
L0 -->|"受け取る"| L1
L1 -->|"骨格が流れを規定"| L2
L1 -->|"仕様が数値の基準"| L3
L2 --> L3
L3 -->|"ビルドに反映"| L4
L4 -.->|"計測 → 仕様修正のフィードバック"| L1
L4 -.->|"外れ値 → マスターデータ調整"| L3
classDef vision fill:#2d3748,stroke:#1a202c,color:#fff
classDef build fill:#c05621,stroke:#7b341e,color:#fff
class L0 vision
class L4 build
表に整理し直すとこうなります。
| Layer | 戦闘企画の成果物 | 変更頻度 |
|---|---|---|
| L0 | (受け取る — ビジョン:「打撃感が生きているアクション戦闘」) | ほぼ固定 |
| L1 | コンボ・キャンセル構造 / Look & Feel仕様 / クラス骨格 | 遅い |
| L2 | チャプター別の敵集団進行曲線 / スキル解放の流れ | 中間 |
| L3 | スキルダメージ係数のマスターデータ、クールタイム値、リソース消費 | 速い |
| L4 | ビルド実測DPS、実際にコンボ可能な経路、プレイヤーフィードバック | ビルドごと |
戦闘企画の特徴は、L4の比重が他分野より大きいという点です。シナリオ企画ではL1仕様がほぼそのまま最終形ですが、戦闘は違います。「打撃感が良い」は、ビルドで直接手を動かして叩いてみて、画面を見なければ分からない領域です。仕様に「ヒットストップ5フレーム」と書いても、それが実際にずっしり感じられるかどうかはL4でしか確認できません。だからこそ、シミュレーションと自動計測ツールがこの分野で最も大きな価値を生みます(4.4)。
ただし、L4が大きいからといってL1の重要度が下がるわけではありません。点線の矢印を見てください。L4の計測値はL1仕様へフィードバックされます。仕様がなければ、計測値は「比較する基準」を失います。5フレームという仕様があってこそ、「実測4フレーム、1フレーム欠落」という診断が出ます。仕様 → ビルド → 計測 → 仕様修正のサイクルが、5つのLayerをすべて通ります。戦闘プランナーはこのサイクル全体に手を載せている、一つの机で最も多くのLayerが同時に回るポジションです。
抽象論はここまでです。ここからは会議室の場面に戻り、「打撃感が弱い」を実際に数字へ引き下ろす過程を、ワークド・トランスクリプトで見ます。道具はLLM一つです。出力は整えず、人が拒否した部分もそのまま残します。
著者がディレクターとして運営しているあるMMORPGプロジェクト(以下「プロジェクトA」)の戦闘TFで、新規近接スキル一つのLook & Feelを仕様化した実際の流れを再構成します。
このスキルのLook & Feelを、形容詞ではなくms・フレームの数値に変えてくれ。
ヒットタイミング、ヒットストップ、カメラシェイク、エフェクト同期、硬直の5つ。
確信がないものは「このゲームで検証必要」と書いてくれ。
スキル: 大剣縦斬り (heavy_overhead_slash)
意図: 「ずっしりと振り下ろし、敵がよろめく感じ」
フレームレート: 60fps
大剣縦斬りの「ずっしり感」は、主にヒットストップと硬直の長さ、そしてカメラシェイクの減衰で作られます。項目別に提示します。
1) ヒットタイミング。 重い武器ほど、入力から発動までの予備動作(発生)が長いほうが重量感が生きます。入力から最初の視覚反応(キャラクターが剣を持ち上げ始める)まで50〜80ms(3〜5フレーム)推奨。ただし、最初の視覚反応と最初の打撃反応は別物です。打撃の命中まではモーションの長さに依存するため、ここでは断定できません — このゲームのモーション長で検証が必要です。
2) ヒットストップ。 ずっしりした一撃の核心変数です。軽い攻撃が1〜2フレームなら、大剣縦斬りは4〜6フレーム(約67〜100ms)推奨。6フレームを超えると「ずっしり」が「引っかかり」へ変わり、応答性が悪くなります。
3) カメラシェイク。 振幅は画面高さの約1〜2%、持続0.2〜0.3秒、減衰は線形よりease-out(速く弱まる)。重い一撃は、最初の振動が強く、速やかに収まる曲線が自然です。
4) エフェクト同期。 VFX(ヒットエフェクト)・SFX(打撃音)・ヒットストップは必ず命中フレームに同時にトリガーされる必要があります。1〜2フレームずれただけで「物足りなさ」として体感されます。UI反応(ダメージ数字)は1〜2フレーム遅れても問題ありません。
5) 硬直。 ずっしり感の代償として硬直が長くなります。命中後、被弾可能な状態へ復帰するまで20〜30フレーム(333〜500ms)推奨。ただしこの値は、ゲーム全体の戦闘テンポ(速いアクション vs 重厚なソウルライク)によって大きく変わります — このゲームの基準テンポで検証が必要です。
まとめると、「ずっしり感 = 長い予備動作 + 4〜6フレームのヒットストップ + ease-outシェイク + 同一フレームのVFX/SFX/ストップ + 長い硬直」です。ただし上記の数字は一般的な推奨範囲であり、実際の重量感はビルドで手を動かして叩いてみて調整して初めて確定します。
ここでプランナーは、出力を丸ごと採用しません。2か所で立ち止まります。
拒否1 — カメラシェイク振幅「画面高さの1〜2%」。 このゲームはモバイル優先です。小さな画面では1〜2%はほとんど見えません。モバイルの画面酔いの問題もあります。プランナーはこの推奨値を拒否し、「モバイルではシェイクの代わりに、ヒットストップの強調で重さを表現する」という自前の原則を適用します。LLMは一般論を出しただけで、このゲームのプラットフォーム制約は知りません。
保留2 — ヒットストップ「4〜6フレーム」。 これは拒否ではなく保留です。範囲としては正しいものの、正確な値はビルドの手触りで決めます。仕様には「4フレームを基本値としてビルドに入れ、5・6フレームのバリエーションを作って3つを手で比較する」と書きます。
再依頼はこう出します。
モバイル優先のプロジェクトだ。カメラシェイクは最小化し、重量感を
ヒットストップ・硬直・SFXで表現する方向で仕様を書き直せ。
ヒットストップは4/5/6フレームの3バリエーションをビルド比較用に表で。
この2回目の出力でLLMは、モバイル制約を反映した仕様表を作ります。その表がビルドに入り、次のビルド会議でプランナーは形容詞の代わりに「4フレーム版は軽すぎる、5フレームを採択」と言います。40分の会議が、5分の決定に縮まります。
3つです。第一に、LLMは形容詞を数字の範囲へ引き下ろす一次ドラフトを上手に作ります — これが会議室の沈黙を破ります。第二に、LLMはこのゲームの制約(モバイル・テンポ・モーション長)を知りません — だから一般的な推奨値を出すだけで、拒否・調整は人の仕事です。第三に、LLM自身が「ビルドで手を動かして叩いてみて初めて確定する」と2回も釘を刺しました — 重量感の最終判断はL4の人の手にあるという事実を、道具も知っているのです。
上のトランスクリプトは、一つの持ち場(仕様化)だけを見せました。戦闘企画全体でAIが価値を生む持ち場は4か所です。
1) シミュレーション — 最大の価値。 DPS(Damage Per Second、秒間ダメージ)曲線・コンボ経路・リソース消費を、ビルドなしで事前計算します。ビルドを作って手で計測するより圧倒的に速いです。4.4でsimulate_dpsシミュレーターを直接扱います。
2) ステートマシン・BTの自動生成。 「このボスは体力50%以下で狂暴化し、狂暴化中は3連撃パターンを使う」のような自然言語の説明を、BT/FSMダイアグラムへ変換します。正確度は高いです — ルール構造はLLMが得意とする領域です。頭の中のロジックを図へ移す時間が節約されます。
3) ビルドキャプチャーの自動分析。 プレイ映像からヒットタイミング・コンボ成功率・被害分布を自動抽出します。ただしこれは実装難易度が最も高い持ち場です(下で正直に検討します)。
4) バランス調整候補の提案。 マスターデータの各行を分析して外れ値・曲線の非平滑さを検出し、調整候補を出します。人は選ぶだけです。
この4か所のうち、ビルドキャプチャーの自動分析(3)は、「できる」と「簡単にできる」の距離が最も遠い持ち場です。書籍ではよく「AIが映像から自動で全部抽出してくれます」と書かれますが、実際はそれほど単純ではありません。映像ピクセルベースのコンピュータービジョン、既製のビジョンAPI、ゲーム内telemetryログ — 3つのキャプチャー方法の正確度・実装負担の比較は4.4を正とするので、そちらを参照してください。ここでは結論だけ押さえます。
最も現実的な道はゲーム内telemetryログです。エンジンが「フレーム1204でskill_overheadが命中、ダメージ340、コンボカウント3」のようなイベントを直接記録するようにします。これはソースデータなので正確で、ロギングコードを一度挿入すれば終わりです。LLMはそのログを読んで、自然言語レポート(「3コンボまではリソース効率が良いが、4コンボから急減する」)へ要約するのに使います。映像は、人が疑わしいケースだけ目で確認する補助として残します。
つまり「AIが映像を自動分析する」というビジョンの現実的な形は、telemetryログ + LLM要約であって、ピクセルビジョンではありません。この正直な区分が、4.4のツール選択の出発点です。
そして、4つの持ち場すべてで変わらないことが一つ。「打撃感が良い」の最終判断はAIにはできません。 それはプレイヤーの感情の領域であり、その感情に対する責任は人が引き受けます。AIはその感情判断の根拠資料を速く作ってくれるだけです。シミュレーション数値、BTダイアグラム、telemetryレポート — すべて、人が手触りで決定を下すための材料です。
ここまでは「成果物をLayerで分ければ、協業のときに言葉が通じる」という表面的な理由でした。コンボのルールをL1に、ダメージのマスターデータをL3に置いたのは、変更頻度が違うからだと説明しました。正しい話ですが、それがすべてではありません。
座標を分けた本質的な理由は、自動化がその上でしか動かないからです。Layer分解がプロシージャル生成・自動化の前提であるという一般的な論題は2.3で扱ったので、ここではその前提が戦闘分野の3つの自動化でどう明暗を分けるのかに絞ります。
第一に、シミュレーションは「何を入力し、何を変えられるのか」が区分されていなければ回りません。 決定論的コア(物理・当たり判定 — L1骨格)と変更可能な仕様(ダメージ値・クールタイム — L3マスターデータ)が混ざっていると、シミュレーターは「変更候補空間」を定義できません。コアは固定、マスターデータは変数 — この分離があってこそ、simulate_dpsが「ダメージ係数を280から340まで20刻みで上げながらDPS曲線を描け」のような探索を行えます。
第二に、ビルドキャプチャーの自動分析は、アクションatomがラベリングされていて初めて意味を持ちます。 仕様の側で「このフレーム区間はskill_overheadのhit段階」とラベル付けされたatomがあってこそ、telemetryログから抽出したシグナルを仕様と自動照合できます。ラベルがなければ、ログは「フレーム1204で何かが命中」という、意味のない点の羅列です。
第三に、LLMによるコンボシーケンス生成は、キャンセルルール・入力キューが外部文書として分離されていて初めて動きます。 「このキャラクターのキャンセル可能ペア7個と入力キュー200msの中で、5コンボのシーケンスを10個提案せよ」のような限定依頼は、キャンセルルールがコードの中に固定されておらず、文書として切り出されているときにのみ可能です。
3つが同じ一つの文を語っています。決定論的コアが仕様と混ざれば自動化は塞がり、分離されれば自動化は開きます。 Layer分解は、協業言語の統一が表面の目的で、本質の目的は自動シミュレーション・キャプチャー分析・LLMシーケンス探索の前提条件を敷くことです。
この前提が敷かれると、戦闘運営は2段階で進化します。
保守的適用 — 人が設計し、自動が検証します。 いま、大半のアクション・MMORPGの戦闘運営はここにあります。人がコンボ・キャンセル仕様を直接書き、自動がDPS・リソースをシミュレートし、telemetryでキャプチャーして「仕様 vs 計測」の比較レポートを出します。人はその差を解釈して仕様修正を決定し、再び仕様作成へとサイクルが回ります。設計は人、シミュレーション・キャプチャー・比較は自動です。
進歩的適用 — AIが候補を発議し、人は採択するだけ。 次の段階です。AIがキャンセルペアと入力キューの範囲内でシーケンスを10〜30個自動列挙し、自動が各シーケンスのDPS・リソースを並列にシミュレートし、LLMが「リソース効率1位、入力難易度は中」のように順位・解釈を付けます。人の手に残る決定は「候補のうちどのシーケンスをシグネチャーとして採択するか」一つ、そしてディレクターによるビルド反映・モーションキャプチャーの決定だけです。シーケンスをゼロから作るのと、30個の中から選ぶのとでは、作業負担の次元が違います。
進歩的適用が定着するには、3つが揃う必要があります。(1)ビルドなしで1秒以内にDPS・リソース・生存時間を計算する決定論的シミュレーションインフラ、(2)コンボ・キャンセル・入力キューが外部文書として分離・ラベリングされたアクションatom、(3)telemetryベースのキャプチャー自動分析。3つとも、上で述べたLayer分解の直接の成果物です。
最後に可逆性です。戦闘プランナーのレビューサイクルには、巻き戻せる段階と巻き戻せない段階が混ざっており、その境界を知ることが重要です。
モーションキャプチャーは、戦闘で最も分厚い不可逆段階です。キャプチャースタジオのスケジュール、俳優のキャスティング、再撮影の費用、どれも大きくつきます。だからシグネチャーアクションのモーションキャプチャーは、シミュレーションとキャプチャー自動分析が十分に回り、シーケンスを確定した後にのみ進めます。保守的であれ進歩的であれ、モーションキャプチャーとライブビルドの直前を決定ゲートとして置きます。戦闘プランナーのすべてのレビューは、このゲートの左側の可逆段階で完結してこそ安全です。
プロジェクトAの戦闘TFが、上の座標とツールを6か月運営しながら計測した変化です。以下の数値はTF運営記録から抜き出したおおよその平均であり、精密な計測値ではなく、体感変化の方向として読むのが正確です。
| 項目 | 導入前 | 導入後 |
|---|---|---|
| Look & Feel会議の時間 | 平均2時間(主観の議論) | 平均30分(計測値ベース) |
| コンボダイアグラムの作成 | 1〜2時間/スキルセット | 10分/スキルセット |
| DPS曲線の検証 | ビルド後に手動計測(≈1日) | シミュレーション(≈10分) |
| 新スキルのバランス調整 | 3〜4回のビルドサイクル | 1〜2回のビルドサイクル |
数字そのものより、方向が核心です。4項目すべてが「主観の議論・手動計測・ビルドの繰り返し」から「計測値・シミュレーション・ダイアグラム自動化」へ移りました。会議室で形容詞が減り、数字が増えました。それが本章全体の言いたい一文です — 戦闘プランナーの仕事は、主観(打撃感・面白さ)から客観(数値・シミュレーション)へ渡る橋を架けることであり、AIはその橋を速く敷く道具です。橋の先で「ずっしりしている」を決める手は、依然として人のものです。
setup. LLMが一つあれば十分です。手元のスキルを一つ選んでください(新規でも既存でも)。そのスキルの意図を形容詞一行で書きます — 「ずっしりと」「素早く」「重々しく」のように。
prompt. 下の骨格にスキル情報を埋め込んでみましょう。
君は戦闘企画の補助だ。下のスキルのLook & Feelを「測定可能な
数値仕様」に変換せよ。形容詞ではなくms・フレーム・%単位で。
確信のない項目は「このゲームで検証必要」と明記せよ。
スキル: [名前]
意図: 「[形容詞一行]」
フレームレート: [60fpsなど]
項目: 1)ヒットタイミング 2)ヒットストップ 3)カメラシェイク 4)エフェクト同期 5)硬直
verify. 出力のすべての数字に、2つの質問を投げてみましょう。(1)このゲームの制約(プラットフォーム・テンポ・モーション長)でこの値は合っていますか? → 合わなければ、制約を伝えて再依頼してください。(2)この値はビルドで手を動かして確定すべきものですか? → そうであれば、単一の値の代わりに2〜3個のバリエーションを仕様に書き、ビルドで比較してください。LLMが「検証が必要」と釘を刺した項目は、絶対にそのまま採択しないでください。
一人で作るゲームなら、5つの成果物・5つのLayerをすべて揃える必要はありません。最低限、次の2つだけ行ってください。一つ、Look & Feel仕様を1ページ — 核心アクション3〜5個について、ヒットストップ・硬直・同期だけを数字で書きます。形容詞で書かれたメモは、6か月後の自分にも読み解けません。二つ、コンボ・キャンセルをコードから分離して1ファイルに — キャンセルペアをデータとして切り出しておけば、後でLLMに「このペアで作れるコンボを5個提案」と頼めます。この2つが、1人開発でも自動化の扉を開けておく最小の座標です。
会議室のモニターの前に5人が集まっています。同じビルド、同じスキル、同じ30秒の映像が、画面で3回目のリピート再生されているところです。クライアントプログラマーが最初に口を開きます。「自分は問題ないと思いますけど」。アート担当が腕を組みます。「弱いですね。何か足りない感じ」。隣にいたプランナーが割り込みます。「エフェクトはいいんですが、手に馴染まないんですよ」。ディレクターはしばらく見てから決定を下します。「うーん……もう少しだけ重めにいきましょう」。
そして会議が終わります。「もう少しだけ重めに」が正確に何msで何フレームなのかは、誰も書き留めませんでした。次のビルドでプログラマーは自分が理解した「重さ」を実装し、アートは自分が理解した「重さ」を載せます。そして翌週、同じ会議室で同じ映像を見ながら、同じ会話が繰り返されます。
打撃感、手触り、Look & Feel。戦闘企画でもっとも頻繁に使われ、もっとも定義されていない言葉です。誰もが知っていると信じているのに頭の中の定義はバラバラで、議論が終わっても何も残りません。本章はその「感じ」を測定可能な数字に分解する作業を扱います。手触りを抽象からデータへ引きずり下ろす場です。
まず境界線を引いておきます。戦闘企画は大きく2つに分かれます。
本章は後者だけを扱います。ダメージが100でも120でも、手触りとは直接の関係がありません。その100のダメージが「入る瞬間」をプレイヤーがどう体感するかが手触りです。同じダメージ計算式でも、ヒットタイミングとヒットストップが違えば、まったく別のゲームのように感じられます。
まず正直に押さえておきたいことがあります。打撃感は3つの数字だけでは完成しません。攻撃モーション(アニメーション)の加速・減速カーブ、受け手側の反応(ヒットリアクション・硬直)、80〜90年代の日本のアクションゲームが好んで使ったデフォルメ(打撃の瞬間にキャラクターを誇張して伸ばし、つぶす残像・つぶれの表現)まで全部そろって、はじめて「当たった」というひとかたまりの感覚が立ち上がります。本章が集中して測定可能な数字に引きずり下ろすものは、そのうちの3つの軸です。モーション・リアクション・デフォルメはアニメーター・アーティストの手がより大きく関わる領域なので、続く章とアートのパートで扱い、ここではプランナーが仕様書で固定し、ビルドで検証できる3つの軸に重きを置きます。その3つの軸は、次のように分かれます。
会議室で誰かが「弱い」と言うとき、その弱さは3つのうちのどれかから来ています。反応が遅いのか(タイミング)、ヒットの手応えがないのか(ヒットストップ)、バラバラに動いているのか(同期)。3つの軸に分解して問えば、「弱い」がようやく直せる文章になります。
ただし、「弱い」の原因がつねにこの3軸だけにあるわけではありません。手触りを構成する要素を漏れなく並べ、本章がどこまで責任を持つのか、線を引いておきます。
| Look & Feelの構成要素 | 何か | 本章での扱い |
|---|---|---|
| ヒットタイミング | 入力 → 最初の反応までのms。最初に疑われる要素 | 測定・仕様化(軸1) |
| ヒットストップ | ヒットの瞬間に時間を止めて重さを与える長さ | 測定・仕様化(軸2) |
| カメラシェイク | 打撃に合わせて画面が揺れる反動 | 測定・仕様化(軸3に含む) |
| VFX・SFXタイミング | エフェクトと音がヒットフレームに同期しているか | 測定・仕様化(軸3に含む) |
| 攻撃モーション(アニメーション) | 振りの加速・減速、予備動作とフォロースルーのカーブ | 言及(アート・アニメーション領域) |
| ヒットリアクション・硬直 | 受け手側がのけぞって硬直する反応 | 言及(次章・アートのパート) |
| デフォルメ | 打撃の瞬間に伸ばしつぶす誇張(残像・つぶれ) | 言及(アートのパート) |
| コントローラーの振動 | 手に伝わる物理フィードバック | 測定・仕様化(軸3に含む) |
上の4つが本章の3軸としてまとめられ、測定・仕様化の対象になります。中央の3つ(モーション・リアクション・デフォルメ)は欠かせないものの、プランナーひとりでは数字で閉じにくいアート・アニメーション領域なので、「存在する」ということだけを明確にしておきます。モーションが硬かったり、受け手側が平然と立っていたりすれば、3軸がすべて合っていても打撃感は立ちません。
3つの軸のうちタイミングを最初に扱うのには理由があります。プレイヤーが手触りを疑うとき真っ先に引っかかるのが「反応が遅い」という感覚であり、他のどこがどれだけ派手でも、入力がもたつけばその瞬間にすべてが崩れるからです。だから、タイミングから固めます。
手触りの最初の軸は時間です。ボタンを押したその瞬間(0ms)から、画面が最初に反応する瞬間まで何msかかるか。人間はこの遅延に驚くほど敏感です。60msと120msの違いを「言葉では説明できなくても、手は知っている」のです。
1回の攻撃は単純な1点ではなく、時間の上に広がる複数の事件です。通常攻撃の1段目を時間軸に載せると、こんな形になります。
この図でもっとも重要な数字は「当たり判定(ヒットボックス)が最初にオンになる100ms」です。ボタンを押してから100ms後に攻撃判定が始まるという意味です。この値が手触りの体感速度を決めます。
推奨範囲はジャンル・キャラクターごとに異なりますが、おおまかな基準線はあります。
| 種類 | 推奨 入力→反応 | 備考 |
|---|---|---|
| 即時反応(軽い攻撃) | 60〜120ms | 手に「吸い付く」感じの核心区間 |
| 重い反応(大型スキル) | 200〜400ms | 重さを出すための意図した発生の遅れ |
| チャージ(長い溜め) | 500〜2000ms | 意図した待機、別枠で処理 |
この範囲は絶対の基準ではありません。著者の推定(未検証)であり、カジュアルモバイルは入力に寛容な方向へ±50msほど揺れ、コンソールの格闘ゲームはより厳しく締める傾向があります。数字そのものより、「うちのゲームの軽い攻撃は90msで合意した」という基準線をチームで共有することが核心です。基準線があってはじめて、ビルドを見て「合っている/間違っている」を言えるようになります。
ところが、ここにひとつ落とし穴があります。人間の目では90msと110msを区別できません。60fpsでは1フレームは約16.67msで、この20msの差は1フレーム余りです。会議室での「ちょっと遅い気がするんだけど?」という言葉が正しいのか間違っているのか、目では最後まで判定がつきません。だから測定が必要なのです。
仕様書に「当たり判定100ms」と書きました。ビルドで実際に100msでオンになることをどう確認するのか。自動化の道は3つ(映像分析・既製のvisionツール・ゲーム内テレメトリー)に分かれますが、3方式の精度・難易度の比較は4.4で本格的に扱います。ここでは結論だけ押さえます。実務で最初に敷くべきものはゲーム内テレメトリーです。理由は単純です。VFXが画面に「現れたフレーム」を映像から推論するより、コードがOnHitイベントを発生させたフレームに直接[HITLOG]を1行出力するほうが、比較にならないほど正確で安上がりだからです。映像分析は入力オーバーレイのない外部映像(例:競合作の分析)にだけ使い、自分たちのビルドにはテレメトリーから敷きます。
テレメトリーログはこんな形をしています。
[HITLOG] frame=6 t_ms=100 evt=hitbox_on skill=1001 char=warrior
[HITLOG] frame=6 t_ms=100 evt=vfx_trigger skill=1001
[HITLOG] frame=6 t_ms=100 evt=sfx_trigger skill=1001
[HITLOG] frame=7 t_ms=117 evt=damage_apply skill=1001 dmg=124
[HITLOG] frame=7 t_ms=117 evt=ui_dmgnum skill=1001
[HITLOG] frame=6 t_ms=100 evt=cam_shake skill=1001 amp=0.4
プランナーの仕事は、このログを仕様書と1行ずつ突き合わせることです。数値の換算と機械的な照合がほとんどなので、人間が目で繰り返すと疲れて間違えますが、LLMは疲れません。次の節で実際にやらせてみます。
仕様のyamlとビルドのテレメトリーログを両方貼り付けて、AIに2つを突き合わせ、ズレている項目を見つけるよう指示しました。以下はプロンプトの全文とClaudeの生の出力、そして人間による検証プロセスの全体です。要約せず、そのまま載せます。
次は戦闘スキル1つの「ヒットタイミング仕様」と、実際のビルドから抜いた
telemetryログだ。仕様に対してビルドがずれている項目を表に整理して。
[仕様]
character: warrior
skill_id: 1001
fps: 60
events:
hitbox_on: {expect_ms: 100, tol_frame: 0}
vfx_trigger: {expect_ms: 100, tol_frame: 0}
sfx_trigger: {expect_ms: 100, tol_frame: 1}
damage_apply: {expect_ms: 110, tol_frame: 1}
ui_dmgnum: {expect_ms: 100, tol_frame: 2}
cam_shake: {expect_ms: 100, tol_frame: 1}
[telemetryログ]
[HITLOG] frame=6 t_ms=100 evt=hitbox_on skill=1001
[HITLOG] frame=6 t_ms=100 evt=vfx_trigger skill=1001
[HITLOG] frame=8 t_ms=133 evt=sfx_trigger skill=1001
[HITLOG] frame=7 t_ms=117 evt=damage_apply skill=1001
[HITLOG] frame=9 t_ms=150 evt=ui_dmgnum skill=1001
[HITLOG] frame=6 t_ms=100 evt=cam_shake skill=1001
60fps(1フレーム=16.67ms)。expect_msをフレームに換算してログのframeと
比較し、差がtol_frameを超えたらFAILと表示して。
まず仕様のexpect_msをフレームに換算します。60fpsではframe = round(ms / 16.67)です。
- hitbox_on: 100 / 16.67 ≈ 6.0 → frame 6
- vfx_trigger: 100 → frame 6
- sfx_trigger: 100 → frame 6
- damage_apply: 110 / 16.67 ≈ 6.6 → frame 7(四捨五入)
- ui_dmgnum: 100 → frame 6
- cam_shake: 100 → frame 6
ログの実際のframeと比較した結果です。
イベント 期待frame 実際frame 差(frame) 許容 判定 hitbox_on 6 6 0 0 PASS vfx_trigger 6 6 0 0 PASS sfx_trigger 6 8 +2 1 FAIL damage_apply 7 7 0 1 PASS ui_dmgnum 6 9 +3 2 FAIL cam_shake 6 6 0 1 PASS 2項目が許容誤差を超えました。 - sfx_trigger:サウンドが視覚ヒットより2フレーム(約33ms)遅れています。許容1フレームを超過。プレイヤーは「音が少しずれている」と感じる可能性が高いです。 - ui_dmgnum:ダメージ数字が3フレーム(約50ms)遅れて表示されます。許容2フレームを超過。「当てたのに数字が遅れて出る」という違和感につながり得ます。
damage_applyがframe 7であるのは、仕様上のframe 7(110ms換算)と一致するので正常です。ただし、damage_applyがhitbox_on(frame 6)より1フレーム後である点は、意図された設計かどうか確認が必要です。
出力を受け取って、すぐに全部を信じたわけではありません。検証ポイントが2つありました。
検証1(四捨五入の境界 — 通過)。damage_applyの110ms換算が疑わしく見えました。110 / 16.67 = 6.598…です。四捨五入すれば7で合っています。しかし、もし仕様の意図が「6フレーム = 100msと同じフレーム」だったとしたら? 直接確認したところ、仕様にはdamage_apply: expect_ms 110としてvfx(100)と意図的に10msの差をつけたのが正しいと分かりました。だからframe 7への換算は正確でした。AIは通過です。
検証2(最後の文 — 拒否)。AIが最後に付け足した「damage_applyがhitbox_onより1フレーム後である点は、意図された設計かどうか確認が必要です」はノイズでした。仕様にすでにexpect_ms 110として意図が明記されているのに、AIがそれを「要確認」と問い返したのです。仕様を渡したなら仕様の中で答えを閉じるべきなのに、不要な質問を作り出しました。この文は採用しませんでした。
拒否の理由を添えて、再依頼しました。
damage_applyは仕様に110として意図が明記されているから「確認が必要」コメントは外して。
// 仕様の中で答えが閉じている項目を問い返すなという意味
FAILの2件(sfx, ui_dmgnum)だけ残して、原因の断定の代わりに
「確認すべきコード/リソースの位置」のチェックリストにして。
FAIL 2件についての確認チェックリストです。原因は断定せず、確認すべき地点だけを整理しました。
sfx_trigger(+2フレーム遅延) - [ ] サウンドのトリガーが
hitbox_onと同じイベントにバインドされているか、それとも別のアニメーション通知(Notify)に掛かっているか - [ ] オーディオアセット自体に先頭無音(leading silence)があるか — ファイル冒頭の空白区間が遅延に見えることがある - [ ] サウンド再生の呼び出しは同じフレームで入ったのに、出力レイテンシーで押し出されたのか(プラットフォームのオーディオバッファ)ui_dmgnum(+3フレーム遅延) - [ ] ダメージ数字のUIが
damage_apply(frame 7)以降の別ティックで更新される構造か - [ ] UI更新が次フレームの末尾にまとめて処理されるのか(遅延レンダーキュー) - [ ] 数字ポップアップアニメーションの開始フレームに追加のディレイ値が入っているか断定できること:2項目とも仕様の許容誤差を超えており、視覚ヒット(frame 6)より遅れています。断定できないこと:上記の原因のうちどれなのかは、コード/アセットを確認するまで分かりません。
この2次出力はそのまま採用しました。原因をでっち上げず、「確認すべき場所」に変えたのが、まさに求めていた形でした。このチェックリストをサウンド担当とUI担当にそのまま渡したところ、サウンドはオーディオアセットの先頭無音33msが犯人でした(チェックリストの2番目の項目)。UIは次フレーム更新の構造でした(1番目の項目)。
ここで分業の線がはっきりします。AIは仕様書とログを機械的に突き合わせてFAILを捕まえ、人間は(a)AIが作り出した不要な問い返しを拒否し、(b)FAILの本当の原因をコードで確定します。AIに原因の断定をさせると、もっともらしい嘘を作り出すので、「確認すべき場所」までにとどめるのが安全です。
2つ目の軸は停止です。ヒットが命中した瞬間、ゲーム時間をごく短く止めたり遅くしたりする効果。これが「当てた」という感覚の強度を決めます。格闘ゲームとアクションRPGで、もっとも強力な手触りの道具です。長すぎればもたつき、短すぎれば重さが出ません。
推奨範囲(60fps基準)は次のとおりです。この数値はアクションゲームで通用するおおまかな慣行であり、絶対値はゲームごとに調整します。
| 種類 | 推奨フレーム | 換算 |
|---|---|---|
| 軽いヒット | 1〜2フレーム | 16〜33ms |
| 中間のヒット | 3〜5フレーム | 50〜83ms |
| 重いヒット(必殺技) | 6〜12フレーム | 100〜200ms |
| クリティカル・弱点ヒット | 上の値 + 2〜3フレーム | — |
キャラクター・スキルごとに変えて与えるべきです。全部同じにすると重さの差が出ず、結局すべての攻撃が同じトーンに収束します。これが、4.2のポイント3つのうちの1つにつながる地点です。
「誰が止まるのか」も設計上の選択です。
| オプション | 効果 | 適性 |
|---|---|---|
| 攻撃側だけ停止 | 攻撃側に重み、受け手側はノックバック・ダウンが進行 | アクション |
| 受け手側だけ停止 | 受け手側は一時停止、攻撃側は自由に動ける | コンボ親和 |
| 両方停止 | もっとも強い重み | 格闘ゲームの伝統 |
仕様書にはこう入力します。
character: warrior
skill_id: 1001
hit_stop:
attacker: 2 # frames
victim: 4
critical_multiplier: 1.5 # クリティカル時1.5倍 (四捨五入)
ヒットストップは、仕様の数字だけでは「合っている/間違っている」を閉じられない、手触り検証がもっとも難しい軸です。テレメトリーで「実際に4フレーム止まったか」は捕まえられますが、「4フレームが適切か」は人間がビルドを直接触りながら判定しなければなりません。AIは仕様との一致を保証し、人間はその仕様値そのものの適切さを見ます。
3つ目の軸は同時性です。VFX(視覚エフェクト)・SFX(音)・UI(ダメージ数字)・カメラ(シェイク)・振動(コントローラー)が同じフレームで始まると、プレイヤーの脳はそれを「1つの事件」として束ねます。1〜2フレームずれただけで「違和感がある」、3〜5フレームずれると「バグみたいだ」という反応が出ます。先のワークド・トランスクリプトでsfxが2フレーム、uiが3フレーム遅れてFAILになったのが、まさにこの軸の問題でした。
同期対象の5種と許容誤差です。
| 要素 | トリガー時点 | 許容誤差 |
|---|---|---|
| VFX(視覚エフェクト) | ヒットフレーム | ±0(必ず同時) |
| SFX(サウンド) | ヒットフレーム | ±1フレーム(16ms) |
| UIダメージ数字 | ヒットフレーム | ±2フレーム |
| カメラシェイク | ヒットフレーム | ±1フレーム |
| コントローラーの振動 | ヒットフレーム | ±2フレーム |
核心は、この5種がすべて仕様書に入っていなければならないということです。よくある失敗が、VFXだけ仕様書に書いて、残りの4つを「うまく合わせてくれるだろう」で済ませることです。仕様書になければビルド検証の基準もなく、テレメトリーで捕まえても比較する対象がありません。5種が全部仕様書にあってはじめて、自動比較が閉じます。
自動比較には、前節のワークド・トランスクリプトがそのまま適用されます。テレメトリーログに5種のトリガーフレームが全部記録されていれば、AIが仕様書と突き合わせて誤差超過の項目だけをレポートします。人間が100個のスキルを毎ビルドごとに目で確認する必要はありません。ただし、AIが仕様とのズレを捕まえることと、人間が「仕様は全部合っているのに、それでも感覚が立たない」領域を捕まえることは、別物として残ります。
ここまでの断片を1つの流れにつなぎます。このループが回り始めると、会議室の「もう少しだけ重めに」が「ヒットストップ victim 4→6フレーム」に翻訳されます。
flowchart TD
A["仕様書の作成
(プランナー: yaml)"] --> B["ビルドの実装
(プログラマー・アーティスト)"]
B --> C["ビルド実行 + テレメトリーログ
[HITLOG] 自動出力"]
C --> D["AIによる自動突き合わせ
仕様yaml vs テレメトリー"]
D --> E{"誤差超過の
FAILはあるか?"}
E -->|あり| F["FAIL項目 + 確認チェックリスト
(AI、原因の断定は禁止)"]
F --> G["担当者がコード・アセットで
本当の原因を確定 (人間)"]
G --> B
E -->|なし| H["プランナー: 「感覚」の検討
仕様は合っているが手触りは出るか (人間)"]
H -->|要調整| A
H -->|OK| I["次のマイルストーンの振り返りの入力として保管"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class C code;
class D,F ai;
class A,G,H human;
class I pass;
このループでは、AIが受け持つマス(D、F)と人間が受け持つマス(A、G、H)がはっきり分かれます。AIは機械的な突き合わせとチェックリストの生成に強く、人間は基準線の設定・原因の確定・最終的な感覚の判定に強いのです。自動化は人間を外すのではなく、毎回半日ずつ費やしていた「目でフレームを数える」作業から人間を解放し、「感覚」だけに集中できるようにします。
ループの最後のマス(I)が重要です。この測定データは一度使って捨てるものではなく、次のマイルストーンの振り返りの入力として再び入っていきます。「先四半期の手触りFAILはsfx同期に集中していた」のようなパターンがデータとして残れば、次の四半期はオーディオパイプラインから先に手を入れます。
著者がディレクターとして参加したあるモバイルMMORPGプロジェクト(以下「プロジェクトA」)で、上記のループを約6か月回しながら観察した変化です。以下の数値は精密な計測ではなく、議事録のタイムスタンプとビルド検証の記録に基づく著者の運用観察(推定を含む)であり、方向性と比率で読んでいただきたいものです。絶対値を引用可能なベンチマークとして使わないでください。
| 項目 | 導入前 | 導入後 | 性格 |
|---|---|---|---|
| Look & Feel会議1件の所要 | 長く伸びがち | 半分以下に短縮 | 議事録基準、体感 |
| ビルド検証(多数のスキル) | ほぼ1日 | 大幅に短縮 | テレメトリー自動突き合わせの効果 |
| 「打撃感が弱い」フィードバックの解消 | 複数のビルドサイクル | 1〜2サイクル | サンプルが少なく、方向性のみ |
| 仕様書 vs ビルドの一致率 | 半分余り | おおむね一致 | テレメトリー導入後に測定可能に |
数字そのものより、定性的な変化が本質です。会議室で「弱いんですが」が出ると、すぐに「どの軸ですか? タイミング? ヒットストップ? 同期?」と問い返すようになり、答えが出なければテレメトリーを一緒に立ち上げました。測定可能な客観が議論の終着点を作ったことが、6か月でもっとも大きな変化です。「もう少し重めに」が会議室の外へ出ていくことが減りました。
| 失敗 | 回避策 |
|---|---|
| 仕様書なしでビルドから検証 | 仕様書が先。基準がなければ「合っている/間違っている」が不可能 |
| 映像分析から敷こうとする | 自分たちのビルドはテレメトリーから。映像分析は外部映像にだけ |
| AIにFAILの原因を断定させる | 「確認すべき場所」のチェックリストまで。原因は人間がコードで確定 |
| VFXだけ仕様化し、残り4種が漏れる | 5種(VFX・SFX・UI・カメラ・振動)すべてを仕様書に |
| 1キャラクターの仕様を全キャラクターにコピー | キャラクター・スキルごとに差別化。同じだと手触りが1つのトーンに収束 |
| ヒットストップをすべてのヒットに乱発 | 意味のあるヒットにだけ。乱発するともたつく |
| AI出力の問い返しをそのまま受け入れる | 仕様の中で閉じている項目の「要確認」コメントは拒否 |
自分のプロジェクトに、このループを最小規模で敷いてみる手順です。
setup
1. 検証するスキルを1つ選びます(通常攻撃を推奨)。
2. コードのトリガー地点6か所(hitbox_on、vfx、sfx、damage_apply、ui_dmgnum、cam_shake)に、[HITLOG] frame=X t_ms=Y evt=... skill=...の形式でログを1行ずつ仕込みます。
3. 仕様のyamlを書きます(eventsブロックにexpect_ms + tol_frame)。本章の1次プロンプトの例をテンプレートとして使ってください。
prompt 4. ビルドを1回走らせて、テレメトリーログを集めます。 5. 仕様のyaml + テレメトリーログを一緒にAIに貼り付けて、こう指示します。「仕様書に対してビルドがずれている項目を、60fpsのフレーム換算で突き合わせてFAILだけ表にして。原因は断定せず、『確認すべき場所』のチェックリストで」(本章の2次プロンプトの形)。
verify 6. AI出力のフレーム換算を、1か所だけ自分で検算してみましょう(ms / 16.67を四捨五入)。1か所でも間違っていたら、全体を疑いましょう。 7. AIが仕様の中で閉じている項目を問い返したり、原因を断定したりしたら、拒否して再依頼しましょう。 8. FAILチェックリストを担当者に渡して、コード・アセットで本当の原因を確定してもらいましょう。
チームもテレメトリーのインフラもない個人開発者なら、こう縮めましょう。ビルドを60fpsで画面録画し、入力の瞬間が見えるようにキー入力オーバーレイをオンにします。検証するスキルを1回録画したあと、動画編集ソフトで「ボタンを押したフレーム」と「画面が最初に変わったフレーム」を自分で数えます。2つのフレーム番号と仕様の期待値をAIに渡して「60fps基準のmsに換算して仕様と比較して」と指示すれば、テレメトリーなしでも核心の1軸(ヒットタイミング)は検証できます。5種の同期までは難しいですが、「入力→反応」の1軸を押さえるだけでも、手触りの議論の半分は客観へ移ってきます。
次章は、1回のヒットからヒットの連続へ進みます。コンボ・キャンセル・入力キュー — 1つのヒットが次のヒットへ自然につながるようにするルールを扱います。
バトルプランナーのチームメンバーBが、会議室のホワイトボードの前に立ってマーカーでボックスを描いていました。基本1、基本2、基本3、そして横へ抜ける強攻撃の分岐。矢印が7本ほどに増えたところで、誰かが尋ねました。「じゃあ、強攻撃の打ち上げの後に回避でキャンセルしたら、また基本1に戻れるんですか?」チームメンバーBはマーカーを止めました。ホワイトボード上のグラフには、その経路は描かれていませんでした。描けるのに描かなかったのか、ルール上不可能なのか、本人も即答できませんでした。
これがコンボ設計の本当の問題です。コンボは頭の中では「1-2-3とつながって強攻撃に分岐」のような単純な幹に見えます。ところがキャンセルと入力キューが絡むと、幹はグラフになります。ノード6個にキャンセルエッジを数本足しただけで、実際に踏める経路は数十通りに膨れ上がります。人間はその数十通りを頭の中ですべて展開できません。だから「この経路が強すぎる」というバランス事故は、ビルドに入った後になって初めて発見されるのです。
この章の目標は1つです。コンボ経路を手で描かずに自動ですべて列挙し、各経路を検証するワークフローを作ること。自然言語で書いたルールを仕様に変換し、仕様から経路を列挙し、列挙した経路をシミュレーションにかけます。その過程でAIがどこまでやってくれて、どこで嘘をつくのか、生のままお見せします。
コンボを表に書くとこうなります。「基本1の次に基本2、基本2の次に基本3」。行と列で見た目はきれいです。ところがこの表は嘘をつきます。表は直線を前提にしているからです。実際の戦闘では、プレイヤーは基本2から強攻撃へ抜け、強攻撃を回避でキャンセルし、回避の直後にまた基本1を押します。この分岐と循環は、表の行の間に隠れてしまいます。
だからコンボの本当の形は有向グラフです。アクションはノード、つながりはエッジ。各エッジには入力ウィンドウ(いつ入力を受け付けるか)と入力キーが付きます。ノードには持続フレームが、一部のノードにはボーナス条件(特定のノードを経由するとダメージ倍率が付く)が付きます。
戦士キャラクターの基本コンボ1セットをグラフに描くと、次のようになります。ノード6個、キャンセル分岐を含みます。
ホワイトボードと決定的に違う点が2つあります。第一に、各エッジに入力ウィンドウのフレーム範囲が明示されていることです。「強攻撃 6〜24f」は、基本2が始まってから6フレーム目から24フレーム目まで強攻撃の入力を受け付けるという意味です。第二に、点線で描いた回避→基本1の再進入エッジがあることです。チームメンバーBが会議室で即答できなかった、まさにあの経路です。グラフとして明示すれば、「ある/ない」がはっきりします。
このグラフを人が手で描くなら、ノード6個にエッジ7〜8本です。キャラクターが20人いて、キャラクターごとにコンボセットが3〜4個あれば、グラフは数百枚になります。手では追い切れません。だからグラフをテキスト仕様として書いておき、図と検証はそこから自動生成するのです。
上のグラフをYAML仕様に書き写します。核心はノード(nodes)、エッジ(edges)、ボーナス(bonuses)の3ブロックです。キャンセルルールもエッジの一種と見なします — 動作を断ち切って別のノードへ移るのも、結局はエッジだからです。
# warrior_basic_chain.yaml
character: warrior
combo_id: basic_chain
nodes:
- { id: basic_1, name: 基本1, duration_frames: 21 }
- { id: basic_2, name: 基本2, duration_frames: 24 }
- { id: basic_3, name: 基本3, duration_frames: 30 }
- { id: heavy, name: 強攻撃, duration_frames: 33 }
- { id: launch, name: 打ち上げ, duration_frames: 28 }
- { id: dodge, name: 回避, duration_frames: 18, cancels_recovery: true }
edges:
- { from: basic_1, to: basic_2, input: light, window: [10, 21] }
- { from: basic_2, to: basic_3, input: light, window: [12, 24] }
- { from: basic_2, to: heavy, input: heavy, window: [6, 24] }
- { from: heavy, to: launch, input: heavy, window: [10, 33] }
- { from: heavy, to: dodge, input: dodge, window: [0, 33], type: cancel }
- { from: basic_3, to: dodge, input: dodge, window: [0, 30], type: cancel }
- { from: dodge, to: basic_1, input: light, window: [8, 18] } # 再進入
bonuses:
- { on: basic_3, requires_path: [basic_1, basic_2], damage_multiplier: 1.5 }
この仕様は2種類の読者を同時に満足させます。人はwindow: [6, 24]を読んで「強攻撃は基本2の中盤から受け付けるのか」と理解し、機械は同じ行をパースしてグラフの描画と経路の列挙に使います。1つのソースから、人の理解と機械の検証が同時に得られるのです。
上のフレーム数値(21、24、[6, 24])は実測値ではなく、この章の説明のために著者が構成した例示値(未検証)です。実際のプロジェクトでは、これらの値はアニメーターが作ったモンタージュの長さと、ビルドのノーティファイのタイミングから得られます。仕様を最初に書く時点ではプランナーの意図値を入れ、ビルドが出た後にキャプチャして実測値に補正します — この補正ループは4.4で扱います。
チームメンバーBがホワイトボードに描いたルールを自然言語で渡し、仕様YAMLに変換させます。要約はせず、プロンプト全文とClaudeの生の出力、人による検証/拒否をそのまま掲載します。
次は戦士キャラクターのコンボルールだ。これをコンボ仕様YAMLに変換しろ。
ルール:
- 基本攻撃は1、2、3段とつながる。軽攻撃キー。
- 基本2の途中で強攻撃キーを押すと強攻撃へ抜けられる。
- 強攻撃の次に強攻撃をもう一度押すと敵を打ち上げる(打ち上げ)。
- 強攻撃はいつでも回避でキャンセル可能。
- 基本3も回避でキャンセル可能。
- 回避の直後には軽攻撃で基本1を再び始められる。
- 基本3は基本1と基本2を両方経由したときだけダメージが1.5倍。
仕様形式:
- nodes(id, name, duration_frames), edges(from, to, input, window, type),
bonuses(on, requires_path, damage_multiplier)
- キャンセルエッジはtype: cancelで表示。
- フレーム値は、私が与えていないものはお前が決めずTODOのまま空けておけ。
最後の行が核心です。フレームを与えていないなら、でっち上げるなと明示しています。v3標準の3番(数値の正直さ)をプロンプトのレベルで強制しているのです。
仕様に変換しました。フレーム値は提供されていないためTODOのまま残しました。
```yaml
character: warrior
combo_id: basic_chain
nodes:
- { id: basic_1, name: 基本1, duration_frames: TODO }
- { id: basic_2, name: 基本2, duration_frames: TODO }
- { id: basic_3, name: 基本3, duration_frames: TODO }
- { id: heavy, name: 強攻撃, duration_frames: TODO }
- { id: launch, name: 打ち上げ, duration_frames: TODO }
- { id: dodge, name: 回避, duration_frames: TODO }
edges:
- { from: basic_1, to: basic_2, input: light, window: TODO }
- { from: basic_2, to: basic_3, input: light, window: TODO }
- { from: basic_2, to: heavy, input: heavy, window: TODO }
- { from: heavy, to: launch, input: heavy, window: TODO }
- { from: heavy, to: dodge, input: dodge, window: TODO, type: cancel }
- { from: basic_3, to: dodge, input: dodge, window: TODO, type: cancel }
- { from: dodge, to: basic_1, input: light, window: TODO }
bonuses:
- { on: basic_3, requires_path: [basic_1, basic_2], damage_multiplier: 1.5 }
```
確認が必要な部分:
1. 「回避直後の基本1再開」をdodge -> basic_1エッジとして入れましたが、これが
回避の硬直中に入力を受け付けるのか、回避が完全に終わった後なのか不明確です。
2. 打ち上げ(launch)の次に続く空中コンボがあるかどうかルールにありません。
打ち上げがコンボの終端かどうか確認が必要です。
3. 回避をノードとして置くか、エッジ(キャンセル動作)としてだけ置くか曖昧です。ひとまず
ノードとして置きました — 回避自体が持続時間を持つアクションだと見なしました。
ここで評価してみましょう。まず良い点です。フレームをでっち上げず、すべてTODOのまま空けました。プロンプトの制約を守っています。そして最後に付けた3つの質問は、どれも本当に曖昧な箇所を突いています — とくに1番(再進入の入力タイミング)は、チームメンバーBが会議室で即答できなかった、まさにあの穴です。AIが人より先に穴を見つけたのです。
一方、曖昧な点もあります。3番で回避をノードにしたのは判断が分かれるところです。回避は「キャンセル動作」であると同時に「持続時間を持つアクション」でもあるので、どちらの扱いも正しいのです。AIが片方を選んだうえで「曖昧だ」と報告したのは正直ですが、これは設計上の決定なので、人が決めてあげる必要があります。
3つの質問に答え、一部を拒否します。
window: [8, 18]。cancels_recovery: true属性を追加して、「硬直を断ち切る動作」であることを明示します。→ 部分採用+属性追加。そして1つを拒否します。AIはdodge → basic_1エッジにtype: cancelを付けませんでしたが、これは回避の硬直を断ち切って進入するものなので、性質としてはキャンセルに当たります。しかしここでは「回避後の通常進入」と見なして、一般エッジのままにします — 硬直キャンセルと通常接続の区別は、このキャラクターではゲームの手触り上の差がないからです。人がドメイン判断でAIの分類を上書きした事例です。
良い。次を反映して最終仕様を出し直せ:
- dodgeにcancels_recovery: trueを追加。
- dodge -> basic_1エッジのwindowは[8, 18]。
- 残りのフレームは依然として私が与えていないのでTODO維持。ただし上のグラフ例示値
(basic_1=21, basic_2=24, basic_3=30, heavy=33, launch=28, dodge=18)を
使うから、その値で埋めろ。これは未検証の例示値だとコメントで明記しろ。
この再リクエストで得られた結果が、4.3.2のYAMLです。一度では完成しませんでした。プロンプト → 生の出力 → 検証/拒否 → 再リクエスト。このサイクルが仕様の信頼度を作ります。AIが曖昧な箇所に印を付け、人がドメイン知識で決定する — どちらか一方だけでは成り立ちません。
仕様がグラフなのですから、コンボ経路の列挙はグラフ探索の問題になります。開始ノードから出発して、終端ノード(またはフィニッシャー)まで到達するすべての経路を探す深さ優先探索(DFS)です。人はこれを頭の中ではできませんが、コードは一瞬でやってのけます。
著者のチームの隔離作業スペース95_BattleTFの中には、この列挙を担当する小さなスクリプトがあります。仕様YAMLを読み込んですべての経路を抽出し、各経路がルール上可能か(エッジが存在するか)を検証します。核心のロジックだけを示すと、次のとおりです。
# 95_BattleTF/enumerate_paths.py (抜粋)
import yaml
def load_graph(path):
spec = yaml.safe_load(open(path, encoding="utf-8"))
adj = {}
for e in spec["edges"]:
adj.setdefault(e["from"], []).append(e)
return spec, adj
def enumerate_paths(adj, start, max_depth=8):
results = []
def dfs(node, path, edges):
# 終端ノード(出るエッジなし)か深さの上限なら経路を確定
outs = adj.get(node, [])
if not outs or len(path) >= max_depth:
results.append((list(path), list(edges)))
return
for e in outs:
if e["to"] in path: # 循環防止: 1経路に同じノードは1回
results.append((list(path), list(edges)))
continue
dfs(e["to"], path + [e["to"]], edges + [e])
dfs(start, [start], [])
return results
basic_1から始めて回すと、手では到底展開しきれない経路があふれ出てきます。一部だけ見ると、次のとおりです。
| # | 経路 | 備考 |
|---|---|---|
| 1 | 基本1 → 基本2 → 基本3 | 正規の3段、フィニッシャーボーナス充足 |
| 2 | 基本1 → 基本2 → 強攻撃 → 打ち上げ | 分岐コンボ |
| 3 | 基本1 → 基本2 → 強攻撃 → 回避 → 基本1 → … | 循環への進入 |
| 4 | 基本1 → 基本2 → 基本3 → 回避 → 基本1 → … | フィニッシャー後のリセット |
3番と4番が重要です。回避の再進入エッジのせいで、コンボが循環します。人がホワイトボードで見落としていたのが、まさにこうした循環経路です。DFSに循環防止(同じノードは1経路につき1回)のガードを入れないと、列挙が無限ループに陥ります — これはコードを最初に回したとき、実際に一度止まってしまって気づいた罠です。グラフに循環があるなら、列挙器には必ずガードが必要です。
列挙段階の成果物は2つです。第一に、ルール上可能なすべての経路のリスト。第二に、ルール矛盾の検出です — 仕様にdodge → basic_1エッジがあるのに、肝心のdodgeノードの定義が抜けていれば、列挙器が「未定義のノードを指すエッジ」として捕まえます。仕様を手で書くときに最もよくあるミスが、このダングリング参照です。
経路のリストだけでは、「どの経路が強すぎるのか」は分かりません。各経路をDPSシミュレーターに入れる必要があります。著者のチームのsimulate_dpsがこの役割を担います — 経路(ノードのシーケンス)と各ノードのダメージ・フレーム、ボーナスルールを受け取り、総ダメージと総所要フレームを計算して、秒間ダメージ(DPS)を出します。
# 95_BattleTF/simulate_dps.py (抜粋、60fps想定)
def simulate(path_nodes, node_dmg, node_frames, bonuses):
total_dmg = 0
total_frames = 0
visited = []
for nid in path_nodes:
dmg = node_dmg.get(nid, 0)
# ボーナス: requires_pathを全て経由したら倍率を適用
for b in bonuses:
if b["on"] == nid and all(r in visited for r in b["requires_path"]):
dmg *= b["damage_multiplier"]
total_dmg += dmg
total_frames += node_frames[nid]
visited.append(nid)
seconds = total_frames / 60.0
return {"dmg": total_dmg, "frames": total_frames,
"dps": round(total_dmg / seconds, 1) if seconds else 0}
4.3.4の列挙結果をここへ丸ごと流し込むと、各経路のDPSが表になって出てきます。以下は、ノードのダメージに例示値(基本打撃100、強攻撃180、打ち上げ140 — いずれも未検証の架空値)を入れて回した結果です。
| 経路 | 総ダメージ | 総フレーム | DPS |
|---|---|---|---|
| 基本1→基本2→基本3(フィニッシャー×1.5) | 100+100+150 = 350 | 75 | 280.0 |
| 基本1→基本2→強攻撃→打ち上げ | 100+100+180+140 = 520 | 106 | 294.3 |
| 基本1→基本2→基本3→回避→基本1 | 350+0+100 = 450 | 144 | 187.5 |
この表が議論を変えます。「強攻撃の分岐のほうが正規の3段より強く見えないか?」という直感が、「強攻撃経路のDPSは294、正規の3段は280で、5%の優位」という数字に変わります。5%の優位が意図したものなら通過、そうでなければ強攻撃のフレームを伸ばしてDPSを下げます。ビルドが出る前に、仕様の段階でこの判断を行うのです。
ワークフロー全体を1枚で見ると、こうなります。
flowchart LR
A["自然言語のルール
(teammate_b ホワイトボード)"] --> B["プロンプト → AI"]
B --> C{"生の仕様
TODO・質問を含む"}
C -->|人による検証/拒否| D["確定仕様YAML
warrior_basic_chain.yaml"]
D --> E["enumerate_paths.py
全経路をDFSで列挙"]
E --> F["ルール矛盾の検出
ダングリングエッジ・無限循環"]
E --> G["simulate_dps.py
経路別のDPS計算"]
G --> H["バランス判断
経路間のDPS格差"]
F --> D
H -->|フレーム調整| D
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class E,F,G code;
class B,C ai;
class H human;
class A,D data;
仕様(D)が中心にあり、列挙(E)・検証(F)・シミュレーション(G)がそこから枝分かれします。矛盾が見つかったり、バランスがずれていたりすれば、仕様に戻って直します。ホワイトボードにはこのループがありませんでした — だからホワイトボードのコンボは、ビルドに入った後になって初めて間違いに気づいたのです。
ここまで、コンボグラフと経路の列挙を見てきました。キャンセルと入力キューは、このグラフを調整する2つのつまみです。向きは正反対です。
キャンセルはエッジを増やします。キャンセルルールを1つ追加するたびにグラフにエッジが増え、列挙される経路の数は掛け算で膨らみます。だからキャンセルは「寛容なほど良い」わけではありません。キャンセルを緩めるほど経路が爆発的に増え、その中に意図しない強い経路(前節の循環経路のような)が紛れ込む確率が上がります。格闘ゲームの伝統がキャンセルを厳格に保つ理由、アクションRPGが寛容にする理由がここにあります — ジャンルが「許容する経路数」を決めるのです。絶対的な正解のウィンドウ値はありません。
キャンセルを扱うときは、必ず分離して明示します。ひとまとめの「何でもキャンセル」にすると、列挙器がすべてのノード間にキャンセルエッジを作り、経路が制御不能に膨らみます。
| キャンセル類型 | 仕様での表現 | 経路への効果 |
|---|---|---|
| アクションキャンセル | 特定ノード → 特定ノード、type: cancel | 選択的な分岐だけ追加 |
| 回避キャンセル | 多数ノード → dodge、window: [0, dur] | ほぼすべてのノードからの脱出口 |
| ガードキャンセル | 多数ノード → guard | 防御への移行、通常は硬直中に限定 |
| キャンセル不可 | 出ていくcancelエッジなし | 発動したら最後まで(スーパーアーマー) |
入力キューは経路を狭めるのではなく、実際に踏めるようにします。キューがなければ、プレイヤーは各エッジの入力ウィンドウ(たとえば[12, 24])をフレーム単位で正確に合わせなければなりません。人間の反応速度ではほぼ不可能です。キューはウィンドウが開く前に押された入力をバッファーに保存しておき、ウィンドウが開いた瞬間に自動で発動させます — いわゆる先行入力の仕組みです。つまりキューはグラフの経路を変えるのではなく、グラフの上を人が歩けるように靴を履かせてくれるのです。
input_queue:
window_start_ratio: 0.5 # アクション進行率50%から次の入力をバッファリング
expire_frames: 10 # バッファされた入力の有効期間
priority: latest # 同時多重入力時は最後を優先
3つのパラメーターのバランスが核心です。window_start_ratioが小さすぎると、アクション序盤の入力までバッファリングされて、意図しない次の動作が飛び出します。expire_framesが短すぎるとキューの意味がなくなって再び正確さを要求することになり、長すぎるとずっと前に押した入力が遅れて発動し、「なぜ急に動いたんだ」という事故が起きます。推奨の出発値はexpire_framesが5〜15、window_start_ratioが0.5前後ですが — これはジャンルとキャラクターの重量感に応じて調整する出発点であって、正解ではありません。
運用上の注意を1つ。入力キューのパラメーターをキャラクターごとに全部変えてはいけません。グローバルな既定値を1つ置き、重量感が特別に異なるキャラクター(巨大ボス型など)だけoverrideします。キャラクター20人のキュー値を別々に管理すると、どれが意図した差でどれがミスなのか、区別がつかなくなります。
最後にもう1つ押さえておくことがあります。ここまでエッジを「ある/ない」だけで扱ってきましたが、エッジがあるとしても、そのノードから次のノードへ実際にどう移るかはまた別の決定です。コンボ間の接続方式には核心の分岐が3つあり、本書の深さの範囲外なのでコードでは扱いませんが、触れずにおくと仕様が半分しか描けていないことになります。
window: [10, 21])が、まさにこのノーティファイの表現です。ノーティファイを前倒しするとコンボは速くなり(打撃が終わる前に次へ)、遅らせると一撃一撃が重くなります。つまりウィンドウの開始値は単なる数字ではなく、「この打撃を最後まで見せるのか、次へ素早くつなぐのか」という感覚の決定なのです。この3つは、同じエッジを巡ってもゲームの手触りを正反対にします。仕様の段階ではエッジの存在とウィンドウだけを決め、接続方式(ブレンディングかフレームスキップか)はビルドの段階でアニメーターと一緒に決めるのが普通です。ただし仕様にtransition: blend / transition: skipのようなフィールドを1行だけ先に空けておけば、ビルド段階で「このエッジはどう移ると決めたんだっけ」と聞き直さずに済みます。接続方式は、コンボグラフの隠れた第3の軸です。
ここまでのすべての検証は、仕様の上で行われました。経路の列挙もDPSシミュレーションも矛盾の検出も、すべてYAMLを対象にしていました。ところが仕様のフレーム値はプランナーの意図値であって、ビルドの実測値ではありません。アニメーターが作ったモンタージュの実際の長さ、ビルドのノーティファイが実際に発火するフレーム、入力キューがエンジン上で実際に機能するウィンドウ — これらはビルドをキャプチャして測定しなければなりません。
ビルド映像から5つの信号(打撃の発生フレーム、硬直(後隙)、キャンセルウィンドウ、入力キュー、ヒットストップ)を自動抽出するのは実装難度が高く、現実的に最も信頼できるのはゲーム内テレメトリーです — ビルドの中に「このアクションがこのフレームでこの入力を受け付けた」というログを出させ、そのログを仕様と突き合わせます(キャプチャ方法の比較は4.4参照)。この突き合わせのループが4.4のテーマです。仕様上[12, 24]だったウィンドウがビルドで[14, 26]と測定されたら、仕様をビルドの実測値で補正するのです。
| ミス | なぜ危険か | 回避策 |
|---|---|---|
| コンボを直線の表で書く | 分岐・循環が行の間に隠れて漏れる | グラフ(ノード+エッジ)で仕様化し、手で描かない |
| キャンセルを「何でも」にひとまとめ | 列挙経路が爆発し、強い経路が紛れ込む | アクション・回避・ガードのキャンセルを分離して明示 |
| 列挙器に循環ガードがない | 回避の再進入で無限ループ | 1経路につきノード1回のガード |
| 仕様のフレームを実測値と勘違い | 意図値とビルド値は異なる | 意図値と明示し、ビルドキャプチャで補正(4.4) |
| 入力キューをキャラクターごとに別々に | 意図した差/ミスの区別が不能 | グローバル既定値+一部のみoverride |
| AIが埋めたフレームをそのまま信じる | でっち上げた数値が仕様に入る | 「与えていない値はTODO」とプロンプトで強制 |
手を動かしてそのまま回せる、最小限の手順です。Pythonとpyyamlさえあれば大丈夫です。
setup. 作業フォルダーを1つ作り、その中に仕様ファイルとスクリプト2つを置いてください。
combo-mini/
warrior_basic_chain.yaml # 4.3.2の仕様
enumerate_paths.py # 4.3.4のDFS列挙器
simulate_dps.py # 4.3.5のシミュレーター
pip install pyyamlの後、仕様ファイルには4.3.2のYAMLをそのまま貼り付けてください。
prompt. 自然言語のルールを仕様に変換する段階は、AIに任せましょう。4.3.3のプロンプトをそのまま使い、最後の制約を必ず含めてください。
フレーム値は、私が与えていないものはお前が決めずTODOのまま空けておけ。
キャンセルエッジはtype: cancelで表示し、曖昧な部分は質問として別に出せ。
この2行が、AIによる数値の捏造と恣意的な判断を防ぎます。出てきた仕様のTODOと質問リストは、人が埋めます。
verify. 仕様が完成したら、2回検証してみましょう。
python enumerate_paths.py warrior_basic_chain.yaml # 全経路 + 矛盾の出力
python simulate_dps.py warrior_basic_chain.yaml # 経路別のDPS表
列挙の出力では、(1)循環経路が無限に増えていないか、(2)未定義のノードを指すダングリングエッジがないかを確認してください。DPSの出力では、経路間の格差が意図した範囲内かを見てください。格差が大きければ、仕様のフレーム/ダメージを直してもう一度回してください。
ツールを別に作る時間がなければ、仕様の作成と経路の列挙を1回のAI対話で済ませましょう。自然言語のルールを渡し、「コンボ仕様YAMLに変換した後、開始ノードから可能なすべての経路を深さ優先で全部列挙し、循環は1回だけ回って打ち切ること。未定義のノードを指すエッジがあれば指摘すること」と、1つのプロンプトに詰め込んでください。AIが仕様化・列挙・矛盾検出を一度にやってくれます。DPSシミュレーションは、ノードのダメージを表で一緒に渡し、「各経路の総ダメージとフレームを計算して表にしてほしい」という後続リクエストで受け取ってください。精度は落ちますが、ホワイトボードよりはずっと遠くまで見通せます。核心は変わりません — コンボは頭の中で展開せず、列挙させて見るのです。
戦闘TF(タスクフォース)のビルド#234が上がってきたばかりです。新スキルskill_thunderに初めて触れるビルドです。仕様書にはヒットタイミング150msと書いてあります。入力を入れます。指先の感覚が言います。遅い。間違いなく遅い。隣の席のチームメンバーAを呼びます。「これ、ちょっと浮いて見えませんか?」チームメンバーAが二、三回打ってみます。「うーん……そんな気もしますね。」二人とも確信が持てません。仕様は150だと言うのに、指は200くらいだと言い張ります。どちらが正しいのか。指先と紙の戦いです。次のビルドでもまた誰かが「体感では大丈夫そうですけど」と言い、その一言でビルドが一つ、また流れていきます。
この章の目標は、その戦いを終わらせることです。指先が200だと言うなら、本当に200なのかを数字で見せること。そしてビルドが上がってくる前に、仕様だけを見て「このスキルはDPSが目標より30%高い」を先に知ること。200人が関わるAAA MMORPGの戦闘を初期から固めていた時代も、感覚と数字がずれたときの途方もなさは同じでした。変わったのは、いまはそのずれを数字で決着させる道具が手元にあるということだけです。
4.2・4.3が戦闘の仕様をどう書くかを扱ったとすれば、4.4はその仕様が意図どおりに動くかを扱います。検証には二つの軸があります。一つはビルドなしで計算だけで検証するシミュレーション、もう一つは実際のビルドから測定値を取り出すキャプチャー分析です。二つの軸が一つのサイクルに束ねられると、戦闘企画の検証の往復が日単位から時間単位へ縮みます。
結論を先に明かすと、この章の核心は、仕様に書かれた数字(150ms)とビルドで測定した数字(220ms)を並べて、その差を読むことです(4.4.5)。前半の節(シミュレーター・コンボ列挙)は、その対照を可能にするための準備段階として読んでください。
戦闘プランナーが新スキル一つを検証しようとすると、何が起きるでしょうか。
プランナーが仕様を書きます。プログラマーがデータを入れ、アーティストがモーション・エフェクトを付け、ビルドが回り、QAが一巡し、そこでようやくプランナーが手で触って確かめます。早くて2日、普通は3〜4日。サイクルの最後で「DPSが高すぎる」が見つかると、その発見は最初へ戻れという命令になります。3〜4日がもう一回。
シミュレーションは、このサイクルの最初の段階で答えをくれる道具です。仕様だけで計算してみます。答えが悪ければ仕様を直して再計算。ビルドという高価な段階に入る前に、仕様そのものが一度ふるいにかけられます。模型の車をまず風洞に入れてみるのと同じです。本物の車を道路に出す前に、疑わしい設計は机の上で脱落します。
もちろん、風洞が道路を100%予測するわけではありません。だから二つ目の軸であるキャプチャー分析が必要です。シミュレーションが理想的な答えだとすれば、キャプチャーは実際にビルドで起きた答えです。二つを並べて差を読むこと — それがこの章のすべてです。
抽象的な擬似コードでは何も検証されません。だから最初から動くコードを作ります。以下は、著者が戦闘TFで使っているsimulate_dps.pyの核心の骨格を、会社のデータを取り除いて本書に載せるために再構成したものです。依存関係なし、Python標準ライブラリだけで動きます(ファイル全体は「やってみよう」を参照してください)。
入力は単純です。スキル一つはdamage・cast_sec(詠唱占有時間)・cooldown_sec・resource_costを、キャラクターはリソース総量・秒間回復量・スキルリスト・優先順位ローテーションを持つdataclassです。その上で回る本体は、「その時点で使えるスキルのうち優先順位が最も高いものを使う」という単純な貪欲法(greedy)のルールだけです。実際のプレイヤーより賢くも愚かでもない、理想的上限を取るのが目的です。背骨になる部分だけを抜粋するとこうなります。
# 0.05秒ティックでタイムラインを作る。詠唱中でなければ優先順位順に最初の使用可能スキルを使う。
while t < duration_sec:
resource = min(char.max_resource, resource + char.resource_regen * tick)
for name in cooldowns:
cooldowns[name] = max(0.0, cooldowns[name] - tick)
if t >= busy_until: # 詠唱モーションが終わっていなければ待機
for name in char.rotation: # 優先順位順
s = skill_by_name[name]
if cooldowns[name] <= 0 and resource >= s.resource_cost:
total_damage += s.damage
resource -= s.resource_cost
cooldowns[name] = s.cooldown_sec
busy_until = t + s.cast_sec # この時刻まで次のスキル不可
break
t += tick
# …(dataclass定義・warrior入力・出力ループは「やってみよう」の全体コード参照)
warriorにskill_thunder(ダメージ420・詠唱0.9s・クールタイム(クールダウン)6s)を第1優先に、skill_dash・basic_1を後ろに置いて20秒回した結果です(python simulate_dps.py)。
平均DPS: 261.0
t= 0.0s skill_thunder リソース=60
t= 0.9s skill_dash リソース=47
t= 1.3s basic_1 リソース=50
t= 1.6s basic_1 リソース=53
t= 1.9s basic_1 リソース=55
...
この値に何の意味があるのか。ビルドなしで、1秒以内に、warriorの理想的なDPS上限が約261だという事実が分かる、ということです。目標DPSが180だったなら、この仕様は+45%で過剰です。ビルドを待つ必要はなく、いまdamageやcooldown_secをいじればいいのです。
限界も正直に書いておきます。このシミュレーターは、プレイヤーの入力ミス、移動・回避による空白、敵の妨害を反映しません。そのため測定値は常に実際のビルドより高く出ます。これはバグではなく、上限というシミュレーターの定義そのものです。実測との差は、4.4.5でキャプチャーによって埋めます。
AI活用ノート。 上の骨格は著者が組んだものですが、新しいリソースモデル(例:怒りゲージがダメージを受けると溜まる構造)を付けるときは、Claudeに「このsimulate_dpsに被弾時怒り+5のルールを追加して。tickループの中で、既存のリソース回復とは別の変数で」のように、既存コードを引用しながら依頼します。白紙からシミュレーターを丸ごと生成させると、検証不可能なコードが出てきます。背骨は人が握り、AIには枝を伸ばさせます。
DPSという数字一つでは足りません。「どのコンボが意図されたメインコンボなのか」を検証するには、可能なすべての経路を広げて見る必要があります。手でツリーを描くと、ノード7〜8個ですでに頭がパンクします。経路を漏れなく広げるのは、機械の方が人より圧倒的に得意です — ただし、人が結果をたどり直せる形で出力させる必要があります。
次は、コンボグラフを受け取ってすべての経路を列挙し、DPSでソートするコードです。combo_graphは「どのアクションの次にどのアクションへキャンセルできるか」を隣接リストで書いたもので、4.3のステートマシン仕様からそのまま抽出されます。核心は、行き止まりまで再帰で展開するall_pathsジェネレーターです。
# enumerate_combos.py — コンボグラフのすべての経路を展開しDPSでソート
combo_graph = {"start": ["A"], "A": ["B", "D"], "B": ["C", "E"], "D": ["C"], "C": [], "E": []}
action_stats = { # (ダメージ, 所要時間 秒)
"A": (300, 0.8), "B": (450, 1.0), "C": (450, 1.2), "D": (600, 1.4), "E": (200, 0.6),
}
def all_paths(node="start", path=None):
path = (path or [])
nexts = combo_graph.get(node, [])
if not nexts: # 行き止まり = 完成したコンボ
yield [n for n in path if n in action_stats]
return
for nxt in nexts:
yield from all_paths(nxt, path + [nxt])
results = []
for p in all_paths():
dmg = sum(action_stats[a][0] for a in p)
dur = sum(action_stats[a][1] for a in p)
results.append((p, dmg, round(dur, 1), round(dmg / dur, 1)))
for p, dmg, dur, dps in sorted(results, key=lambda r: -r[3]):
print(f"{' → '.join(p):<18} {dmg:>5} dmg {dur:>4}s DPS {dps}")
実行結果です。
A → D → C 1350 3.4s DPS 397.1
A → B → C 1200 3.0s DPS 400.0
A → B → E 950 2.4s DPS 395.8
ここでプランナーが読み取るべきシグナルは、単純な1位ではありません。三つの経路のDPSが396〜400でほぼ張り付いていること — これは「どのコンボを使っても効率が似ていて、メインコンボのアイデンティティがない」というシグナルです。意図が「A→D→Cがハイリスク・ハイリターンのメインであるべき」だったなら、Dのダメージを上げるか所要時間を縮めて、DPSを一段浮かせる必要があります。仕様に戻る番です。
この自動列挙が手計算を置き換える場面では、コンボノードが20個に増えても、人はソート済みの表を読むだけで済みます。
いよいよ二つ目の軸です。ビルドの中で実際に何が起きたかを測定する段階です。よく「ビルド映像をAIが見て自動分析」を思い浮かべますが、ここでは正直に分岐を分けます。測定値を得る道は三つあり、三つのコスト・正確度は大きく異なります。
A案(映像のピクセル分析)は魅力的に聞こえます。入力表示、キャラクターのモーション変化、エフェクトの最初のフレーム、サウンド波形、ダメージ数字UI — 五つのシグナルを画面から自動抽出するという絵です。しかし実際に作ってみると、フレーム圧縮ノイズ、UIの遮り、モーションブラーのせいで±1〜2フレームの誤差が基本として乗ります。60fpsでは1フレームが約16.7msです。ヒットタイミングをms単位で問う検証で、±33msのノイズは致命的です。実装難易度は非常に高く、正確度はその努力に見合いません。
だから現実の答えはC案、ゲーム内テレメトリー(telemetry)ログです。エンジンは、入力の時刻、アニメーション通知(ノーティファイ)の発生時刻、VFXスポーンの時刻、ダメージ適用の時刻を、すでに内部で正確に知っています。その時刻をピクセルから推論するのではなく、1行のログとして記録させればいいのです。ピクセルから100msを復元する代わりに、エンジンが知っている100msをそのまま書き取ります。
// 戦闘アクション処理コードに1行追加 (UE C++ 疑似例)
// 入力受信 / ダメージ適用の時点で同じロガーを呼び出す
CombatTelemetry::Log("input", SkillName, GetWorld()->GetTimeSeconds());
CombatTelemetry::Log("hit", SkillName, GetWorld()->GetTimeSeconds());
ロガーは1行ずつJSON Linesで吐き出します。
{"event":"input","skill":"skill_thunder","t":12.340}
{"event":"hit", "skill":"skill_thunder","t":12.560}
{"event":"input","skill":"basic_3","t":14.100}
{"event":"hit", "skill":"basic_3","t":14.166}
inputとhitの時刻差が、そのまま測定ヒットタイミングです。12.560 − 12.340 = 0.220秒 = 220ms。ピクセル分析の±33msではなく、エンジン時刻そのままの値です。このログを取り出してきて仕様と対照するのが、次の節です。
いよいよ二つの軸を一か所に集めます。仕様は150msを約束し、テレメトリーは220msを測定しました。+70ms。指先が正しかったのです。この差の原因を絞り込む過程を、AIと一緒に最後まで追いかけます。要約せず、AIが最初に見当違いをする場面までそのまま載せます。
仕様はヒットタイミング150msなのに、測定は220msです。下のログを貼り、人が実際に打ちそうな長さで尋ねます。長々しい形式より、意図が明確な短い依頼の方が優れています — 検証方法まで書かせる一行と、断定禁止の一行が、このプロンプトの背骨です。
skill_thunderのヒットタイミングが仕様150msのところ、測定220msで+70ms超過している。下のテレメトリーから可能な原因を分け、それぞれどのログで確定/排除できるかまで書くこと。断定はせず、確実でなければ「未確認」と表記。 // 「検証方法まで」・「断定禁止」の二つが核心の意図です。残りの形式は省略しても結果は同じです。
{"event":"input","skill":"skill_thunder","t":12.340} {"event":"anim_start","skill":"skill_thunder","clip":"cast_thunder","t":12.340} {"event":"anim_notify","skill":"skill_thunder","notify":"HitWindow","t":12.560} {"event":"hit","skill":"skill_thunder","t":12.560}仕様上、cast_thunderのHitWindowノーティファイは0.150s地点。
+70ms超過の原因として、次の四つを推定します。
- ネットワークレイテンシー。 入力がサーバーを往復する過程で70msが追加された可能性があります。マルチプレイのビルドであればRTTを確認してください。
- フレームレート低下。 ビルドが30fpsで動いていると1フレームが33msのため、ノーティファイのタイミングがフレーム境界に丸められて遅延が蓄積します。
- キャストモーションの長さの変更。 cast_thunderクリップが仕様より長くなり、HitWindowノーティファイが後ろへずれた可能性があります。
- 入力バッファの遅延。 入力キューが1ティック遅れて処理された可能性があります。
最も可能性が高い原因は1番のネットワークレイテンシーです。70msは典型的なRTTの範囲です。
ここで手を止めて、ログを読み直します。AIの第1候補の推定(ネットワーク)は、このログだけですでに排除できます。inputとanim_startが正確に同じ時刻12.340に記録されています。入力が入った瞬間にモーションが即座に始まったということであり、ネットワーク往復が割り込む隙がありません。1番は誤りです。
2番(フレームレート)も弱い推定です。30fpsなら33ms単位のばらつきが見えるはずですが、input→hitは正確に0.220で揃っています。これはフレーム境界の丸めではなく、クリップ内の固定位置の匂いです。
決定的な手がかりは別にあります。anim_notifyの時刻が、anim_start基準で+0.220です。仕様ではHitWindowはクリップ開始から0.150の位置にあるはずなのに、実際のクリップでは0.220の位置に固定されています。つまり、クリップ自体が仕様と違う形で作られたか、ノーティファイの位置が0.150から0.220へ移されたかです。原因はネットワークでもフレームレートでもなく、3番のモーション/ノーティファイのデータです。AIは正解の候補を四つの中に含めてはいましたが、順位を逆に付けていました。
排除の根拠を人が手で埋めて、もう一度投げます。長く説明する必要はなく、排除したものと残った事実だけを短く釘付けにします。
ネットワーク・フレームレートは排除。(根拠:input=anim_startが同一時刻のため入力→モーションの遅延なし/input→hitが0.220でフレームノイズなく一定。)残った事実:anim_notifyがanim_start +0.220、仕様は+0.150。この事実だけで原因を絞り、プログラマー・アニメーターに何を依頼するかを行動単位で。 // 核心は「行動単位で」 — 推定ではなく、誰に何を依頼するかで終わらせます。
提示された根拠から、原因はアニメーションクリップcast_thunderのHitWindowノーティファイが0.150sではなく0.220sに配置されていることへ絞り込まれます。残る分岐は二つです。
- (ア)ノーティファイは0.150に合っているが、クリップ全体が遅くなった — 再生速度(play rate)が仕様より低いか、クリップ長が伸びていれば、0.150の地点が実時間では0.220に引き伸ばされます。
- (イ)ノーティファイの位置自体が0.220へ移された — クリップ長はそのままで、マーカーだけが移動。
二つの分岐を分ける確認: 1. アニメーターへ:cast_thunderのHitWindowノーティファイのフレーム番号とクリップ総フレーム数、そして仕様作成時点の値と比較した変更履歴を依頼。マーカーが移動していれば(イ)。 2. プログラマーへ:skill_thunderのmontage play rateが1.0か、キャラクターの攻撃速度ステータスがこのクリップに乗算されるかを確認。play rateが0.68(=0.150/0.220)なら(ア)。 3. anim_start→anim_notifyの間隔を、別のキャラクター攻撃速度でもう一度測定。攻撃速度によって間隔が変われば(ア)、不変なら(イ)。
推奨される対応:(イ)と確認されたら、ノーティファイを0.150へ戻すか仕様を0.220へ更新(意図された方に合わせる)。(ア)と確認されたら、play rate適用ロジックを点検。
今回の出力は採用に値します。推定を断定せず(ア)/(イ)の二つの分岐に分け、各分岐をデータで判別する方法と誰に何を依頼するかが行動単位で書かれています。特に3番の確認(攻撃速度を変えて再測定)は、人が見落としやすい決定的な分岐です。このレポートをそのまま会議に持ち込めば、会議は「原因は何だろう」を議論する場ではなく、「(ア)か(イ)かを30分以内に確定して対応を選ぶ」場になります。
このワークド・トランスクリプトが見せている核心は、AIが最初から正解をくれるわけではないという事実です。1回目の出力は、ネットワークを第1候補に挙げる見当違いでした。ログを読んで候補を排除する人の検証が挟まったとき、初めて分析は正解へ収束しました。AIは候補を広く並べ、人が絞り込む。この分業が4.4全体の方法論です。
シミュレーション(4.4.2〜4.4.3)とキャプチャー分析(4.4.4〜4.4.5)が別々に回っているだけでは、価値は半分です。一つに束ねられたとき、次のループが生まれます。
flowchart TD
A["仕様作成
(プランナー、4.2・4.3の様式)"] --> B["シミュレーション
simulate_dps・コンボ列挙
(自動、1秒)"]
B -->|"DPS・コンボの異常"| A
B -->|"仕様パス"| C["ビルド
(プログラマー・アーティスト、数日)"]
C --> D["テレメトリーログ収集
(input・hit・anim_notify)"]
D --> E["仕様 vs 測定の自動対照
(150ms vs 220msのような差分検出)"]
E -->|"しきい値超過の項目"| F["AI原因推定レポート
(候補を広げる → 人が絞る)"]
F --> G["プランナー検討 → 対応決定
(仕様更新 vs ビルド修正)"]
G --> A
E -->|"全項目一致"| H["次のスキルへ"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class B,E code;
class F ai;
class A,G human;
class D data;
class H pass;
シミュレーションがビルドの前に答えをくれるため、ビルドに入る仕様はすでに一度ふるいにかけられたものです。そのため、ビルド後に発見される問題は「仕様が間違っていた」ではなく「仕様と実装がずれた」へと性格が絞られます。4.4.5の+70msがまさにその後者でした — 仕様の150は合理的で、実装が220へずれていただけです。この区別が、会議から責任の所在をめぐる争いをなくします。
ただし、このループがすべての戦闘コンテンツをカバーするわけではありません。メインボスのシグネチャー演出のように、感触がそのままコンテンツである領域は、DPSの数字には還元されません。シミュレーションは数値ベースのコンテンツに強く、演出は依然として人の目で見る映像チェックが答えです。ループは数値の川を回り、演出の川は別に流れます。
著者が運用したあるMMORPG(refgame系の操作感を目標にしたプロジェクトA)戦闘TFの、6か月の測定です。以下の数値はTF内部の記録から抜き出した実測値であり、ビルド数・時間はサイクル単位に丸めた値であることを明記しておきます(正確な分単位ではなく、サイクル・半日単位の測定です)。
| 項目 | 導入前 | 導入後 |
|---|---|---|
| 新スキルの検証サイクル | 平均3〜4ビルド | 平均1〜2ビルド |
| 100スキルのビルド検証 | 半日(手動) | 30分(テレメトリー自動対照) |
| バランス会議 | 2時間(主観の議論) | 30分(データに基づく) |
| ビルド直前に見つかる欠陥 | 平均5〜8件/ビルド | 平均1〜2件/ビルド |
数字より重要な変化は、会議の性格です。導入前の会議の半分は、「このスキルは強すぎる」対「いや、適切だ」でした。指先と指先の戦いです。導入後、その場は「測定DPSが目標+12%、測定ヒットタイミングが仕様+70ms。モーションを短縮するか、ダメージを10%下げるか」へ移りました。何が問題かを争っていた時間が、どう直すかを決める時間に変わったのです。
この転換のコストも正直に書いておきます。テレメトリーロガーを戦闘コードの全域に仕込む初期作業に約1〜2週間、仕様スキーマをキャラクター・スキルの全部で統一するのに、さらに四半期一つ分かかりました。最初の四半期は、シミュレーションとテレメトリーのどちらか一方だけ動けば十分だと判断していました。二つが一つのループとして噛み合ったのは、二つ目の四半期からです。
繰り返されるのは五つです。
第一に、シミュレーション値を絶対的に信頼することです。simulate_dpsの261は上限であって、実測ではありません。テレメトリーと比較しなければ、常に過大評価になります。
第二に、測定をしないことです。仕様だけシミュレーションして、ビルドをテレメトリーで見なければ、4.4.5のような+70msが静かに蓄積します。テレメトリーのロギングはオプションではなく、戦闘コードの基本設備です。
第三に、レポートを会議に持ち込まないことです。自動レポートが上がっていても、会議のアジェンダになければ誰も見ません。「今回のビルドのテレメトリー対照表」をアジェンダの固定項目として入れます。
第四に、AIの推定を検証なしに採用することです。4.4.5で見たとおり、AIの1回目の推定は見当違いでした。AIは候補を広げる道具であって、結論を下す道具ではありません。ログで候補を排除する人の一段階を、決して飛ばさないことです。
第五に、スキルごとに仕様の構造が違うことです。cast_secをあるスキルではcast_time、別のスキルではcastMsと書いていると、シミュレーターも対照スクリプトも毎回壊れます。共通の仕様スキーマ一つを、すべてのスキルに強制します — これが4.4のツール全体が回る前提です。
最初の四半期で五つすべてを押さえる必要はありません。一つ二つ定着するだけでも、サイクルは目に見えて短くなります。残りはループを回しながら自然に埋まっていきます。
4.1〜4.4では、戦闘企画の座標・Look & Feel・コンボ・シミュレーションを順に扱ってきました。4.1は戦闘プランナーが何を測定可能な対象として見るか、4.2はヒットタイミング・ヒットストップ・エフェクト同期をどう測定・調整するか、4.3はコンボ・キャンセル・入力キューをステートマシンとしてどう記述するか、そして4.4は、そのすべての仕様が意図どおりに動くかを、ビルドなしで/ビルドの中でどう検証するかを扱いました。
第4部を終えた戦闘プランナーの一週間は、こう変わります。月曜、新スキルの仕様にsimulate_dpsの自動検証が付きます。火曜、コンボ経路の自動列挙でメインコンボのアイデンティティを点検します。水曜、ビルドが上がるとテレメトリー対照表が自動で上がってきます。木曜、データに基づく議論30分。金曜、次サイクルの仕様修正。ビルドサイクルが3〜4回から1〜2回に減り、会議は半分以下に短くなります。打撃感という抽象が、測定可能な220msへ移っていきます。
そして、ビルド#234のあの場面 — 「これ、ちょっと浮いて見えませんか?」という問いに、いまはテレメトリーログが「220msです」と代わりに答えてくれます。指先と紙の戦いは終わりました。
次の第5部はナラティブ企画です。2.3で紹介したNarrativeDocs Layer 0〜4構造の、本格的な適用事例へ進みます。
setup.
1. 以下のsimulate_dps.py全体をそのまま作成してください。依存関係なし、python simulate_dps.pyで即座に実行でき、4.4.2の「평균 DPS: 261.0」(平均DPS: 261.0)が再現されます。
# simulate_dps.py — ビルドなしで仕様だけからDPSを計算する
from dataclasses import dataclass
@dataclass
class Skill:
name: str
damage: float # 1回の打撃ダメージ
cast_sec: float # 詠唱(モーション占有)時間 (秒)
cooldown_sec: float # 再使用待機時間 (秒)
resource_cost: float # リソース消費 (MP/気力)
@dataclass
class Character:
name: str
max_resource: float
resource_regen: float # 秒間リソース回復
skills: list # list[Skill]
rotation: list # 優先順位の順序 (スキル名)
def simulate_dps(char: Character, duration_sec: float, tick=0.05):
cooldowns = {s.name: 0.0 for s in char.skills} # 残りクールダウン
skill_by_name = {s.name: s for s in char.skills}
resource = char.max_resource
total_damage = 0.0
busy_until = 0.0 # 詠唱モーションが終わる時刻
log = []
t = 0.0
while t < duration_sec:
resource = min(char.max_resource, resource + char.resource_regen * tick)
for name in cooldowns:
cooldowns[name] = max(0.0, cooldowns[name] - tick)
if t >= busy_until: # 詠唱中でなければ次のスキルを選択
for name in char.rotation: # 優先順位順
s = skill_by_name[name]
if cooldowns[name] <= 0 and resource >= s.resource_cost:
total_damage += s.damage
resource -= s.resource_cost
cooldowns[name] = s.cooldown_sec
busy_until = t + s.cast_sec
log.append((round(t, 2), name, resource))
break
t += tick
return total_damage / duration_sec, log
if __name__ == "__main__":
warrior = Character(
name="warrior", max_resource=100, resource_regen=8,
skills=[
Skill("skill_thunder", damage=420, cast_sec=0.9, cooldown_sec=6, resource_cost=40),
Skill("skill_dash", damage=180, cast_sec=0.4, cooldown_sec=3, resource_cost=20),
Skill("basic_1", damage=60, cast_sec=0.3, cooldown_sec=0, resource_cost=0),
],
rotation=["skill_thunder", "skill_dash", "basic_1"],
)
dps, log = simulate_dps(warrior, duration_sec=20)
print(f"平均DPS: {dps:.1f}")
for t, name, res in log[:8]:
print(f" t={t:>5}s {name:<14} リソース={res:.0f}")
GetTimeSecondsなど)をそのまま記録するのが核心です。prompt. ビルドのテレメトリーから仕様とずれた項目を一つ選び、4.4.5の様式でAIに質問してみましょう — ログの抜粋を貼り、「推測を断定せず、原因ごとの検証方法まで。確実でないものは未確認と表記」を必ず含めてください。
verify. AIの1回目の出力をそのまま採用しないでください。ログを直接読んで排除できる候補を手で消したうえで(4.4.5のネットワーク・フレームレート排除のように)、根拠を付けて再依頼してください。最終レポートが「原因推定+誰に何を依頼するか」の行動単位で終わったら、会議に持ち込みましょう。
一人ミニ版。 テレメトリーロガーの全域設置が負担なら、検証したいスキル一つだけにinput・hitの2行を記録してください。simulate_dpsも、そのスキル一つのDPSだけを見ます。ツール全体を敷こうとせず、最も疑わしいスキル一つでループを一周回してから拡張しましょう。
会議室に入ると、ホワイトボードにキャラクター名がひとつ、赤い丸で囲まれていました。キム某というNPCでした。あるプランナーのサイドクエストでは、彼は「主人公を幼いころに引き取って育てた養父」であり、別のプランナーのメインクエスト チャプター3では「主人公を裏切って去った昔の仲間」でした。どちらの文書も1か月前に承認され、どちらもビルドに入っていました。ボイス収録の見積もりまで取った状態でした。
誰の落ち度でもありませんでした。どちらのプランナーも世界観の文書を読み、キャラクター設定を参照していました。問題は、同じキャラクターの設定が3つの異なるファイルに散らばっていて、そのうちどれが「本物」なのか誰も断言できなかったことにありました。世界観の文書はひとかたまりの70ページのWordファイルで、検索するとキム某は11か所に登場しました。どの行が決定で、どの行がメモなのか、区別がありませんでした。
その日の会議が終わって決めたのが、NarrativeDocsを五つの層に分割することでした。本章は、その五つの層の話です。
分野別のLayer分解をナラティブから先に始めるのには理由があります。
ナラティブはもっとも抽象的です。世界観、感情、トーンといったものは数字に落ちません。アートやシステムのように「スプライト数」「ダメージ係数」といった明確な単位がありません。これほど抽象的な分野がLayerにきれいに分解できるなら、より具体的な他の分野は自然と同じパターンで解けます。難しいところで先に通用するかを試すわけです。
また、ナラティブはインターフェースがもっとも多い分野です。キャラクターはアートと接し、クエストはコンテンツ・レベルと接し、セリフはUX・ローカライズと接し、報酬はシステムと接します。分野の間をもっとも多く横断する場所なので、Layer統合の価値がすぐに表れます。
最後に、自然言語の成果物の比重がもっとも高い分野です。そのため、AIの支援がもっとも大きく効く分野でもあります。ただし、すべてのゲームがナラティブ中心というわけではありません。カジュアル・アーケードのジャンルなら、本章の深さは過剰かもしれません。それでも「ひとかたまりの文書をLayerに分割し、インターフェースを狭くする」という骨格自体は、どの分野にもそのまま持ち込めます。
NarrativeDocsを五つの層に分解した形は次のとおりです。ビジョン(L0)が上にあり、ビルド・QA(L4)が下にあって、層の間をつなぐ通路は意図的に狭くしてあります。
フォルダーに落とすと次の形になります。各ファイル名は本書で抽象化した名前ではなく、実際にそのフォルダーに存在するファイル名です。
NarrativeDocs/
├── Layer0_Vision/
│ ├── world_premise.md (世界観の前提 — 不変)
│ ├── narrative_pillar.md (感情の柱3つ)
│ └── tone_manifesto.md (トーン・禁止語彙リスト)
├── Layer1_System/
│ ├── faction_system.md
│ ├── reputation_model.md
│ ├── dialogue_branching_rule.md
│ └── lore_consistency_rule.md
├── Layer2_Content/
│ ├── main_quest/ (チャプター単位)
│ ├── side_quest/
│ ├── character_bible/
│ └── lore_codex/
├── Layer3_Data/
│ ├── quest_table.xlsx
│ ├── npc_table.xlsx
│ ├── dialogue_id_table.xlsx
│ └── reward_table.xlsx
└── Layer4_Build_QA/
├── narrative_qa_checklist.md
├── voice_review_log.md
└── localization_status.md
五つの層を一人で抱え込まない、という点が重要です。層ごとに主担当が異なり、隣接する層の間の通路だけを標準化します。上の図の赤い矢印がその通路です。とくにL2とL3の間の通路はわざともっとも狭く(太い赤の矢印で)描いてありますが、その理由は後で見ます。
引き出しのたとえで言えば、五段の引き出しです。1段目には絶対に動かさない世界観の1行、2段目にはルールブック、3段目には本文、4段目にはシート、5段目にはレビューのログ。段の間の通路は狭く、通路の上には変更通知のベルが付いています。
L0は変わりません。変わるなら、それはゲームのアイデンティティが変わるということです。だから分量が小さくなければなりません。小さいからこそ変わらないのです。
| 文書 | 分量 |
|---|---|
| world_premise.md | A4 1.5枚 |
| narrative_pillar.md | A4 1枚(感情3つ) |
| tone_manifesto.md | A4 2枚(トーン+禁止語彙リスト) |
合わせて約4.5枚。これがL0の重さです。重くなると変更が怖くなり、怖くなると他の層がL0を迂回し始めます。迂回が始まると、L0は死んだ文書になります。
narrative_pillar.mdの実際の骨格はこんな形です(内容は抽象化しています)。
---
title: ナラティブ感情の柱
layer: L0
status: locked
last_updated: 2026-05-18
---
## 1. 失ったものへの郷愁
- プレイヤーは毎チャプターの終わりに1つを失う。
- 失ったものは二度と戻らない (回想でのみ)。
## 2. 義務と自由の葛藤
- すべての主要NPCは2つの義務を負う。
- プレイヤーの選択は片方の義務しか生かせない。
## 3. 小さな手助けの重み
- 大きな英雄的行為より、小さな親切のほうが大きな結果を生む。
この3行の柱が、その下の数百ページの方向を決めます。status: lockedは単なるラベルではありません。L4の自動チェックのひとつがこのラベルを読み、lockedの文書がPRで修正されると、リードナラティブの承認なしにはマージがブロックされるように仕掛けてあります。
L1は変更可能ですが、コストが大きい層です。ルールブックだからです。ルールブックの1行が変わると、そのルールに従うすべてのコンテンツが影響を受けます。
faction_system.mdの骨格は次のとおりです。
---
title: 勢力システム
layer: L1
atoms:
- faction_relation_matrix
- faction_membership_rule
- faction_quest_eligibility
---
## 1. 勢力の概念
N個の勢力。各勢力は (理念、資源、領土) で定義される。
## 2. 勢力間の関係
- relation_matrix.json (-3 敵対 ~ +3 同盟)
- 関係変化トリガー: メインクエストの決定、評判のしきい値
## 3. プレイヤー所属ルール
- 同時所属は最大2つ (敵対関係は同時不可)
- 脱退ペナルティ: 評判 -2、同盟勢力 -1
frontmatterのatoms:リストに注目してください。この3つのatom名は単なるメモではなく、第7部のオントロジーと第11部の関係図が追跡する識別子です。あるクエストがfaction_quest_eligibilityを参照していれば、このルールが変わったときに、そのクエストが影響リストに自動で載ります。L1はゲームコードにもっとも近いナラティブ成果物なので、システムプランナーとペアを組んで作業します。
L2はもっとも厚い層です。メインクエスト、サイドクエスト、キャラクターバイブル、ロア事典がすべてここに住んでいます。先ほど会議室で衝突した「養父vs裏切った仲間」のキム某の本当の設定も、いまはcharacter_bible/内のひとつのファイルとしてだけ存在します。そのファイルが唯一の信頼できる情報源(single source of truth)であり、クエストはそこを参照するだけです。
メインクエストのフォルダーは次の形です。
main_quest/
├── chapter_01_awakening/
│ ├── 00_chapter_overview.md
│ ├── 01_quest_a_call_to_arms.md
│ ├── 02_quest_b_first_choice.md
│ └── ...
├── chapter_02_road/
│ └── ...
└── _TEMPLATES/
└── quest_template.md
各クエストファイルはatomの標準形式に従います。
---
title: 武器を取るとき
layer: L2
type: main_quest
atoms:
- quest_chapter_01_awakening_a
related:
affects: [reputation_model, faction_relation_matrix]
derives_from: [narrative_pillar, world_premise]
requires: [character_kim, faction_alpha]
part_of: chapter_01_awakening
---
## 進行段階
1. ...
## 分岐
- A案選択時: ...
- B案選択時: ...
## 報酬 (L3参照)
- reward_table.xlsx → quest_001 行
核心はrelated:ブロックです。requires: [character_kim]と書いた瞬間、このクエストはキム某の設定をcharacter_bibleから取得すると宣言したことになり、もう自分のファイルの中でキム某を新たに定義することはありません。ナラティブの本文はL2に、数値の報酬はL3のシートに置きます。両方がひとつのファイルにあると、シートを1行直すたびに本文に触れることになり、そうなると翻訳キーがずれます。
L3はシートとIDです。自然言語の文は1行も入りません。
quest_table.xlsx
| quest_id | chapter | type | unlock_level | reward_xp | reward_gold | dialogue_set_id |
|----------|---------|------|--------------|-----------|-------------|-----------------|
| q_001 | ch01 | main | 1 | 500 | 100 | ds_001 |
| q_002 | ch01 | main | 2 | 800 | 150 | ds_002 |
セリフでさえIDでのみ参照します。本文はdialogue_id_table.xlsxに別途あり、翻訳キーと1:1でマッピングされます。L2とL3をつなぐ通路は、quest_idのたった1列で十分です。前の図でこの通路だけが太い赤の矢印だった理由がここにあります。インターフェースを1列に狭めることが、Layer分離の核心です。通路が広いと両側が互いを知りすぎてしまい、片方を直すときにもう片方がつられて壊れます。
L4はレビューと出荷です。新しいコンテンツが入ってくるたびに、自動・手動のチェックが作動します。自動チェックはスクリプトで回します。下の4つは、実際にCIに掛かっているlintです。
| チェック | ツール |
|---|---|
| すべてのdialogue_idのマッピングが存在する | dialogue_lint.py |
| すべてのquest_idがchapterに属する | quest_lint.py |
| 報酬の合計がチャプター別カーブの範囲内 | reward_curve_check.py |
| 禁止語彙の出現有無 | tone_lint.py(L0のtone_manifestoベース) |
tone_lint.pyがL0のtone_manifesto.mdを直接読む点に注目してください。最上層(不変のビジョン)と最下層(出荷ゲート)が、自動化で直接つながっています。ビジョンに書いた禁止語彙がリリース直前の本文で検出されると、ビルドがブロックされます。
自動で捕まえられないものは、人が見ます。
| チェック | 担当 |
|---|---|
| L0の感情の柱との整合 | リードナラティブ |
| キャラクターボイスの一貫性 | キャラクター担当+ナラティブ |
| ローカライズ可能性 | ローカライザー |
自動と手動の境界が明確なら、レビュー時間が予測可能になります。「今回のチャプターのレビューは何日かかりますか?」に答えられるようになります。
ここまでが構造です。しかし、この構造が本当に働く場面は、新しいコンテンツを作るときです。すでに五つの層が敷かれたプロジェクトで、サイドクエストの1行アイデアを受け取ってLayerにばらまく作業をClaudeにやらせた、実際のセッションをそのまま載せます。要約せず、外れた部分と拒否した部分まで含めます。
送ったプロンプト(全文):
添付したL0/L1コンテキストを絶対に破らず、うちのLayer構造で分解して。
[narrative_pillar.md 全文 — 感情の柱3つ]
[tone_manifesto.md 抜粋 — 禁止語彙: "運命", "予言", "選ばれし者"]
[character_bible/character_kim.md 抜粋 — キム: 主人公を引き取った養父、
寡黙、語尾を濁す、感情を直接言わない]
アイデア: "キムがプレイヤーに亡くなった妻の遺品を渡す短いサイドクエスト。"
L2クエストファイル(relatedブロック込み、本文200字ほど)、L3 quest_tableの行と
dialogue_id、それからtone違反のセルフチェックまで。まずL2だけ書いて止まって。
Claudeの生の出力(L2):
---
title: 色あせたハンカチ
layer: L2
type: side_quest
atoms:
- quest_side_kim_keepsake
related:
affects: [reputation_model]
derives_from: [narrative_pillar]
requires: [character_kim]
part_of: chapter_02_road
---
## 進行段階
1. キムがプレイヤーを静かに呼ぶ。いつもどおり用件をすぐには言わない。
2. 古びたハンカチを差し出す。「これは…君が持っていたほうがいい気がしてな。」
3. プレイヤーが何かと尋ねると、キムは答える代わりに窓の外を見る。
4. (選択) 受け取る / 辞退する。
## 分岐
- 受け取る: キムの評判 +1。以降のチャプターでハンカチ関連の回想を1回解禁。
- 辞退する: キムはうなずき、ハンカチをまた仕舞う。評判の変化なし。
ハンカチは永遠に再び登場しない。
## 報酬 (L3参照)
- reward_table.xlsx → quest_side_kim_keepsake 行
ここでClaudeは止まり、1行を付け加えました。
「L0の柱1番(失ったものは二度と戻らない)を反映し、『辞退する』を選ぶとハンカチが永遠に消えるようにしました。ただし、ひとつ確認が必要です。亡くなった妻という設定は、character_kim.mdの抜粋にはありませんでした。私が独自に入れたものなので、キャラクターバイブルと衝突する可能性があります。」
人による検証と拒否:
この出力はトーンが正確でした。キムの寡黙さ、語尾を濁す癖、感情を直接言わない癖が生きていました。禁止語彙もありませんでした。しかし、最後のセルフチェックが、まさに本当の問題を突いていました。キムに亡くなった妻がいるという設定は、どこにもなかったのです。AIが「遺品」という言葉から自然に亡くなった配偶者を推論したわけですが、これはバイブルに新しい事実をこっそり追加する行為です。まさにこれが、本章冒頭の会議室で起きた事故の種です。
だから拒否しました。再依頼を送りました。
再依頼:
トーンはいい。ただ"亡くなった妻"はバイブルにない新設定だから使えない。
キムの過去は"故郷を離れるとき何かを置いてきた"までしか確定していない。遺品の正体を
特定せず、"ずっと前に置いてきたもの"くらいにぼかして書き直して。
ハンカチもバイブルにないから"布切れ"くらいに落として。
再依頼後の出力(抜粋):
2. 古びた布切れを差し出す。「ずっと前に…置いてきたものが、ひとつあってな。
これはその切れ端だとだけ言っておこう。」
3. プレイヤーが何かと尋ねると、キムは答える代わりに窓の外を見る。
今回は受け入れました。キムの過去を新たに確定させることなく、感情の柱1番(喪失)に触れています。何を失ったのかは、最後までぼかしたまま残ります。
このひとつのセッションが見せてくれるのは2つです。第一に、Layerのコンテキスト(L0の柱+L1のトーン+L2のバイブル)を正確に注入すれば、AIは驚くほどトーンを合わせてきます。第二に、AIは空欄に出会うと、もっともらしく埋めます。その埋めた内容がバイブルにない新事実であるとき、それを捕まえるのは依然として人です。Layer構造のrequires: [character_kim]が「どこを見るべきか」を教えてくれたからこそ、検証者はどのファイルと突き合わせるべきかをすぐに判断できました。構造がなければ、70ページをまた漁っていたはずです。
五つの層に分けた本当の理由は、インターフェースを狭くするためです。そして、狭いインターフェースごとに変更検知の自動化を付けます。
| インターフェース | 何が流れるか |
|---|---|
| L0 → L1 | pillar、tone(変更時にL1ルールブックの再検討トリガー) |
| L1 → L2 | ルールブック・分岐ポリシー(変更時に影響を受けるクエストを自動リスト化) |
| L2 → L3 | quest_id、npc_id、dialogue_id(列1つ) |
| L3 → L4 | シート変更時に自動lintトリガー |
たとえば、L1のfaction_system.mdで「同時所属は最大2つ」を「最大1つ」に変えるPRを上げると、関係図がfaction_quest_eligibilityを参照しているL2のクエストをなめて影響リストを作り、そのリストがPRコメントに自動で添付されます。ルールを変えた人は、「会議を2、3回設定して誰が影響を受けるか調べる」代わりに、コメントに付いたリストを見て30分の会議で終わらせます。
核心はこれです。Layerだけ分けてインターフェースが曖昧なら、仕切りが増えただけになります。Layer分離そのものより、インターフェースの自動化が本質です。
五つの層に移して6か月回した後の測定です。以下の数値は著者チームの運用記録に基づきますが、絶対値は方向・比率としてのみ書いています(著者の推定・未検証)。分離前は記憶、分離後は実測なので、同じ資料を2回測ったわけではないという限界があります。
| 項目 | Layer分離前 | Layer分離後 |
|---|---|---|
| 新規プランナーのオンボーディング | 3週間 | 1週間 |
| ルールブック変更の影響範囲の把握 | 会議2〜3回 | 自動コメント+会議30分 |
| 新メインクエスト1チャプターの制作 | 4週間 | 2.5週間 |
| リリース前の1チャプターのレビュー | 5日 | 2日 |
| ローカライズ漏れ事故 | 四半期あたり3〜5件 | 四半期あたり0〜1件 |
もっともはっきり減ったのは、ローカライズ漏れです。dialogue_idがL3で翻訳キーと1:1で結ばれ、dialogue_lint.pyがマッピング漏れを防ぐようになって、「翻訳されていないセリフがビルドに入る」事故はほぼ消えました。オンボーディングが短くなったことも大きな成果でした。新しいプランナーに「70ページのWordを全部読んで」ではなく、「L0の4.5枚だけ頭に入れて、自分のクエストはL2のテンプレートを埋めて」と言えるようになりました。
ひとつ正直に付け加えると、この効果が一度に出たわけではありません。最初の四半期には自動コメント1つだけを付け、残りは手作業でした。インターフェースの自動化は、四半期ごとに1つずつ増やしました。Layerを分けることより、自動化を付けることのほうが長くかかりました。
ここまでが表面的な理由です。「分野間の協業の言語を統一する」。しかし、より本質的な理由がもうひとつあります。Layer分解は、プロシージャル生成を可能にする前提です。5つの層のそれぞれがプロシージャル生成のひとつの役割(L0アンカー → L1ルールブック → L2本文 → L3数値 → L4ゲート)に対応し、ひとかたまりに混ざると生成器がどこから読みどこに書くかを決められず崩れる、という一般論は§6.6で全体として扱いました。ここでは、その前提がナラティブの五つの層の上でどのように実際に動くのかだけを見ます。
ひとかたまりの70ページ文書の上では、生成アルゴリズムはどこから読みどこに書くかを決められません。ナラティブの五つの層は、そのまま生成パイプラインの5段階になります — L0アンカー、L1入力ルール、L2本文が積み上がる場所、L3シミュレーション入力、L4検証ゲート。
先ほどのワークド・トランスクリプトが、すでにこの5段階のミニ版でした。L0の柱とL1のトーンをコンテキストとして注入し(アンカー)、L2に本文が生成され、L3の行が続いて出てきて、toneの違反セルフチェックが検証ゲートの真似をしました。人が一度に1クエストずつやらせていたものを、同じ構造の上でgeneratorがサイドクエストを量産するように移せば、プロシージャル生成になります。
さらに先へ行くと、こう流れます — プレイヤー行動の累積 → ワールドBTノードの状態変化(Squad上位) → NPC数値の変化(評判・関心事・優先順位) → NPCのタグ+数値がそのまま発現条件 → クエストクラウドからマッチするクエストが発現。クエストをあらかじめ全部書いておくのではなく、タグが付いたまま「空中に浮かぶ雲」のように置いておき、プレイヤーの行動がNPCの数値を変えると、その数値が発現条件と合うクエストが降りてきて現れます。この進歩的なモデルは、5.3で改めて詳しく扱います(フロー図を含む)。ここで強調したいのは、このすべての流れが五つの層の分解の上でのみ動くという点です。
逆に、Layerが混ざっているチームは、プロシージャル生成へ進めません。試みた瞬間、一貫性の事故で崩れます。キム某が養父であり裏切り者でもあるというあの事故が、生成器を通じて自動で量産されると想像してみればわかります。
ただし、最初から五段の引き出しを完璧にそろえなければならないという意味ではありません。最初の四半期には、L0の1行とL1のルールブック1冊だけを分離すれば十分です。分離は段階的に、インターフェースは狭く、です。
最後に、時期に関してひとつ。Layer分解自体は、決定論的なPCG(Procedural Content Generation、プロシージャルコンテンツ生成)の時代からあった分離です。新しいのは、LLMがその分離の上で自然言語の本文・ペルソナ・物語の分岐まで処理するようになったという点です — 上のトランスクリプトでAIがキムのトーンに合わせてセリフを書いたことは、5年前のルールテーブルでは不可能でした。この時期論は5.3でさらに扱います。
次章(5.2)では、この五つの層の上でlore_consistency_ruleがどのように世界観→キャラクター→クエストの一貫性を自動で検証するのか、つまりキム某の事故を構造的に防ぐチェッカーを見ます。
すでにひとかたまりの世界観文書がある状態から始めると想定します。
setup. NarrativeDocsフォルダーの下に、空の5フォルダーを作りましょう。Layer0_VisionからLayer4_Build_QAまでです。既存のひとかたまりの文書はそのまま置いておき、その中から「絶対に変わらない1行」だけを選んでLayer0_Vision/narrative_pillar.mdに移します。frontmatterにstatus: lockedを入力します。このひとつのファイルが4.5枚を超えないようにします。
prompt. 新しいコンテンツを作るとき、AIにこの順番でコンテキストを渡します。
[narrative_pillar.md 全文]
[tone_manifesto.md 禁止語彙]
[関連する character_bible ファイルの抜粋]
アイデア: "<1行アイデア>"
このコンテキストを破らず、L2クエストファイルから書いて。relatedブロック
(requires/derives_from/affects) を埋めて、バイブルにない新設定は
入れず、必要なら止まって質問して。
verify. 出力で2つのことを確認しましょう。(1)requires:に書かれたファイルを実際に開き、AIがそこにない事実を新たに作っていないか突き合わせます。(2)禁止語彙を本文から検索します(tone_lint.pyがあれば自動、なければ目で)。どちらか1つでも引っかかったら拒否し、何が間違っているかを明示して再依頼してください。上のトランスクリプトで「亡くなった妻」を拒否したのが、まさに(1)です。
チームなしで一人で作るインディー開発者なら、五つの層は過剰に見えるかもしれません。2段で十分です。
vision.md1枚(L0+L1を統合)とcontent/フォルダー1つ(L2)、それからスプレッドシート1枚(L3)を置きます。QAは別の層を設ける代わりに、contentファイルのfrontmatterにrequires:を1行だけ書く習慣で代用しましょう。新しいクエストを書くたびにrequiresに書いたファイルだけを開き直して突き合わせれば、70ページを全部漁らなくてもキム某の事故を防げます。核心は層の数ではなく、「絶対に変わらない1行を切り出して、毎回AIに先に見せる」習慣です。その1行がコンテキストアンカーであり、一人でも中規模チームでも、そこから始めます。
ベータ直前、QAからバグレポートが1枚上がってきました。タイトルは「王がタメ口をきいています」。本文は短いものでした。「3.4の導入カットシーンでK_001(国王)がプレイヤーに『おい、ちょっと待て(原文:야, 잠깐만)』と発言。このキャラクターは1.1から3.3まで一貫して『そなた(原文:그대)』を使用」。
ライターに聞いてみると、意外な答えが返ってきました。「そのセリフ、私は書いていません」。追跡してみると、外注ライターがカットシーンの分岐の1行を急いで埋める際に入れたものでした。私たちのキャラクターバイブルにはvoice_profileがありましたが、その外注ライターはその文書を見たことがなかったのです。ルールは文書の中にあり、セリフは文書の外から入ってきました。
これが一貫性事故の本質です。ルールがないから起きるのではなく、ルールが本文まで付いていけないから起きるのです。そしてこの1行がカットシーンだったとなると、話はさらに怖くなります。カットシーンには通常、声優の収録が付きます。テキストの段階なら一度直せば終わりですが、録音された後に発見されると、声優の再招集・再収録・再ミキシングという不可逆のコストが付いてきます。一貫性検証の本当の目的は「録音の前に」捕まえることです。
本章では、その事故を人の目の代わりにルールブックとチェッカーで捕まえるワークフローを扱います。lore_consistency_ruleというルールブックがどのようにチェッカーの入力になるのか、voice_lintがトーンのブレをどのように疑い候補として挙げるのか、そしてなぜ最終判定だけは最後まで人の領分として残すべきなのかを、実際の成果物そのままで見ていきます。
リリース済みのRPG・MMORPGのユーザーレビューを集めてみると、ナラティブの一貫性事故はいくつかのパターンに収束します。種類は違って見えても、原因はほぼ1つです。
5つはそれぞれ別の事故に見えますが、追跡してみると同じ場所で漏れています。Layer 0(世界前提)やLayer 1(ルール)が変わったのに、その変更がLayer 2(本文)とLayer 3(マスターデータ)まで伝播していないのです。ルールは更新されたのに、本文は古いルールの上で止まっています。
手作業のチェックでこれを防ごうとするのは無理があります。1つの章にNPCが50人、セリフが2,000行、クエストが30個絡み合っているのに、ルールを1行変えたときにその影響がどこまで波及するかを人が100%追跡するのは不可能です。見落とした1行はレビュー段階では捕まらず、リリース後のユーザーレビュー欄で捕まります。
かといって、自動チェックが100%を保証するわけでもありません。核心は役割分担です。自動チェックは疑い候補を素早く挙げ、判定は人が行う。自動化の目的は人のレビュー時間を減らすことであって、人をなくすことではありません。この前提を曖昧にすると、後で扱うすべての失敗が付いてきます。
プロジェクトAのL1文書の1つがlore_consistency_rule.mdです。この文書は人が読むガイドであると同時に、チェッカーがパースする入力でもあります。フロントマターのatomsとaffectsが、その2つの役割を1つの体に束ねています。
---
title: ロア一貫性ルール
layer: L1
atoms:
- lore_check_world_rule
- lore_check_character_voice
- lore_check_timeline
- lore_check_faction_relation
related:
derives_from: [world_premise, narrative_pillar]
affects: [main_quest/*, character_bible/*, dialogue_id_table]
---
## 1. 世界ルール (World Rule)
- 魔法は禁止された状態から始まる → 魔法使用時は(時点、使用者、正当化)の明記が必要
- 神は沈黙状態 → 直接応答の描写は禁止(夢・幻想は許容)
## 2. キャラクターボイスルール
- 各キャラクターごとに voice_profile の参照を強制
- 新規セリフ作成時は voice_profile の5項目(語彙、文長、敬称、感情表現、禁忌表現)を遵守
## 3. タイムラインルール
- すべてのNPCに status_timeline を定義(生存 / 負傷 / 死亡 / 行方不明 / 位置変更)
- セリフ・登場時点で status_timeline を自動点検
## 4. 勢力関係ルール
- faction_relation_matrix の変更時点を記録
- 変更後のセリフは新しい関係を反映
affectsの1行がチェッカーのスキャン範囲を定義します。world_premiseが変われば、チェッカーはmain_quest/*、character_bible/*、dialogue_id_tableのすべてをスキャンし直します。人が「どこまで影響が及ぶのか」を頭の中で追跡していた作業を、ルールブックに書かれた依存関係グラフが代わりに引き受けるのです。
voice_profileは、このルールブックが参照する別のL2資産です。キャラクター1人のプロファイルは、チェッカーが比較基準として使えるように、項目が数値化・列挙型で入力されています。
# character_bible/K_001_voice_profile.yaml
character_id: K_001
display_name: 国王
voice_profile:
vocabulary_register: 고풍_격식 # 語彙の格
avg_sentence_len: 18 # 平均文長(字)
honorific: "그대" # 二人称敬称(固定)
emotion_expression: 절제 # 感情表出の度合い
forbidden_terms: ["야", "잠깐만", "ㅋ"] # 禁忌表現
このyamlがあって初めて、「王がタメ口をきく」という事故が、人の直感ではなく機械が比較できる項目になります。honorificが「그대(そなた)」なのに、セリフに「야(おい)」があれば、それは意見ではなくルール違反の候補です。
変更が発生した瞬間にチェッカーが起動します。フローは次のとおりです。
flowchart TD
A[変更発生: L0前提 / L1ルール / L2本文] --> B{変更分類器}
B -->|どのルールのaffectsに該当するか| C[該当チェッカーを呼び出し]
C --> D[affects範囲のL2本文 + L3マスターデータをスキャン]
D --> E[ルール違反/疑い候補リスト]
E --> F[変更リクエストにコメントを自動添付]
F --> G{人による判定}
G -->|本当の違反| H[本文修正 → 再チェック]
G -->|意図された変化| I[voice_profile / ルールブック更新]
G -->|ルールが過敏| J[ルール自体を調整]
H --> K{テキスト段階か}
I --> K
J --> K
K -->|はい: 可逆| L[レビュー完結可能]
K -->|いいえ: 録音後| M[不可逆 — 再録音コスト]
L -.遮断線.-> M
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class B,C,D,F code;
class G human;
class A,E data;
class L pass;
class M fail;
最後の分岐が、本章の隠れた背骨です。すべての一貫性判定はテキスト段階、つまり可逆の段階で終わらせなければなりません。レビューが録音・キャスティングの後にずれ込むと、修正は不可逆になります。だからvoice_lint・timeline_lintのようなチェッカーは、「速く」回すことよりも「早く」回すことが核心です。カットシーンのセリフが録音キューに入る前に、一度は通過していなければなりません。
チェッカーは4種で、それぞれルールブックの1セクションと一対一で対応します。
world_rule_lint.py — L1世界ルール + すべてのL2本文 → 魔法使用・神の応答などの違反候補voice_lint.py — voice_profile + dialogue_id_table → ボイスのブレが疑われるセリフtimeline_lint.py — npc status_timeline + すべてのセリフ・登場時点 → 死亡NPCの再登場などの衝突faction_lint.py — faction_relation_matrix + dialogue tone → 関係矛盾のセリフ4つのチェッカーはいずれも100%正確ではありません。だから出力の名前が「違反」ではなく「疑い候補」なのです。
「チェッカーがある」という抽象的な話ではピンと来ません。実際に一度回してみます。先ほどの「王がタメ口をきく」事故を再現した入力です。
setup — チェック対象のセリフ2行をdialogue_id_tableから取ってきます。
dialogue_id_204 speaker=K_001 text="야, 잠깐만요. 그쪽이 먼저 말해 봐."
dialogue_id_217 speaker=K_007 text="...젠장, 또 실패야. 다시 처음부터."
prompt — チェッカー本体はルールベース(正規表現・長さ比較・禁止語マッチング)で動きます。AIは「なぜ疑わしいのか」をライターが読める自然言語で説明する場所にだけ付けます。チェック段階のLLMプロンプト全文は次のとおりです。
voice_lintが抽出した疑い候補だ。判定はせず、各候補が
どのvoice_profile項目に違反していそうかと、その根拠だけを1〜2文で説明して。
「違反だ/違反ではない」と断定せず、「〜と疑われる」までにとどめること。
[voice_profile]
K_001: honorific="그대", avg_sentence_len=18, forbidden=["야","잠깐만"]
K_007: forbidden=["젠장"]
[疑い候補]
- dialogue_id_204 (K_001): "야, 잠깐만요. 그쪽이 먼저 말해 봐."
- dialogue_id_217 (K_007): "...젠장, 또 실패야. 다시 처음부터."
Claudeの生の出力(要約・整理なしにそのまま掲載します)
[dialogue_id_204 / K_001]
このセリフはhonorific項目と衝突するものと疑われます。voice_profileは
二人称の敬称を「그대」に固定していますが、本セリフは「그쪽」を使っており、
敬称自体が現れていません。また、forbidden_termsの「야」と「잠깐만」がいずれも
登場しており、語彙の格(고풍_격식)から外れているものと疑われます。平均文長18字の
基準とは直接矛盾するようには見えませんが、語調自体がカジュアルになっている点が違反シグナルとして疑われます。
[dialogue_id_217 / K_007]
forbidden_termsの「젠장」がそのまま使用されており、禁忌表現項目の違反と疑われます。
ただし、K_007(学者)のemotion_expression項目が提示されていないため、この表現が
意図された感情の高揚なのか単純な逸脱なのかは、この情報だけでは判断が困難です。
判定にはライターのレビューが必要です。
この出力には、私たちがあえて残した2つの正直さがあります。第一に、204でAIは「平均文長の18字基準とは直接矛盾して見えない」と述べ、違反ではない項目を違反だと言い張りませんでした。第二に、217で「emotion_expression項目が提示されていないため判断が難しい」と述べ、情報不足を認めて判定を人に渡しました。AIがすべての疑いを「違反確定」として押し切っていたら、その方が危険なチェッカーです。
verify — ライターはこのコメントを変更リクエスト上でそのまま受け取ります。判定はライターが行います。
emotion_peak_exceptionフラグを追加し、217を例外として登録2つの候補を同じチェッカーが挙げたのに、結末は正反対です。一方は本文を直し、もう一方はルールを直します。この分岐を機械が自動ではできないということが、次の節の核心です。
チェッカーが疑いまでしか挙げず、判定を人に渡すのには3つの理由があります。
第一に、意図された違反が存在します。キャラクターが崩れたり変わったりする章では、ボイスは意図的に揺れます。先ほどの217がそうです。自動拒否型のチェッカーは、ライターの演出意図を塞いでしまいます。
第二に、ルール自体が進化します。同じ種類の疑いが続けて「意図された変化」と判定されるなら、それはルールが現実に追いついていないというシグナルです。チェック結果は本文だけを直させるのではなく、ルールブックも直させます。
第三に、新規キャラクター・勢力には学習期間が必要です。voice_profileがまだ2〜3項目しか埋まっていない新規NPCは、疑いが多めに挙がるのが正常です。この時期に自動拒否をかけると、ライターはチェッカーを敵と認識します。
自動チェックと人の判定の境界が明確であってこそ、チェッカーは生き残ります。自動拒否型にすると、1か月以内にライターたちが「これ、切りましょう」と言い出します。会社の出入口に敏感すぎる自動センサーを付けると、人が通るたびにドアが閉まり、結局誰かがセンサーを外してしまうのと同じです。チェッカーはドアを閉める装置ではなく、「ここを誰かが通った」と知らせる装置でなければなりません。
1つ但し書きを添えます。レビューはテキスト段階で完結すべきだという原則(先のフロー図の遮断線)は、人の判定にもそのまま適用されます。ライターの「意図された違反」という判定も、録音の前に終わっていなければなりません。録音後の覆しはチェッカーの問題ではなく、工程コストの問題に変わります(可逆/不可逆の境界の全体像は5.4.5)。
プロジェクトAでチェッカー4種を段階的に導入し、6か月測定しました。以下は実測ログに基づきつつ、絶対値の代わりに方向・比率に置き換えたものです(社内測定、著者の推定ではありません)。
最後の項目が一番興味深いところです。チェッカーがあれば、ルールを頻繁に変えても安全です。ルールを1行変えればその影響が自動的に可視化されるので、変更への恐れが減り、ルールがより速く進化します。一貫性ツールの本当の効果は「事故を減らした」ではなく、「ルールを恐れずに変えられるようになった」という方に近いのです。
ただし、上の数値はチェッカー4種がすべて稼働している時点の数字です。導入初期にvoice_lintの1つだけでも目に見える効果が出たという点の方が重要です。最初から4種を全部オンにする必要はありません。
自動チェッカーの本体はルールベースが効率的です。同じ入力に同じ結果が返ってきてこそ信頼が積み上がるのですが、LLMは非決定論的なので、その場所には合いません。AIは別の4つの場所に入ります。
ルールは速くて決定論的、LLMは説明と生成に強い。この2つの役割を混ぜると、どちらも壊れます。チェックをLLMに任せると、同じセリフが昨日は通って今日は引っかかるということが起き、説明を正規表現に任せると「honorific項目違反」という機械語しか出てきません。
最初からチェッカー4種を全部作ると、負担が効果より先に来ます。推奨順序は、最も安くて効果の大きいものからです。
ステップ2(voice_lint)だけでも効果が大きいという点を強調しておきます。先ほどの「王のタメ口」事故は、まさにこのステップ1つで捕まる種類のものでした。
導入過程で繰り返される失敗も、ほぼ決まっています。
最後の項目が、その前のすべての項目よりも高くつきます。ほかの失敗は時間を失いますが、この失敗は声優のスケジュールを失います。
次章(5.3)では、チェッカーの代わりにAI補助でナラティブ本文を書くフローを扱います。L0のトーンとL1のルールをコンテキストとして注入し、AIが一般的な答えではなく、私たちの世界の答えを出すようにする方法を見ていきます。
setup — character_bibleからキャラクターを1人選び、voice_profileの5項目(語彙の格・平均文長・敬称・感情表現・禁止表現)をyamlで完全に埋めましょう。同じキャラクターの既存セリフ10行をdialogue_id_tableから抜き出し、1つのファイルにまとめます。
prompt — 先のワークド・トランスクリプトのチェック補助プロンプトをそのまま使いましょう。核心は2つの制約です。「判定するな」と「『〜と疑われる』までにとどめよ」です。入力にvoice_profileのyamlとセリフ10行を貼り付けます。
verify — 出力された疑い候補を1行ずつ自分で判定しましょう。本当の違反なら本文を直し、意図された変化ならvoice_profileに例外フラグを追加します。AIが「違反ではない項目」まで違反だと言い張っていないか、「情報不足」を認めているかも併せて確認します。AIがすべての項目を違反と断定するなら、プロンプトの「判定するな」という制約を強化しましょう。
チェッカー4種もルールブックもない1人開発なら、チェッカー本体なしで、プロンプト1つで同じ効果を出せます。キャラクターごとのvoice_profileのyamlだけを手で維持し、新しいセリフを書くたびに、そのキャラクターのyaml + 新しいセリフを先の補助プロンプトに貼り付けて「疑い候補」を受け取りましょう。自動化はありませんが、判定は人、AIは説明という核心構造はそのまま生きています。守るべきはたった1行です — 録音・音声合成に回す前に、このレビューを一度通すこと。可逆段階を越えないという原則は、チームの規模とは無関係です。
新しいサイドNPCの最初のセリフを作っていた日のことです。空のチャット欄に「村の鍛冶屋NPCのセリフを5つ作って」と打ち込みました。5秒後、画面に「勇者よ、そなたの武器をわしに任せるがよい」が表示されました。どこかで見たような、ではなく、正確にどこで見たのか分かる気がする文章でした。同じプロンプトを別のチームの別のゲームに入れても、まったく同じ答えが返ってくるはずでした。その瞬間に気づいたのは、モデルが弱いということではなく、私がモデルにこちらのゲームのことを何ひとつ教えていなかったということでした。
AIは一般的なファンタジーの文章を上手に書きます。しかし、この世界の文章は書けません。違いはただ一つ、コンテキスト注入です。L0トーンとL1ルールを毎回のリクエストに添えて送れば、AIが吐き出す一行は「どこかで見たような文章」から「このゲームの文章」に変わります。本章ではその注入を4層で運用する実務を扱い、最後に同じ原理をワールドシミュレーション規模へ引き上げる進歩的な適用(ワールドBT(BehaviorTree、ビヘイビアツリー)+クエストクラウド)をRnDの最前線として押さえます。
ナラティブ分野でのAI支援は、最も早く導入され、最も早く信頼を失う領域です。失敗パターンがほとんど同じだからです。
「クエスト開始のセリフを5つ」と投げると、「勇者よ、われらの村が…」で始まる一般的なファンタジー5種が返ってきます。「このキャラクターのセリフを直して」と頼むと、ボイスが平準化されてすべてのNPCが似た口調に収束します。「チャプター1のシノプシスを書いて」と頼むと、どこかで見たことのあるRPGシノプシスの平均値が出てきます。
問題はモデルではなく、コンテキストが空だということです。モデルは学習データの平均を出力します。平均がほしくないなら、平均から遠ざかる手がかりを与えなければなりません。本章のテーマは、その手がかりをどう作り、どう注入するかです。
著者が運営するMMORPGプロジェクト(以下、プロジェクトA)では、ナラティブAI支援は4層のコンテキストを順番に積み上げます。5.1でNarrativeDocsをLayer 0〜4に分解したあの構造が、ここでそのまま注入単位として再利用されます。
4層を毎回すべて入れるわけではありません。作業タイプによって必要な層だけを取り出します。あるキャラクターの次のセリフの草案なら、A + B(トーンのみ)+ D(そのキャラクターの直近のセリフ10行)で十分です。新規サイドクエストのシノプシスならC(quest構造ルール)が加わります。分岐結果4案ならC(分岐規則)+ D(分岐直前の本文全体)が重くなります。机の上の書類棚からペルソナシート、世界観の一行、ルールブックのページ、隣接本文のひと束を、作業の大きさに合わせて選んで送るようなものです。
抽象的に説明する代わりに、実際の一つのリクエストを最後まで追いかけます。同じキャラクター(学者タイプのNPC、社内ID K_007)が初めて感情を表に出す場面のセリフ3案を作る作業です。プロンプト全文から始めます。
送信したプロンプト(Layer A + B(トーン)+ D + 作業指示 + 出力形式):
[システム]
プロジェクトAのナラティブ作家だ。「勇者」・「選ばれし者」のようなRPG常套句は使わず、
口調は下の直前のセリフのトーンにそのまま従うこと。知らない設定はでっち上げず
「コンテキストでは判断不可」と答えること。君は草案だけ、決定は私がする。
[L0トーン — tone_manifesto 抜粋]
この世界の情緒は抑制だ。人物は感情を簡単に口にしない。
感情は爆発ではなくひび割れとして現れる。言葉の省略と短い文。
[参考 — K_007の直前5つのセリフ (時系列、原文のまま)]
1. "...それは私も初めて聞く話ですが。"
2. "記録はありますか? なければ疑うべきでしょう。"
3. "確実になるまで動かないでおきましょう。"
4. "私の意見はそれで終わりです。"
5. "...すみません。私は冷たすぎましたか。"
[状況]
仲間のNPCが傷ついたまま戻ってきた。学者は初めて感情を表に出さなければならない。
ちょうど3案だけ、各25字以内で。案ごとに末尾へ (反応タイプ: 怒り/悲しみ/否定) のラベルを付け、
説明や前置きは省くこと。
Claudeの生の出力(要約・脚色なしそのまま):
1. "...なぜ一人で抱え込んだんですか。バカみたいに。" (反応タイプ: 悲しみ)
2. "記録を見るべきだった。私が、私が見落としたんです。" (反応タイプ: 否定)
3. "大丈夫です。全部大丈夫です。だからもう—" (反応タイプ: 否定)
ここで止まらないことが核心です。作家が3つの案を読んで検証します。1案目はトーンが合っています。直前のセリフ5番の「...죄송합니다(…すみません)」が見せたひび割れを、1案目は「바보같이(バカみたいに)」という一語で受け継ぎました。採用候補です。2案目は「제가, 제가 놓쳤어요(私が、私が見落としたんです)」の言いよどみが、学者の抑制とわずかにずれています。保留です。3案目はラベルが間違っています。「괜찮아요(大丈夫です)」を繰り返すのは否定ではなく回避に近いのですが、このプロジェクトのvoice_profileには、学者が感情を回避で処理するパターンがありません。却下です。
再リクエスト(却下理由を明記して再送):
3案目は却下。「大丈夫です」の繰り返しはこの学者のパターンじゃない。この人物は感情を
回避ではなく「事実確認の失敗に対する自責」で処理する。1案目は採用する。
2・3案目の枠に、1案目と毛色の違う2案をもう一度。言いよどみなしで25字以内で。
Claudeの2回目の出力:
2. "記録は嘘をつきません。私が見なかったんです。" (反応タイプ: 自責)
3. "...座って。まず傷を。話はあとで。" (反応タイプ: 悲しみ)
今回は2つとも通過しました。2案目は「기록(記録)」という学者の核心語彙(直前のセリフ2番「기록이 있나요?(記録はありますか?)」)を自責の媒介として使い直し、3案目は抑制された命令形で、学者が感情を行動で押さえ込むパターンを見せました。最終採用は1案目+2案目+3案目です。この3行は5.2のvoice_lintによる自動チェックを通過したあとでL2本文に反映され、L3でdialogue_idの発行を受けます。
この一本のトランスクリプトに、本章のすべてが詰まっています。トーン注入(L0)が1案目を生かし、原文そのままの隣接本文(L2)が学者の語彙「기록(記録)」を再リクエストで再利用させ、出力形式の強制が雑談を防ぎ、作家の却下ゲートが3案目の間違ったラベルをふるい落としました。AIは一行たりとも最終決定していません。
いちばん上に敷かれるペルソナ定義です。一度決めたらほとんど変えません。上のトランスクリプトのシステムブロックがその実物です。5行のうち最後の1行(「草案だけを書き、決定は私がする」)が最も重要です。これが抜けると、AIは「最終版」のふりをした文章を自信満々に出してきて、作家はレビューの代わりに採点をするはめになります。そして3行目(「知らない設定はでっち上げず『コンテキストでは判断不可』と答える」)が2番目に重要です。この行がなければ、モデルは空欄をもっともらしい嘘で埋めます。ナラティブにおいて、もっともらしい嘘は数日後にロアの衝突として返ってきます。
L0は分量が少なく(5.1基準で約4.5ページ分)、ほぼ毎回全体を注入できます。韓国語基準の推定でworld_premise.mdが約2,500トークン、narrative_pillar.mdが約1,500トークン、tone_manifesto.mdが約3,000トークン、合わせて約7,000トークンです。(これらの数値は著者の推定(未検証)です。トークナイザーや文書の改訂によって変わります。)
7,000トークンを毎回のリクエストで送り直すと、コストが積み上がります。そこでプロンプトキャッシングをかけます。Anthropic・OpenAIの両方が対応している機能で、キャッシュヒット時には入力トークンのコストが大きく下がります。核心は、変わるものと変わらないものをメッセージの中で分離しておくことです。
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": [
{"type": "text", "text": L0_FULL, "cache_control": {"type": "ephemeral"}},
{"type": "text", "text": L1_SELECTED, "cache_control": {"type": "ephemeral"}},
{"type": "text", "text": L2_ADJACENT}, # 毎回変更 — キャッシュしない
{"type": "text", "text": TASK_INSTRUCTION}, # 毎回変更
]},
]
cache_controlを付けたL0とL1はキャッシュ対象で、L2の隣接本文と作業指示は毎回変わるためキャッシュしません。キャッシュブロックを常にメッセージの前方にまとめておくことがヒット率を左右します。変わるブロックが前に挟まると、その後ろのキャッシュはすべて無効化されます。この順序を間違えることが、キャッシングを有効にしてもコストが下がらない最も多い原因です。
キャッシュヒット率・コスト削減数値の詳細はPart 22(コスト)の章で扱います。ここでは「変わるものを後ろに寄せる」という原理だけ覚えておけば十分です。
L1ルールブックは分量が大きく、全部入れるとコンテキストがあふれ、さらに悪いことにモデルが核心を見落とします。作業に関連するルールだけを、それも_summary節だけを選びます。
メインクエストの分岐結果を作るときはdialogue_branching_ruleとfaction_relation_matrixを選びます。新規NPCのセリフならそのNPCのvoice_profileとtone_manifesto。ロア辞典の新規項目ならlore_consistency_ruleとworld_premise。サイドクエストの骨格ならquest_templateとreputation_modelです。選択は人が直接行うか、wikilinkグラフ(第7部)をたどって自動抽出しますが、自動抽出のときは適合率より再現率を優先します。ルールが1つ抜ける損害のほうが、ルールが1つ余計に入る損害よりはるかに大きいからです。
ルールブックの本文を全部入れる代わりに、ルールブックファイルの冒頭に_summary節を置き、それだけを注入します。
---
title: 分岐規則
layer: L1
---
## _summary
- 分岐はチャプターの末尾でのみ発生
- 分岐は2~3案。4案以上は禁止
- 分岐の選択は評判に +/-1 の影響、結末分岐には +/-3
- すべての分岐結果は24時間以内に結果を見せなければならない
- 分岐は取り消し不可 (セーブ分離推奨UIの表示)
## 1. 分岐発生時点の規則
(詳細説明、運営者参考用 — LLMには注入しない)
...
_summaryの5行のほうが、本文50行よりLLM出力の品質に効きます。モデルは短く断定的なルールをよりよく守ります。長い説明はモデルの注意を分散させ、分散した注意はルール違反として返ってきます。
直前のセリフ、隣接クエスト、同じチャプターのシノプシス。最も変動が大きいコンテキストです。同じキャラクターの新規セリフにはそのキャラクターの直前のセリフ10行を時系列で、チャプター中盤のクエストにはチャプターシノプシスと同じチャプターのクエストの1行要約を、分岐結果の結末には分岐直前の本文全体と選択肢テキストを入れます。多く入れすぎるとLLMは平均を出力し、少なすぎると一般化された出力になります。適正な範囲はトークン1,500〜3,000の間(著者の観察基準、未検証)です。
核心ルールを一つ。隣接本文は加工も要約もせず、原文のまま入れます。上のトランスクリプトで学者の直前のセリフ5行に手を入れずそのまま入れたからこそ、モデルは再リクエストの段階で「기록(記録)」という単語を正確につまみ上げ、自責の媒介として再利用できました。もしあの5行を「学者は慎重で冷たい」と要約して入れていたら、作家の微細な選択はすべて消え、モデルはまた平均へ戻っていたでしょう。要約は情報を減らすのではなく、作家がすでに下した決定を消すのです。
AI出力は常に草案です。レビューは決められたゲートを通過します。
flowchart TD
A[AI出力N個] --> B[1次: voice_lint自動
5.2]
B --> C[2次: 作家1人が選択・修正
約15分]
C --> D{採用案あり?}
D -->|あり| E[3次: リードナラティブ合意
必要時]
D -->|0個採用 = 全部廃棄| F[再リクエストまたは直接執筆]
E --> G[L2本文に反映
+ L3 dialogue_id発行]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class B code;
class A ai;
class C,D,E human;
class G pass;
class F fail;
作家がN個のうち0個を選ぶケース(全部廃棄)も正常です。上のトランスクリプトで3案目が却下されたように、却下は失敗ではなく、ゲートが機能した証拠です。だからこそ、廃棄率を作家別・キャラクター別に測定し、コンテキスト注入品質の指標とします。
廃棄率が0〜20%ならコンテキストが十分な安定運用なので、そのままにします。20〜50%は一般的な運用範囲なのでモニタリングだけ行います。50〜80%に上がったら、L1ルールの選択が抜けていないか再点検します。80%を超えたら、個別ルールの問題ではなくシステムプロンプトやペルソナ自体がずれているということなので、Layer Aを書き直します。廃棄率は週1回、作家別に集計して振り返りで共有します。
ただし、廃棄率は絶対的な指標ではありません。変化の速いキャラクター(たとえば上のトランスクリプトのK_007のように、初めて感情を表に出す転換点)は、廃棄率が高くても正常です。数字は対話の始まりであって、判決ではありません。
L0〜L1はゲームの核心IPです。外部LLM APIにそのまま送るのが負担なら、選択肢が分かれます。外部APIを学習不使用契約のまま使う方式が最も速いものの、法務レビューが必要です。会社名・固有名詞をplaceholderに置換して送る方式は、追加の処理コストがかかり、自然さが損なわれます。セルフホスティング(オープンモデル)はデータこそ安全ですが、品質・運用の負担が大きくなります。L0は内部に置いて草案だけ外部に送るハイブリッドは、運用が複雑です。
著者のプロジェクトAは1番目の方式(外部API+学習不使用契約)を使っています。2番目の方式は試したうえでやめました。placeholder置換が「○○王国の○○学者が○○について語った」という形に本文を平準化させ、出力品質を崩してしまったからです。匿名化が品質を殺すのは本書全体で繰り返されるトレードオフです(Part 1の匿名化の章を参照)。ナラティブではその損傷がとくに大きいのです。固有名詞がそのままトーンだからです。
システムプロンプトなしで作業指示だけ投げると、平均が出てきます。ペルソナと禁則を先に敷きます。L0を毎回全体注入しながらキャッシングを使わないと、コストが漏れます。キャッシュブロックを前方にまとめます。L1ルールブックを丸ごと入れると、モデルが核心を見落とします。_summary節だけを抜き出します。隣接本文を要約して入れると、作家の選択が消えます。原文のまま引用します。出力形式を決めないと、回答のかなりの数が「こちらが3つの候補です:」で始まり、作家がその前置きを本文と取り違える事故までついてきます。個数・長さ・ラベルを明示します。AI出力を最終版として使うと、レビューゲートが崩れます。常に作家ゲートを通過させます。廃棄率を測らないと、ツールの健全性が人の印象に依存します。週次で集計し、振り返りで共有します。
ここまでは保守的な適用でした。作家がコンテキストを丁寧に注入し、AIは一行一行の草案だけを作ります。単位は「このキャラクターの次のセリフ3案」「このクエストのシノプシス」のような小さな作業です。安定していますが、量産規模と動的な反応性には限界があります。
ここで一つ、先に押さえておきます。プロシージャル生成・ワールドシミュレーション・動的クエストは、プランナーたちが20〜30年前から紙の上に描いてきたビジョンです。決定論的なルールブックベースのPCGは、ダンジョンの部屋・武器オプション・スポーン分布のような数値領域は扱えましたが、自然言語の本文・キャラクターペルソナ・物語の分岐・NPCの会話には手が届きませんでした。企画のかなりの部分が紙の上にとどまっていたわけです。2024〜2026年のLLMと画像モデルの発展が、その領域を実装可能な場所へ引き寄せました。AIの発展の核心的な意味はモデルのスコアではなく、長く紙の上にあった企画の実現可能性が開かれたことにあります。ただし、可能性が開かれることと、運用可能なシステムとして定着することは別の問題です。
5.1のLayer 0〜4分解は、単なる整理ではなくプロシージャル生成の前提でした。5つの階層がそれぞれ生成パイプラインのどの段階(L0アンカー → L1ルールブック → L2本文 → L3数値 → L4ゲート)に対応するかは、§6.6と5.1.11で扱いました。要点は一つです — ひとかたまりの文書の上では、生成器がどこから読みどこに書くかを決められず、L0がどこにあるか分からないままコンテキストがぼやけ、L1・L2が一つのファイルの中で衝突して量産ラインが崩れます。Layer分解が先にあり、その上でプロシージャル生成が機能します。ここでは、その前提をナラティブAI支援の量産段階に適用します。
3つの要素が束ねられます。第一に、NPC Personaがプロシージャルに生成されます。メインNPCは作家の手によりますが、サイドNPCは6.2〜6.3のgenerator・Squadパイプラインが量産します。各Personaはvoice_profileとともにタグ(職業・勢力・性向・役割)を付けます。第二に、ワールドBTがSquadの上位に位置します。Squadが一つの狩り場のNPCグループの行動を束ねるなら、ワールドBTはその上でプレイヤー行動の累積数値を受け取り、世界全体の状態を更新します。プレイヤーがどの勢力を助けたか、どの地域によく行ったか、どんな決定を下したかがワールドBTのノード状態を揺らし、揺れたノードは影響圏のNPCたちの数値(評判・関心事・優先順位)に反映されます。第三に、クエストはクラウドモデルです。メインクエストを除くすべてのクエストがプロシージャルに生成され、それぞれタグ(誰が・どこで・なぜ・いつ)と出現条件を付けたまま漂います。どのクエストも特定のNPCに固定されません。
出現は5つの段階を経ます。
flowchart TD
P[1. プレイヤー行動の累積
勢力支援・地域訪問・決定] --> W[2. ワールドBTノードの状態変化
Squad上位の世界状態更新]
W --> N[3. NPC数値の変化
評判・関心事・優先順位]
N --> M[4. NPCタグ + 数値 == 出現条件
マッチングキー生成]
M --> Q[5. クエストクラウドから
マッチするクエストが出現]
Q -.->|作家ゲート通過後に公開| PL[プレイヤーの前に登場]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class M code;
class P,W,N,Q data;
class PL pass;
比喩で言えば、クエストは図書館の本棚に挿さっているのではなく、空中に漂う雲です。NPCがある状態に到達すると、自分に合った雲が降りてきて手に収まります。この構造でAIは一行を書く補助者ではなく、3つを同時に扱います。プロシージャル生成されたPersona・クエストの自然言語本文(説明・セリフ)、ワールドBTの状態がNPCに反映されるときの語彙の変動(同じvoice_profileを維持しつつ、話題はワールド状態に合わせて変動)、そして検証段階の疑義分類器(このクエストがこのNPCに出現してよいか)です。
進歩的な適用でも、可逆/不可逆の境界(5.4.5)はそのまま生きています。クラウドから出現したクエストのセリフがレビューを通過しないまま音声パイプラインへ流れてしまうと、コードのロールバックでは戻せません。だからこそ、すべてのレビューゲート(voice_lint、疑義分類器、作家ゲート)を不可逆の境界の手前に置くことが、進歩的な適用の安全装置です。自動量産が加わるぶん、このゲートは保守的な適用よりもさらに固くなければなりません。
本書は進歩的な適用の完成形までは扱いません。別の本一冊分の分量であり、会社・プロジェクトごとにインフラの前提が異なるからです。覚えておくべきことは2つだけです。まず、保守的な適用ができないチームは進歩的な適用もできません。保守的な適用でレビューゲートが回らなければ、進歩的な適用ではクラウドが暴走します。運用が生き残るには、プロシージャル生成インフラ、ワールドBTノードの定義・テストツール、出現クエストの自動疑義分類+作家ゲート、出現率・廃棄率・プレイヤー満足度の同時トラッキング、誤って出現したクエストの自動回収・差し替えがそろっていなければなりません。5つのうち1つでも欠けると、1四半期以内に廃棄されます。一貫性の事故が爆発的に増え、人のレビューが追いつかなくなるからです。
次に、進歩的な適用は量産を増やす道具ではなく、動的な反応性を増やす道具です。量産だけを狙うと、一般的なRPGの平均がクラウドの中に満ちあふれ、プレイヤーは「より多様に見えるのに、より空っぽな」世界に出会います。とはいえ、この道が危険なだけというわけでもありません。この領域はゲーム企画RnDの最前線です。プロシージャル生成・シミュレーション・LLMが出会う場所から、本当に新しいゲーム形式が生まれる可能性が最も高いのです。すぐに会社へ導入することはまれでも、次の5〜10年の方向性の一つとして見ておく価値はあります。
本章の保守的な適用をそのまま再現する手順です。
setup. L0の3ファイル(world_premise.md・narrative_pillar.md・tone_manifesto.md)を1つのテキストに結合し、L0_FULL変数に入れてください。システムプロンプトは上のトランスクリプトの5行のとおりに書き、最後の行(「草案だけを書き、決定は作家がする」)を抜かさないでください。セリフを作るキャラクターの直前のセリフ10行を、原文のままテキストとして集めます。
prompt. メッセージを[시스템] → [L0 톤] → [참고: 직전 대사 원본] → [상황] → [출력 형식]の順に組み立ててみましょう。出力形式には「正確にN案/各案の最大文字数/ラベル/それ以外は一切禁止」を必ず明記します。L0とL1にだけcache_controlを設定し、変わるブロックはメッセージの後ろに寄せます。
verify. 受け取ったN案を1行ずつ検証してみましょう。トーンが直前のセリフとつながっているか、ラベルが実際の反応と合っているか、このキャラクターが使わないパターン(回避・言いよどみなど)が混ざっていないかを見ます。却下する案は理由を明記して再リクエストします。0個採用も正常で、廃棄率として記録してください。通過した案だけをvoice_lintに通してL2に反映し、L3でdialogue_idを発行します。
一人ミニ版. API・キャッシング・voice_lintがなくても核心は同じです。ChatGPT/Claudeのチャット画面が1つあれば十分です。キャラクターの直前のセリフ5〜10行を毎回コピペで先頭に敷き、出力形式の3行を常に付け、返ってきた答えのうち却下するものは理由を書いて再リクエストしてみましょう。キャッシングの代わりに同じ会話スレッドを維持すれば、前のコンテキストがそのまま残ります。廃棄率は紙1枚に「今週の採用12/全体30」のように手で数えて書けば十分です。道具が小さくても、4層注入とレビューゲートという骨格は一人の作家にもまったく同じように機能します。
voice_profileをAIで抽出・更新し、ボイスをレビューする収録ブース。ディレクターがヘッドホンを着け、手を挙げてキューを出します。声優が学者NPCのセリフを読み上げます。「와, 진짜 대박이네요(わあ、ほんとにすごいですね)」。ディレクターの手が止まります。このキャラクターは、5つの章を通して「와(わあ)」という感嘆詞を一度も口にしたことのない人物です。罵り言葉も、現代的な感嘆詞も口にせず、最後まで語尾を濁す学者です。それなのに、台本にはその行がそのまま載っています。
ここで2つのコストが同時に発生します。第一に、その行を直せば、声優は読み直さなければなりません。出演料とスタジオの時間は、すでに時計が回っています。第二に、もっと怖いのは、ディレクターがその行を捕まえられずに通してしまう場合です。収録が終わって音源がビルドに入れば、その学者はゲームの中で永遠にそういう話し方をします。収録した音源は、テキストのように1行の修正では元に戻せません。同じ声優の同じコンディション、同じブースをもう一度押さえなければならないのです。
本章では、そのブースの手前にある点線を扱います。点線の上はテキストなので無限に直せて、点線の下は音源なので直せません。すべてのセリフのレビューは、点線の上で終わらせなければなりません。そのためには、キャラクターごとに「この人はこう話す」が頭の中ではなくファイルとして記録されていて、新規セリフが上がってくるたびにそのファイルと自動で照合される必要があります。そのファイルをvoice_profileと呼び、照合ツールをvoice_lintと呼びます。
ナラティブの一貫性事故の中でもっとも頻繁に起きるのが、キャラクターボイスの揺らぎです。リリース後になってはじめて「このNPCはなぜ急に話し方が変わったのか」という報告が届きます。原因は毎回ほぼ同じです。
ライターが交代すると、同じNPCが別人になります。ライターが変わらなくても、6か月経てば本人が自分のトーンを忘れます。新規セリフを書くときにそのキャラクターの過去のセリフを開いて見なければ、コンテキストが途切れます。ボイスの規則がライターの頭の中にしかなく、文書になっていなければ、次の人には伝わりません。そして、ここ2年でもっとも速く増えた5つ目の原因 — キャラクター情報なしでLLMに「このNPCのセリフを作って」と投げると、AIは平均的で無難な、つまり誰の声でもないセリフを返してきます。
5つ目がAI導入の副作用です。前の章(5.3)のコンテキスト注入が処方箋ですが、注入するコンテキスト自体が貧弱なら、注入するものがありません。そのコンテキストこそがvoice_profileです。本章では、このファイルを作り、自動で検査し、収録ブースの手前で終結させる1サイクルを見ていきます。
プロジェクトAでは、すべてのNPCが5項目からなるvoice_profileを持ちます。語彙領域(よく使う単語群/絶対に使わない単語群)、文の長さ(平均・最大文字数)、敬語体系(一人称・二人称、敬語の比率、呼称)、感情表現(直接・間接・抑制のどの方式か)、禁止表現(絶対に使わない単語・構文)。5項目すべてに具体例が付いていなければなりません。「重厚な学者」のような抽象的な描写だけでは、人によって読み方が変わります。次のライターは、その学者を自分流に想像してしまうのです。
学者NPC K_007の実際のプロファイル形式は次のとおりです。このファイルがそのままvoice_lintの入力になります。
---
title: K_007 学者 voice_profile
layer: L1
character_id: K_007
atoms:
- voice_profile_k_007
related:
derives_from: [character_bible/k_007.md]
affects: [dialogue_id_table (K_007のすべてのセリフ)]
---
## 1. 語彙領域
- よく使う: "기록", "근거", "정황", "추정", "데이터", "사례"
- 絶対に使わない: "느낌", "감", "운명", "신의 뜻", "마음의 소리"
## 2. 文の長さ
- 平均: 18字
- 最大: 35字 (それ以上は2文に分割)
- 短い途切れが多い: "...아닙니다." "기록부터."
## 3. 敬語体系
- 一人称: "저"
- 二人称: 役職優先(대장님, 사령관님)。親密になった後にのみ名前。
- 敬語100% (回想シーン除く)
- 感嘆詞はほぼなし。あるときは "...아."
## 4. 感情表現
- 直接表現はほぼなし (怒り・喜び)
- 沈黙・語尾を濁すことで表現 ("...그런 식이라면.")
- 悲しみ: 話題転換で回避 ("...다른 얘기 합시다.")
## 5. 禁止表現
- 罵り言葉は一切なし
- 現代的な感嘆詞 "와", "헐", "대박"
- 3音節以上の漢字語を一文に2個以上
- "운명", "예언" など神秘主義語彙
この形式の核心は、抽象の置き場ごとに例文の行が付いているという点です。「感情を抑制する」ではなく、"...그런 식이라면."(「……そういうやり方なら。」)という実際のセリフが付きます。そうしてはじめて、次のライターも、翻訳者も、voice_lintも同じ基準を見ることができます。
5項目を翻訳・ローカライズの目で見直すと、2つのグループに分かれます。言語依存の属性(語彙領域・文の長さ・敬語体系の表層形)は翻訳先の言語ごとに決め直す必要があり、言語非依存の属性(感情を抑えるのか直接表現するのか、何を最後まで言わないのかといった態度)は、どの言語に移してもそのまま守られなければなりません。ローカライズ作業を依頼するときにこの区分を一緒に渡せば、翻訳者が表層の語彙だけを変えているうちにキャラクターの態度まで揺らしてしまう事態を防げます。
最初からNPC 50人×5項目=250件を白紙の状態で書こうとすると、抽象に流れます。本文が1行もないまま「このキャラクターは冷たい学者」と書いても、その冷たさが何なのかは誰にも分かりません。そこで、順序を逆にします。メインNPC 5〜7人だけを最初からフルで作成し、残りは本文のセリフが20〜30行たまった後に、そのセリフからprofileを逆抽出します。
以下は、K_007のセリフが25行たまった後にprofileの草案を抽出した、実際のワークド・トランスクリプトです。要約はせず、AIが間違えた箇所までそのまま載せます。
[ライターが投げたプロンプト全文]
以下はキャラクター K_007 のセリフ25行です。
1. "기록부터 봅시다."
2. "...정황이 맞지 않습니다."
3. "추정일 뿐입니다. 근거가 없어요."
4. "그 사례는 저도 알고 있습니다."
5. "...아닙니다. 데이터가 다릅니다."
(... 6~23番は中略なしで全体引用 ...)
24. "...그런 식이라면, 저는 빠지겠습니다."
25. "다른 얘기 합시다."
ここから、よく使う語彙(実際に出たものだけ)、平均文長、敬称・呼称のパターン、
感情表現の方式、一度も使っていない表現領域、この5つを抽出して。
項目ごとに根拠のセリフ2~3行を番号で引用し、"重厚な"・"冷たい" のような
主観的な印象は書かないで。
[Claudeの生の出力 — 未加工]
1. よく使う語彙
- "기록" (1番), "정황" (2番), "추정/근거" (3番),
"사례" (4番), "데이터" (5番)
→ 事実・証拠系の名詞に集中。
2. 平均文長
- 25行平均 約14.6字。最長21字(24番)。
→ 短い単文中心。
3. 敬称・呼称
- 一人称 "저"(3·24番)。終結語尾 "-습니다/-요" の敬語。
- 二人称の呼称はこの25行には登場しない。
→ 呼称パターンはこの標本だけでは断定不可。(ライター確認必要)
4. 感情表現
- 直接の感情語なし。拒否は "빠지겠습니다"(24)で、
悲しみ/回避は話題転換 "다른 얘기 합시다"(25)で処理。
→ 抑制・間接型。
5. 一度も使っていない領域
- 感嘆詞、罵り言葉、神秘主義語彙はすべて未出現。
※ ただし、"未出現"が"禁止"だという保証はない。
標本が25行と少なく、偶然の未出現の可能性がある。
ここで2つのことを押さえておく必要があります。第一に、AIは3番の項目で立ち止まりました。「この標本だけでは断定不可」と、自分で限界線を引いたのです。呼称は25行の中に出てこなかったので、推定しなかった — これが正直な出力です。もしAIが「二人称は『대장님(隊長殿)』と推定」と埋めていたら、それはハルシネーションであり、profileに入れば汚染になります。第二に、5番の項目でAIは自分から免責を付けました。「未出現≠禁止」は核心的な警告です。感嘆詞が25行に出てこなかったのは、偶然かもしれないのです。
[ライターの検証・拒否]
ライターは1・2・4番を採用しました。3番の呼称はcharacter_bibleを開いて「隊長殿(대장님)優先、親密になった後は名前」を手動で埋めました — AIが空けておいた場所を、人が埋めるのです。5番はAIの警告どおり、「未出現」をそのまま「禁止」に昇格させませんでした。代わりにライターがキャラクター設定に照らして「現代的な感嘆詞・罵り言葉・神秘主義」だけを禁止として確定し、残りの未出現語彙は保留としました。
[ライターの再依頼]
"운명", "예언" は禁止で確定する。意味の重なる神秘主義語彙をあと10個抽出して。
ただ、K_007は学者として反証・批判の文脈では引用する可能性もあるから、その例外ケースも
1行で一緒に表示して。
この最後の再依頼が重要です。禁止を機械的に広げると、「学者が迷信を批判しながら『運命などというものは』と言う」正当なセリフまで塞がれます。そこで、禁止には例外となる文脈を一緒に定義させます。AIが候補を広げ、ライターが境界線を引く。この一周を回してはじめてvoice_profile_k_007が確定し、L1に固定されます。
根拠の引用を強制し(「番号で引用」)、主観的な形容詞を禁止すれば(「重厚な・冷たい、の禁止」)、AIのハルシネーションが減り、ライターが検証できる表面が生まれます。profileはAIが書くものではなく、AIが草案を敷き、ライターが固定するものです。
voice_profileがファイルとして存在すれば、新しいセリフが上がってくるたびに自動照合が可能になります。5つの検査のうち実戦で効果が大きいのは、禁止語彙のマッチング(語彙が禁止リストに引っかかるか)と語彙領域の違反(絶対に使わない単語群に入っているか)の2つです。文の長さの逸脱・敬語の欠落・頻出語彙の比率は偽陽性(false positive)が多いため、補助としてだけ使います。回想シーンの1行が平均の長さを揺らすことまで全部拾うと、ライターが警告に鈍感になります。
voice_lintは章の新規セリフの束を受け取り、次のようなレポートを出します。
voice_lint 結果 (ch04 新規セリフ32行, profile=voice_profile_k_007)
─────────────────────────────────────────────
[위반] dialogue_id_412 — K_007
内容: "와, 진짜 대박이네요!"
理由: 禁止語彙 "와", "대박" (profile §5)
→ ライターレビュー必要
[의심] dialogue_id_421 — K_007
内容: "그 운명은 받아들이기 어렵습니다."
理由: 禁止語彙領域 "운명" (profile §5)
ただし '反証・批判の文脈' の例外可能 — ライター判定
→ ライターレビュー必要
[정상] 30件のセリフ
─────────────────────────────────────────────
要約: 위반 1 / 의심 1 / 정상 30
違反([위반])は赤、疑い([의심])は黄色です。どちらもライターの判定を経なければ通過できません。ここに絶対原則が1つあります — voice_lintは自動では拒否しません(5.2の原則の延長です)。上のdialogue_id_421を見てください。「運命(운명)」は禁止語ですが、学者が迷信に反論する文脈なら、正当な引用かもしれません。その判断はツールにはできません。自動拒否型のlintはこの微妙な場所を全部塞いでしまい、ライターからトーンを磨く機会まで奪います。lintは疑わしい箇所を照らす懐中電灯であって、扉に鍵をかける錠ではありません。
本章の背骨は、この1枚の図です。セリフ1行がライターの手を出発して声優の口に届くまでの間、レビューゲートが段階ごとに敷かれます。そして、その流れの真ん中に太い点線があります。
flowchart TD
A["ライターによる本文執筆(L2)"] --> B["voice_lint自動
違反・疑いレポート"]
B --> C["ライターのセルフレビュー(15分)"]
C --> D["ナラティブリードのサンプルレビュー
章あたり10%サンプル"]
D --> E["L3 dialogue_id発行
+ 翻訳キーマッピング"]
E --> F["翻訳・ローカライズレビュー"]
F -.->|"━━━ 可逆/不可逆の境界 ━━━
この線の上はテキスト、無限に修正可能
この線の下は音源、修正不可"| G
G["VAキャスティング"] --> H["ボイス収録(最終・不可逆)"]
H --> I["音源のビルド反映"]
classDef reversible fill:#e8f4ea,stroke:#3a7d44,stroke-width:1px,color:#1b3a22;
classDef irreversible fill:#f7e3e3,stroke:#b23b3b,stroke-width:2px,color:#5a1414;
class A,B,C,D,E,F reversible;
class G,H,I irreversible;
緑は可逆の段階、赤は不可逆の段階です。点線の上(緑)はすべてテキストです。セリフが1行気に入らなければ、キーボードで直せば済みます。コストはライターの数分です。点線の下(赤)は音源です。声優がブースでその行を読み、音源がビルドに入った瞬間、そのセリフはアセットとして固定されます。直すには同じ声優の同じコンディション、同じスタジオをもう一度押さえる必要があり、出演料・スタジオ・ディレクターの時間が最初とまったく同じだけ、またかかります。日程が厳しければ、同じ声優の追加セッション自体が押さえられないこともあります。
だから、たった1つのルールがすべてのワークフローを支配します — すべてのレビューゲートは点線の上で終わらせる。 収録はレビューの段階ではありません。レビューがすべて終わった結果を、アセットとして固定する段階です。点線の下で「このセリフ、おかしくないか」という疑問が生まれたら、それはさらにレビューすべき場所ではなく、上流のレビューが漏れていたというシグナルです。懐中電灯は点線の上ですべて照らし切らなければなりません。ブースは、暗くてもいい場所ではなく、暗くてはいけない場所なのです。
図の真ん中で、リードのサンプルレビューが「章あたり10%サンプル」と設定されています。この比率はレビュー時間と精度のバランス点です(著者の運用値、未検証の推定)。5%を下回ると事故が漏れ、20%を超えるとリード1人がボトルネックになります。lintが違反・疑いを先に濾し取ってくれるため、サンプルはlint通過分の中から抽出します — 人の目は、ツールには捕まえられない文脈の誤り(例:正当に見える「運命」の引用が、実はキャラクター崩壊)に集中します。
キャラクターが最後まで同じ話し方をすると、プロットが停滞します。仲間の死を経験した学者がその前とまったく同じトーンで話したら、むしろ嘘になります。変化が意図されたものなら、voice_profileも一緒にバージョンを上げます。
---
character_id: K_007
voice_profile_versions:
- v1: ch01~ch05 (初期 — 感情抑制、短文の学者)
- v2: ch06~ch10 (仲間の死後 — 感情表現の頻度増加)
- v3: ch11~ (覚醒後 — 直接話法の登場)
---
バージョンごとにprofileファイルが別にあり、voice_lintは検査対象のセリフの章番号を見て、どのバージョンを適用するかを選びます。ch07のセリフにv1の「感情抑制」ルールを当てると、まともな変化のセリフが軒並み疑いとして上がってきます。変化はバグではなく、設計です。
バージョンを上げるシグナルは3つです。ライターが意図的にトーンを揺らすなら、新規バージョンを発議してリードと合意します。voice_lintの疑い件数が1人のキャラクターでだんだん増えていくなら、それはライターが無意識のうちにトーンを移しているという — バージョン更新の時期が来たというシグナルです。character_bibleに変化イベント(死・覚醒・裏切り)が追加されたら、profile更新のalertが上がります。ただし、1人のキャラクターが章ごとに変わると一貫性が崩れるため、現実的なバージョン数はキャラクターあたり2〜4個です。
[方向標識 — ボイス空間でキャラクター間を見るなら(まだ時期尚早)]
voice_lintがルールによって「1人のキャラクターの中」の一貫性を守るものだとすれば、キャラクターごとの実際のセリフの束(1人のキャラクターが話したセリフ全体)を1つの点として埋め込んだ「ボイス空間」は、「キャラクター間」が十分に離れているかを見ます — 点同士が寄り固まっていくなら、それが§5.3.1・§5.4.1の指摘したボイスの平準化・収束の直接の測定値になります。ただし距離のしきい値を絶対値で固定せず、「寄り固まりつつある」という方向標識としてだけ読むべきであり、voice_lintを置き換える判定ゲートではありません(この発想は§8.2.7の次元ベクトル圧縮と同じ位置にあり、概念の直観は付録Mに地図1枚で解説してあります — 処方箋ではなく方向標識です)。
セリフが多言語に翻訳され、声優のボイスが乗ると、管理単位は掛け算式に増えます。韓国語の1行が英語・東南アジアの言語へと枝分かれし、そのそれぞれにトーンが乗ります。
翻訳の一貫性でもっともよく漏れるのは、同じ表現が章ごとに違う訳になることです(翻訳メモリの一貫性チェックで拾います)。キャラクターのvoice_profileが翻訳に反映されないこと(キャラクター別の翻訳ガイドを別途付けます)、新規語彙が用語集に未登録であること(用語集lint)がそれに続きます。翻訳ガイドはvoice_profileから自動生成します — 「このキャラクターはフォーマルな敬語100%、感嘆詞なし、神秘主義語彙は禁止」を翻訳指示書の先頭に自動で付けます。翻訳者がその学者を英語に移すとき、同じ境界線を見ることになります。
VA(Voice Actor、声優)のレビューは、点線の下に触れる直前 — テキストとして可逆な最後の段階のレビューです。トーンの一貫性(怒り・悲しみの表現の強さ)はディレクターとナラティブが、発音の正確さ(固有名詞)は用語集の担当が、呼吸・区切り(profileの「短い途切れが多い」のような指示)はディレクターが見ます。レビュー結果はvoice_review_log.md(L4)に通過・差し戻しとして記録し、次のキャラクターのキャスティング時に参照します。
差し戻しは、できる限りキャスティング・収録の直前までに終わらせます。ブースの中で見つかる台本の誤りは、その日のセッションを丸ごと崩し、次のセッションの日程まで揺らします。かといって、収録を遅らせながらレビューを引きずるのが答えでもありません。レビューがブース直前で頻繁に詰まるなら、それは上流(ライター・リード)のワークフローが遅いのであって、収録日程の問題ではないのです。
プロジェクトAで、voice_profile+voice_lintの導入前後を6か月追跡しました。絶対件数は著者の推定(未検証)です。方向と比率だけを信頼してください。
| 項目 | 導入前 | 導入後 | 方向 |
|---|---|---|---|
| 章あたりのボイス事故(リリース後) | 5〜8件 | 1〜2件 | 約1/4 |
| 新規NPCのボイス定着 | 3章分 | 1章分 | 1/3 |
| ライター1人あたりの管理NPC数 | 約15人 | 約40人 | 約2.5倍 |
| 翻訳の一貫性事故(章あたり) | 10〜15件 | 2〜4件 | 約1/4 |
| ボイスレビュー時間(章あたり) | 3日 | 1日 | 1/3 |
もっとも意味のある行は、ライター1人あたりの管理NPC数です。約2.5倍というのはライターを減らしたという意味ではなく、同じライターが章あたりのNPCの多様性を増やせるという意味です。世界が、より賑わうのです。
コスト構造を見ると、導入コストよりも運用コストのほうがはるかに小さくなっています。voice_profileの作成はメイン7人にライター2週間、voice_lintツールは開発1〜2週間に保守が月1日。運用側は、章あたりライターのセルフレビュー15分、リードのサンプルレビュー約2時間(10%サンプル)、変化のある章のprofile更新がキャラクターあたり1〜2日です。運用コストが小さくなければ、システムは生き残れません。運用が重いツールは、一四半期のうちに静かに廃棄されます。
| パターン | 処方箋 |
|---|---|
| profileに抽象的な描写だけ(「重厚な」) | 5項目ごとに実際のセリフ例を強制 |
| 最初から50人フル作成を狙う | メイン7人はフル作成+残りは本文の蓄積から逆抽出 |
| voice_lintが自動拒否型 | 違反・疑い+ライター判定。拒否は人だけ |
| 禁止を機械的に広げる | 禁止に例外の文脈(「反証の引用は可」)を併記 |
| キャラクター変化時にprofile未更新 | バージョン管理(v1・v2・v3)、章番号で適用 |
| 翻訳にprofile未伝達 | profileから翻訳ガイドを自動生成 |
| 収録後にセリフ修正を試みる | 収録は不可逆。レビューは点線の上で終結 |
| 収録日程にレビューを圧縮 | 上流のワークフロー改善で解く |
| profileを頭の中だけに保管 | 必ずファイル化。頭の中はライター交代で消える |
新しい章のセリフが上がってきたとき、profile 1つで一周回す最小の手順です。
setup
1. 対象キャラクターのvoice_profile_<id>.mdを開きます。なければ本文のセリフを20〜30行集めます。
2. 新規セリフをid / 캐릭터 / 내용(id/キャラクター/内容)形式のプレーンテキストの束として準備します。
prompt
K_007の voice_profile §5(禁止表現)だ。
[禁止リストを貼り付け]
ch04 新規セリフ32行。
[id / 内容 の形式で貼り付け]
各セリフを [위반](禁止語彙を直接含む) / [의심](禁止領域に触れるが例外文脈の可能性)
/ [정상]に分類して。[위반]·[의심]は id·内容·理由を表に。判定は私がやるから
自動で拒否はしないで。
verify 1. まず[위반](違反)から見てください。明白ならテキストを直します(点線の上なので無料です)。 2. [의심](疑い)は文脈で判定します。正当な引用なら通過、そうでなければ修正します。 3. 通過分から10%をサンプルとしてリードに送り、文脈の誤りをもう一度濾します。 4. すべての判定が終わった後にのみdialogue_idを発行し、収録キューへ回してください。ブースの手前では、もう検査しません。
一人ミニ版 — ツールを作れない個人開発者なら、voice_profileをキャラクターあたり§5(禁止表現)の1項目だけ書いてください。新規セリフを書くたびにその禁止リストをプロンプトの先頭に付け、AIに「このリストに引っかかる行だけ表示して」と指示します。ツールなしでも、プロンプト1行でlintの80%が得られます。収録(またはTTS)に回す前にこの一度だけ通せば、ブース手前の点線は守られます。
月曜の朝の企画会議。ホワイトボードに一行が書かれています。「リリースまでにサイドクエスト1,000本」。誰かが電卓を叩きます。ライター1人が1本に1日をかけると4年。5人がかりでも1年近くかかります。部屋の空気が重くなります。24年間この部屋に座り続けてきた私は、この数字を前に人々がいつも同じ二手に分かれることを知っています。一方は「分量を減らそう」と言い、もう一方は「ツールで量産しよう」と言います。そして決定は、ほぼ毎回その両方でした。
プロシージャルコンテンツ生成(Procedural Content Generation、以下PCG)は、その「ツールで量産しよう」側の古くからの答えです。ダンジョンの部屋配置、武器オプションの組み合わせ、敵スポーンプールは、20年前からルールブックと確率テーブルで自動化されてきました。新しいのはPCGそのものではなく、自然言語・画像・物語が入る場所にLLMと生成モデルが載ってきたという点です。
ただし本書で語ろうとしているのは、「AIをPCGに付けましょう」ではありません。それは誰でもやります。問題はどこに付けるかです。コンテンツの一かたまりを置いて、それが自動化のどの強度と、構造のどの層で出会うのかを一つのマスとして確定しないと、ツールはあるのに居場所がない状態になります。本章では、その一マスを座標として描く方法、そしてそのマスの上でコンテンツ一つが実際にパイプラインを一巡する様子を見ていきます。
伝統的なPCGは決定論に強い領域です。同じ入力には同じ出力が返り、検証が可能です。ダンジョンの部屋グラフ、武器オプションのprefix・suffix、敵スポーン分布は、だからこそ早くから定着しました。「炎の剣+5」は20年前にも自動で出ていました。
問題は、常にその次の場所でした。部屋は配置されても、部屋の中のNPCの名前・外見・短い背景はライターの手に残りました。「炎の剣+5」は出てきても、「王が失った最後の剣」という一行は出てきませんでした。クエストジェネレーターが目標と報酬の組み合わせを出してくれても、「なぜこのクエストをやるのか」は人が書いていました。
規模の大きいゲームでは、この場所が常にボトルネックでした。量産可能な領域と人手が必要な領域の比率はおよそ4対6で、その人手側の6が日程の大半を食っていました。量産ラインが4の側を速く作っても、6の側が追いつかなければ、サイクル全体がその速度に縛られます。
LLMと画像モデルが入ってくる場所が、まさにそこです。ルールブックが扱えなかった自然言語・物語・ビジュアルの領域まで、量産可能な範囲が広がります。だからといって、その場所を丸ごとAIに渡すのが答えではありません。AIは毎回少しずつ違う答えを出し、コンテキストが空だと「一般的なRPGの平均値」を吐き出します。だから結合点の設計が必要です。結合点は、二つの座標軸で定義されます。
縦軸は、人とルールブックとAIをどんな比率で混ぜるかです。私が働くとあるMMORPG開発会社(以下「プロジェクトA」)では、四段階に区切って使っています。
L0 — 完全手作業。 すべての文字・決定が人の手から生まれます。メインクエストの本文、シグネチャーキャラクターのセリフ、分岐エンディング。一貫性と物語の深さがゲームのアイデンティティに直結する場所です。
L1 — ルールブック自動化。 伝統的なPCGの場所です。ルールブック・確率テーブル・BSPのような決定論アルゴリズムが出力を作り、人はチェックだけを行います。ダンジョンの部屋配置、武器オプションの組み合わせ、敵スポーンが代表例です。
L2 — ルールブック+AI補助。 ルールブックが骨格を組み、AIがディテールを埋めます。サイドクエストのシノプシス、一般NPCの名前・短い背景、狩場の紹介文。人は入力メタデータと最後のレビューゲートだけに責任を持ちます。
L3 — AI優先+人によるレビュー。 AIが本文を作り、人はレビューだけに入ります。魅力的ですが、非決定性・ハルシネーション・一貫性毀損のリスクがすべてここに集まります。
核心はL2です。L1の安定性とL3の量産力を組み合わせ、二つの領域の短所は検証ゲートで防ぎます。L3は早く導入したくなる誘惑が大きいのですが、レビュー負担が膨れ上がって1〜2四半期のうちに廃棄される事例を何度も見てきました。100本のうち70本が要確認項目として上がってくるなら、人が最初から100本書くほうが安上がりです。
縦軸だけでは、量産ラインは回りません。コンテンツそのものが層に分解されていてこそ、自動化が入り込む場所が生まれます。これが第5部で扱ったLayer分解であり、コンテンツ分野における横軸です。五つの階層がそれぞれプロシージャル生成における一つの役割(アンカー・ルールブック・本文・数値・ゲート)に対応するという一般論は§2.3.6で扱ったため、ここではコンテンツ量産ラインにそのまま当てはめます。Layer 0のビジョンはトーン・世界観のアンカー(生成のたびに注入)、Layer 1のシステムは生成ルールブック(規則・確率テーブル・タグ体系)、Layer 2のコンテンツは生成結果が積み上がる本文の場所(サイドクエスト・NPCの背景・都市の紹介文)、Layer 3のデータは数値・ID・関係(報酬・スポーン・カーブ)、Layer 4のビルド・QAは検証ゲート(lint・一貫性チェック・ライターによるレビュー)です。
この二つの軸は、別の話です。縦軸は「人がどれだけ手を入れるか」、横軸は「コンテンツのどの部位か」を語ります。しかし、二つは掛け算でしか意味を持ちません。コンテンツ一つを二つの軸の交差点、すなわち一つのマスに確定したとき、初めて「これは誰が、どこを、どう作るのか」が決まります。
ここまで文章でほどいてきた二つの軸を、一枚のグリッドとして重ねてみます。横はコンテンツのLayer、縦は自動化強度です。各マスに入ったラベルは、プロジェクトAで実際にそのマスを占めているコンテンツです。色が濃いマスほど、量産ラインの重心に近い場所です。
このグリッドが本章の核心です。散文として散らばっていた「メインはL0」「サイドはL2」「報酬はルールブック」といった判断が、一つの座標に集まります。会議で新しいコンテンツが議題に上がったら、「これはどのマスか」という一つの質問で済みます。マスが決まれば、そのマスの縦座標が「誰が手を入れるのか」を、横座標が「どの部位なのか」を教えてくれます。
グリッドを読んでいると、二つのことが目に入ります。第一に、重心(濃いマス)はL2行×Layer 2列にあります。サイドクエストの骨格・NPCの背景がその場所です。量産ラインの心臓部です。第二に、一つのコンテンツが一つのマスにだけあるわけではありません。サイドクエストは本文(Layer 2)がL2のマスにありますが、その報酬数値(Layer 3)はL1のマスへ下ります。同じクエストでも、部位ごとに違うマスに住んでいます。これが、二つの軸を分離しておいた理由です。
重心のマス — L2行×Layer 2列、サイドクエストの骨格 — の上で、コンテンツ一つが実際に一巡する様子を見ます。流れは次のとおりです。
flowchart TD
A["Layer 0 ビジョン注入
(トーンアンカー: 東洋風の田舎の村)"] --> B["Layer 1 ルールブック
(目標・報酬スロットの骨格生成)"]
B --> C["L2 AI補助
(NPCの動機・一行背景の補完)"]
C --> D{"Layer 4 レビューゲート
(トーン・一貫性lint)"}
D -->|合格| E["Layer 3 データ確定
(報酬数値はルールブックが算出)"]
D -->|差し戻し| B
E --> F["ビルド反映"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class B,D code;
class C ai;
class E data;
class F pass;
この流れを、ワークド・トランスクリプトで一度たどってみます。田舎の村のおつかいクエストを一つ作るとしましょう。
ステップ1 — Layer 1のルールブックが骨格を出します。 ルールブックは決定論です。スロットを埋めただけの形で出てきます。
[クエスト骨格 — ルールブック出力]
地域: 향촌_3
依頼人役割: 薬草採り
目標タイプ: 収集(薬材 x6)
妨害要素: 狼スポーン(夜)
報酬スロット: { gold: ?, exp: ?, item: 採集鎌 }
ステップ2 — AIにディテールを依頼します。 プロンプトの全文は次のとおりです(Layer 0のビジョンをコンテキストとして先に入力します)。
あなたは東洋ファンタジーの田舎の村を舞台にしたMMORPGのクエストライターです。トーンアンカー:「素朴だが迷信と恐れが底に流れる田舎」。以下の骨格を受け取り、依頼人の動機一行とNPCの名前、そしてプレイヤーに見える依頼文二文を書いてください。報酬数値は絶対に作らないこと(システムが決めます)。出力はJSON。 骨格:{ 地域: 향촌_3, 依頼人: 薬草採り, 目標: 薬材6個の収集, 妨害: 夜の狼, 報酬アイテム: 採集鎌 }
ステップ3 — Claudeの生の出力。(要約せず、そのまま載せます)
{
"npc_name": "삼월 어멈",
"motive": "夜ごとの狼の遠吠えで薬草畑が荒らされ、最後の薬材を採りに行く気力が出ない。",
"quest_text": "夜になると狼どもが薬草畑を荒らしていくのじゃ。怖くて足がすくんでしもうて、山すその薬材六株だけ、わしの代わりに採ってきてくだされ。",
"reward": { "gold": 120, "exp": 300 }
}
ここで立ち止まって見るべきものがあります。AIはrewardフィールドを、指示していないのに作って入れました。これこそが、1軸と2軸がなぜ分離されるべきかをそのまま見せてくれます。報酬数値(Layer 3)はL1ルールブックの場所であって、AI(L2)の場所ではありません。AIにそれを任せると、呼び出しのたびに数字が揺れて、報酬カーブが崩れます。
ステップ4 — 人による検証・拒否。 レビュー担当者は二つのことを行います。(1) rewardフィールドを削除します — これはルールブックが埋めるマスです。(2) トーンを見ます。NPC名「삼월 어멈」(サムウォル・オモム、「三月ばあさん」の意)、動機の一行、依頼文の二文は村のトーンに合っています。合格です。もしAIが「魔法使いギルドの依頼」のような世界観の外の単語を入れていたら、ここで差し戻して骨格の段階に戻します。
ステップ5 — Layer 3のデータ確定。 削除された報酬スロットを、ルールブックが埋め直します。地域レベル・目標難易度に紐づいた決定論の公式です。gold: 85, exp: 240。AIが勝手に吐いた120・300ではなく、カーブに合った値が入ります。
この一巡が、重心マスの標準サイクルです。ルールブックが骨格、AIが肉付け、人がゲート、ルールブックが再び数値。コンテンツ1,000本が、すべてこのサイクルを回ります。マスが決まっているので、毎回「これは誰が作るのか」を議論し直すことはありません。
新しいコンテンツをグリッドのどのマスに置くかを決めるには、五つの質問が役に立ちます。会議で量産の議題が上がるたびに書き留めて一緒に答えてみると、マス配置の一貫性が1四半期のうちに定着します。
一つ、量産の負担はどれほどか。リリースまでにN本必要か。Nが100を超えると、L0行はほぼ不可能です。
二つ、一貫性の要求はどれほど大きいか。コンテンツ間の一貫性が体験の核心ならレビューゲート(Layer 4)が強くなければならず、多様性が核心なら、より上の行へ行く余地があります。
三つ、非決定性を許容できるか。毎回少しずつ違う結果が豊かさを生む領域なのか、同じ結果が信頼の核心である領域なのか。
四つ、レビューコストはどれほどか。コンテンツ1本あたり5分なのか30分なのかが、運用サイクルの長さを決めます。
五つ、事故が起きたときのコストはどれほどか。廃棄・書き直しが自由なのか、一度世に出たらユーザーに影響する事故に直結するのか。
サイドクエストにこの五つを投げると、答えは一方向に集まります。1,000本以上(L0不可)、一貫性はメインより低い、非決定を許容、レビューは5〜10分、事故コストは低い(個別廃棄が可能)。五つの答えが揃うので、L2行×Layer 2のマスが自然です。同じ五つをメインクエストに投げると、正反対に集まります。50本、一貫性・物語の深さは最高、非決定は不許可、レビューコストは大きい、事故コストは非常に大きい — L0のマスです。
グリッドを描いておいても、はまる落とし穴は似ています。四つが繰り返されます。
第一に、L3行から始めるケース。「AIが勝手に100本」という期待で出発すると、レビューが爆発的に増えます。L1のマスを先に定着させ、L2へ上がり、L3は一部にだけ慎重に。先ほどのミニパイプラインで報酬フィールドを人が消したあの一動作が、L3行がなぜ危険なのかを小さく見せてくれます。
第二に、ルールブックなしで丸ごとAIに委任するケース。「サイドクエストを100本作って」は、一般的なRPGの平均値を呼び込みます。Layer 1のルールブックが骨格を先に組み、AIがその上で肉付けをしてこそ、「私たちのゲーム」のコンテンツが生まれます。ルールブック一冊を書く仕事は、PCGで最も手間がかかり、最も面白くない作業ですが、これを飛ばすと、その上のすべての量産が平均値に沈み込みます。
第三に、レビューゲート(Layer 4)が空のケース。 AIの出力が自動でビルドに反映されると、一貫性インシデントに直結します。どのマスであれ、人のゲートは必須です。
第四に、コストだけを見てツールを決めるケース。 LLM APIのコストは四半期ごとに下がりますが、一貫性インシデントのコストは下がりません。ツールの決定は、APIコストに一貫性・レビュー時間の合計を加えて見ます。
プロジェクトAでサイドクエストをL0のマスからL2のマスへ移した後、6か月を測定しました。以下の数値のうち絶対値は著者の推定(未検証)であり、変化の方向と比率が実測で観察された部分です。
| 項目 | L0の時期 | L2転換後 |
|---|---|---|
| ライター1人あたりクエスト1本の作成 | 約4時間 | 約50分(メタ30分+AI 5分+レビュー8分) |
| 週あたりの量産 | 5本 | 30〜40本 |
| 廃棄率 | ほぼ0% | 約20% |
| 一貫性インシデント(四半期あたり) | 3〜5件 | 5〜8件(補強後は正常) |
| ライター満足度(10点満点) | 8 | 6 → 7(ポリシー補強後) |
廃棄率は20%に上がりましたが、量産速度が6〜8倍なので、正味のスループットは4〜5倍に増えました。一貫性インシデントは四半期あたり5〜8件へと小幅に増えましたが、レビューゲートとルールブックの補強で、四半期のうちに正常範囲へ戻りました。
最も大きな変化は、数字ではなく人でした。最初はライターたちが「量産のレビュー係」になった気分だと言い、満足度が8から6に落ちました。これを回復するために、メインクエストとシグネチャーサイドクエスト(都市あたり1〜2本)にライターの時間を明示的に保証するポリシーを挟みました。量産ラインがライターの時間を吸い上げるのではなく、その時間をメインへ送り返すツールになるよう、はっきり決めたのです。6か月後、満足度は7に戻りました。
この測定から持ち帰るべきことは一つ。マスを移す決定は、スループット・ライターの時間配分・満足度が一緒についてこなければなりません。スループットだけを見れば量産は成功ですが、人は去っていきます。
Layer分解がプロシージャル生成の前提だという一般論は§2.3.6にあります。ここでは、それがPCGグリッドの上でどう現れるかだけを見ます。横軸(Layer 0〜4)が曖昧なチームでは、どのマスも安定して回りません。Layer 0のビジョンがどこにあるか分からなければ、ジェネレーターごとにトーンアンカーが空になって一般的なRPGの平均値が出てきますし、Layer 1のルールブックとLayer 2の本文が一つのファイルに混ざっていれば、規則一行を直すたびに本文の数十か所を一緒に触らなければならず、Layer 3のデータが本文に書き込まれていれば、報酬カーブの一度の調整にライターが1週間を使います — 先ほどのミニパイプラインで報酬を別のスロットとして切り離しておいた理由が、これです。
だからPCG導入の前に点検すべきは、ツールの選択ではなく、横軸が分解されているかです。5層が揃ったチームでL1のジェネレーターを付けるコストは、ライター1人の1四半期分です。5層が混ざったチームで同じ導入をすると、2四半期のうちに一貫性インシデントで廃棄されます。
最初から五つのマスが完璧である必要はありません。分離は漸進的に、インターフェースは狭く。最初の四半期にはLayer 0のトーン一行とLayer 1のルールブック一冊だけを切り離しておくだけでも、ジェネレーターが入る場所が開きます。だからといって、無限に先送りしてよいという意味ではありません。Layer 2の本文とLayer 3のデータが最後まで一かたまりのままなら、次章の具体的なツールも居場所を持てません。
次章では、このグリッドの重心マスを占める具体的なツールを一つ解剖します。都市別の狩場を量産するproj_city_hunting_generatorです。入力メタデータ・ルールブックの骨格・AI本文・検証ゲートが一つのサイクルにどう束ねられるのか、本章のミニパイプラインが実際のツール規模でどう大きくなるのかを見ます。
setup. 量産候補のコンテンツを1種類選んでみましょう(例:サイドクエスト)。Layer 0のトーン一行と、Layer 1のルールブック骨格(スロット定義)を別ファイルとして切り離しておきます。報酬数値のスロットは、ルールブック側に空けておきます。
prompt. ビジョンをコンテキストとして入力した後に骨格を渡し、「報酬数値は作らないこと、出力はJSON」を明示しましょう。先ほどのステップ2のプロンプトを、そのまま変形して使えば大丈夫です。
verify. 三つを確認しましょう。(1) AIが報酬フィールドを勝手に入れていたら削除します(L3はルールブックの場所です)。(2) 世界観の外の単語があれば、骨格の段階へ差し戻します。(3) 合格分だけ、ルールブックが報酬数値を埋めてビルドに入れます。
一人ミニ版。 チームがなくても大丈夫です。自分でルールブック一冊(スロット5個)とトーン一行だけを、テキストファイルとして作ってみましょう。クエスト10本を先ほどのサイクルで回してみて、レビューで何本を差し戻すか数えてみてください。差し戻し率が30%を超えるならマスが間違っているということなので — ルールブックの骨格をより細かくするか、一つ下の行(L1)へ下げてもう一度見てみましょう。差し戻し率が安定すれば、それが自分の規模でそのマスが機能しているという合図です。
想定読者:コンテンツ量産を担うMMORPGプランナー(中規模(10〜50人)チーム) ソロ/趣味の読者向け縮小版:§6.2.10「一人ならここまで」
リリースまでに30の都市が必要だというスケジュール表を初めて受け取った日の計算を、いまだに覚えています。1つの都市は、紹介文5〜10行、狩り場3〜5か所、狩り場ごとに5〜10人のNPCと2〜3本のサイドクエスト、特産アイテム1〜3種、都市ボス1体で構成されます。手作業で1つの都市を作り込むと1〜2週間かかります。30都市なら、シナリオライター1人が6か月を丸ごと都市だけに費やす計算です。
ところが、その6か月はありませんでした。ライターの時間はメインクエストとシグネチャーキャラクターに割かれており、30の都市はその作業と並行して進める必要がありました。「AIに30の都市を作ってもらえばいいのではないか」という最初の衝動は、すぐに崩れました。丸ごと任せると、互いによく似たファンタジーの村が30個出てくるのです。本章では、その衝動の代わりに作ったツールcity_hunting_generatorが、入力・ルールブック・AI・検証の4段階をどのように1つのサイクルにまとめたのか、そしてそのサイクルを実際に最後まで1回回すと何が出てきて何が破棄されるのかを見ていきます。
著者の実運用メモ 本章の
city_hunting_generatorは、著者が会社のR&Dフォルダで運用している実際のツールを匿名化したものです。ファイル名・コード構造・検証項目は実際のツールを忠実に写し、都市名(silvermarkなど)・会社固有の名称は書籍用に置き換えました。出力本文は実際のセッションを再構成したものです。
ツールの全体フローは4段階です。要は、第1段と第3段が決定論(ルールブック)で、AIなのは第2段だけだという点です。ルールブックが骨格と検証を両側から押さえていれば、間に挟まったAIが毎回少しずつ違う答えを出しても、都市間の一貫性は揺らぎません。人が関わるのは、最初の入力(メタデータ)と最後のゲート(レビュー)だけです。
flowchart TB
A["入力: 都市メタデータyaml
(人が15〜20分/都市)
lore_seeds 3個 ・ forbidden_names 自動付与"]
A --> B["第1段 決定論: rules.py
generate_skeleton()
狩り場数・敵分布・報酬カーブ・ボス"]
B --> C["第2段 AI: プロンプト3種
L0ビジョンのキャッシュ + L1ルール注入
+ L2隣接都市の本文
→ 紹介文・NPC・サイドクエスト"]
C --> D{"第3段 決定論: lint
名前重複・禁止語・ボイス
・分量・報酬範囲"}
D -->|違反alert| E["第4段 ライターレビューゲート
(5〜10分/都市)
破棄・再生成を決定"]
E -->|再リクエスト| C
E -->|通過| F["ビルド反映"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class B,D code;
class C ai;
class A,E human;
class F pass;
この図で人の手が入る場所は2か所だけです。一番上でメタデータ1ページをきれいに入れる場所と、一番下でlintには拾えないトーンや物語の判断を下す場所。その間の退屈な骨格生成と本文量産は、ルールブックとAIが回します。決定的な設計は、lint(第3段)が違反を見つけても自動では破棄せず、ライターゲート(第4段)にalertを上げるだけにしている点で、その理由は§6.2.5で見ます。
ライターは都市1つにつきメタデータを1ページ書きます。作成時間は15〜20分。短いですが、この1ページが続く3段階の入力のすべてです。
# city_021_silvermark.meta.yaml
city_id: city_021_silvermark
region: west
climate: cold_arid
dominant_faction: scholar_guild
cultural_tone: scholarly_strict
level_range: [25, 30]
lore_seeds:
- 100年前に魔法封印の中心地だった
- 封印弱体化の最初の兆候がこの都市で観測された
- 学者ギルド本部の所在地
neighbors: [city_018, city_023]
# forbidden_names: (スクリプトが自動付与 — ライターの入力は不要)
もっとも重要なスロットはlore_seedsです。3〜5個の中核となる出来事が都市のアイデンティティを決めます(silvermarkの例では「100年前に魔法封印の中心地だった」「封印弱体化の最初の兆候がこの都市で観測された」「学者ギルド本部の所在地」の3つです)。少なすぎるとAIはありきたりなファンタジー都市を吐き出し、多すぎると出来事同士が矛盾します。著者の経験では、3個がもっとも安定していました。
forbidden_namesはライターが記入しません。既存の都市・キャラクター名のリストをスクリプトが読み込み、メタデータに自動付与します。30都市 × 平均50人のNPCが積み上がると、1,500個の名前の重複を人の頭で検査するのは不可能だからです。「他の都市のNPCと被らないようにして」と毎回手で書く必要はありません。
ルールブックはメタデータを受け取り、都市の構造骨格を作ります。コードは単純です。
# city_hunting_generator/rules.py (骨格)
def generate_skeleton(meta):
region_rules = REGION_RULES[meta.region]
hg_count = region_rules.hunting_grounds_range.sample()
enemy_dist = ENEMY_RULES[meta.climate][meta.dominant_faction]
skeleton = {
"hunting_grounds": [
{
"id": f"{meta.city_id}_hg_{i}",
"level": meta.level_range[0] + i,
"enemy_types": enemy_dist.sample(k=3),
"reward_curve": calc_reward(meta.level_range[0] + i),
"npc_count": region_rules.npc_per_hg,
"sidequest_count": region_rules.sidequest_per_hg,
}
for i in range(hg_count)
],
"boss": {
"id": f"{meta.city_id}_boss",
"level": meta.level_range[1] + 2,
"pattern": BOSS_PATTERNS[meta.region],
},
}
return skeleton
結果は決定論的です。同じメタデータを入れれば同じ骨格が出てきます。報酬カーブがregion・levelごとの標準範囲内にあるか、敵分布がclimate・factionのルールに合っているかがコードで保証され、リグレッションテストで捕捉できます。この段階は絶対にAIに任せません。報酬カーブをAIが呼び出しのたびに違う数字で出してきたら、都市間のバランスがその場で崩れるからです。
silvermarkのメタデータを入れると、rules.pyは狩り場4か所(city_021_silvermark_hg_0〜hg_3)、各狩り場にNPCスロット6枠・サイドクエストスロット3枠、そしてレベル32のボス1体という空の骨格を返します。まだ名前も本文もない、埋めるべき枠の表です。その枠を埋めるのが第2段AIの仕事です。
ルールブックが骨格を作ったあと、AIがその上に自然言語の本文を埋めていきます。都市紹介文、NPCの名前・外見・短い背景、サイドクエストのシノプシス、特産アイテムのフレーバーテキストがここで生まれます。
呼び出しパターンは、コンテキスト注入の4層構造そのままです。L0ビジョン(world_premise + tone_manifesto)をキャッシュし、L1ルール(city_naming_rule + region_west_lore)を選択注入し、L2隣接本文(他都市のNPC名リスト)を加え、最後に作業指示を付けます。都市紹介文のプロンプトは、そのままコピーして使える形です。
[L0 コンテキスト] world_premise + narrative_pillar + tone_manifesto (キャッシング)
[L1 コンテキスト] city_naming_rule, region_west_lore
[入力] city_021_silvermark.meta.yaml + lore_seeds 3個
この都市の紹介文を6~8行で書いて。lore_seedsの3つすべてを自然に織り込み、
「平和な村」のようなRPGの常套句は抜きで。トーンは学者的かつ厳格に、感傷は抑制。
本文のみ、前置きや解説なしで。
このプロンプトの指示内容は、「この都市の紹介文を6〜8行で書くこと。lore_seedsの3つすべてを自然に織り込み、『平和な村』のようなRPGの常套句は排すること。トーンは学者的かつ厳格に、感傷は抑制。本文のみで、前置きや解説は不要」というものです。
同じパターンが、NPC量産、サイドクエストの呼び出しにもそのまま繰り返されます。違いはコンテキストと出力形式だけです。NPCの呼び出しにはforbidden_names(他都市のNPC名との重複禁止)スロットが入り、サイドクエストの呼び出しには「依頼主のNPCは上で生成したNPCの中から選ぶこと」という制約が入ります。
では、実際に何が出てくるのか。一度最後まで回してみます。
抽象的に「AIが本文を埋める」とだけ書いたのでは、このツールが実際に何を吐き出すのかは分かりません。silvermarkという1つの都市のサイクルを、入力から破棄まで一度最後まで追いかけます。
[第2段AI出力 — 都市紹介文](実際のセッションの再構成)
シルバーマークは封印の都市である。100年前、七人の学者が西方の魔力の氾濫を食い止めるためにこの地の地下に封印陣を築き、都市はその陣の上に石を一枚ずつ重ねて育っていった。学者ギルド本部の鐘楼は封印の脈を読む観測所であって、神に向かう尖塔ではない。ここ3年、観測記録には同じ一行が繰り返し記されている。「脈が冷えていく」。封印弱体化の最初の兆候は、ほかのどこでもない、この都市の計器盤で最初に震えた。街の学者たちはそれを大声では語らない。ただ、鐘楼の火が一晩中消えない日が増えただけである。
ここでは、lore_seedsの3つ(封印の中心地・弱体化の最初の兆候・学者ギルド本部)がすべて本文に盛り込まれ、「冒険者を歓迎する平和な村」のような常套句がなく、scholarly_strictのトーン(感傷の抑制、観測・記録の語彙)が維持されています。これが通過できる出力の姿です。
続いて、同じサイクルが狩り場hg_0のNPCスロット6枠のうち3枠を埋めます。
[第2段AI出力 — NPC 3人](実際のセッションの再構成)
1) ドレン・ベイル(Doren Vale) — 鐘楼の観測助手。50代、片目に真鍮の拡大鏡を常時装着。封印の脈の微細な振動を記録する仕事を30年続けている。口数が少なく、数字でしか会話しない。「今日12.4。昨日12.1。上がっている。良いことじゃない」
2) ミラ・コスト(Mira Kost) — ギルド文書庫の司書。30代、指のインク染みが消えない。封印陣の設計原本を守っているが、その図面を読める学者はもう皆死んだと信じている。外部の人間への警戒心が強い。
3) グレム(Grem) — 鐘楼の下の炉番。正体不明、年齢不詳。鐘楼の火を絶やさないことが唯一の仕事で、封印について尋ねる者には「火だけ見ていればいい」とだけ答える。(あいまいフラグ — AIによる自己申告)
3人目のNPC「グレム」に、AIが自らあいまいフラグを付けた点に注目してください。良いプロンプトは、AIが「これは確信が持てません」と言えるようにします。次は、第3段のlintがこの出力一式を検査します。
[第3段lint出力](実際の形式)
[PASS] 分量検査: 紹介文7行 (基準6~8) [PASS] 報酬範囲: hg_0~hg_3 reward_curve 標準範囲内 [WARN] 名前重複: "Mira Kost" — city_014_riverholdの"Mira Veldt"と 姓(Kost/Veldt)は相違だが名(Mira)が同一。forbidden_names 近接衝突。 [PASS] 禁忌語彙: tone_manifesto 違反0件 [WARN] ボイス一貫性: "グレム" の台詞 voice_lint 信頼度0.62 (しきい値0.70未達)
lintは違反を2件 — Mira Kostの名前の近接衝突(riverholdのMira Veldtとファーストネームが同一)と、グレムの台詞のボイス一貫性(voice_lint信頼度0.62、しきい値0.70未達) — 捕まえましたが、どちらも自動では破棄しませんでした。WARNとしてライターゲートに上げただけです。これが§6.2.1で予告した設計の核心です。チェッカーに自動拒否の権限まで握らせると、ライターたちは1〜2か月もしないうちにそのスイッチを切ってしまいます。機械が意図された変形まで一緒くたに殺し、その境界をライター自身が見極めてみる機会まで奪うからです。だから、疑わしい候補を拾い上げる仕事は機械に任せつつ、その候補を生かすか捨てるかの最後の判断だけは人の手に残しておきます。
[第4段 ライターレビュー — 判定と破棄]
ライターは2件のalertを次のように処理しました。
- Mira Kost → 存続。riverholdのMira Veldtと同じファーストネームだが、別の都市、別の姓で、同時に登場する可能性はない。意図的な変形として通過。(ただし、forbidden_namesのルールを「名+姓の完全一致」から「名単独の衝突もWARN」に広げるかどうかは別件としてメモ。)
- グレム → 破棄。 voice_lintの信頼度の低さがシグナルだった。読み直すと、「火だけ見ていればいい」という炉番のキャラクターはscholarly_strictな都市のトーンとずれていた。学者ギルドが支配する都市のNPCが神秘主義のトーンに流れると、都市のアイデンティティがぼやける。破棄して再リクエスト。
ライターが破棄を決めると、再リクエストが1回回ります。「グレムのスロットを破棄する。同じ狩り場の学者ギルドのトーン(観測・記録・厳格)に合う炉番のNPCとして再生成せよ。神秘主義の語彙は禁止」。AIは、鐘楼の炉の温度を記録し、火さえもデータとして見る老人を返してきて、その出力はvoice_lint 0.81で通過しました。入力→骨格→本文→検証→破棄→再生成という1つのサイクルが、ここで閉じます。
この一周が、本書全体のShowの基準です。ツールが何を吐き出し、何が引っかかり、人が何を切り捨てるのかを一度でも最後まで見なければ、「AIで量産した」という文は空虚です。
上のサイクルではNPCが1人破棄されました。都市全体で見ると、破棄はさらに積み上がります。レビュー時間は都市あたり平均5〜10分、破棄率はNPCが約20%、サイドクエストが約33%です。
この比率の算出根拠を正直に明らかにしておきます。破棄率は、導入初期にsilvermarkを含む5つの都市を直接レビューしながらカウントした値です。NPCはレビューした30人のうち6人を破棄(20%)、サイドクエストはレビューした15本のうち5本を破棄(33%)でした。サンプルが5都市と小さいため、精密な母集団の比率ではなく、「5つに1つ、3つに1つ」程度の方向値として読むのが正しいです。30都市すべてをレビューし終えたあとの累積比率は、これより低くなることも、狩り場の性格によって高くなることもありえます。
重要なのは、破棄率0%が目標ではないという点です。破棄0%は、レビューが形だけで流れたというシグナルに近いのです。5人のNPCのうち1人がトーンの不一致で破棄され、3本のサイドクエストのうち1本がlore_seedsと噛み合わず再生成されるとき、レビューゲートは実際に機能しています。
ツール導入の前後を比較します。下の時間の数値はsilvermarkを含む初期都市群の実測平均で、「導入前」の列はツール以前の手作業期のライターによる推定です。加工した数字はありません。
| 項目 | 導入前(手作業) | 導入後(実測) |
|---|---|---|
| 都市1つの作成時間 | 1〜2週間 | 約30分(メタ15分 + AI 5分 + レビュー8分) |
| 30都市の総期間 | ライター1人で6か月級 | 4〜5週間 |
| 破棄率(NPC) | —(全量直接作成) | 約20%(30人中6人) |
| 破棄率(サイドクエスト) | — | 約33%(15本中5本) |
| 一貫性の事故(都市あたり) | ほぼなし | 0〜1件 |
表だけ見ると数字がすべてのようですが、実際の効果は別のマスから生まれました。都市の量産に縛られるはずだったライターの時間が解放されたことで、ライター1人が四半期あたりのメインクエストの産出を大きく増やせました(正確な倍率は四半期ごとに変わるため断定しません — 方向としては「メインの産出が明確に増えた」です)。量産ツールがライターの時間を吸い取るのではなく、解放するツールとして機能したのです(ライターが「レビュー機械」になったと感じるとツールが拒否される、という§6.1.8の警告がそのまま当てはまります)。
自動化の幅が広がっても、次のものはツールの外に置きます。
| コンテンツ | ツールの外に置く理由 |
|---|---|
| メインクエスト本文 | 一貫性・物語の深さがゲームのアイデンティティに直結 |
| ボスのパターン・演出 | 視覚・インタラクションのディテールが多く、デザイナーが直接やるほうが速い |
| シグネチャーメインキャラクター | voice_profileのフル作成が必要で量産不可 |
| 分岐エンディング | ライターが直接決める領域 |
| 都市ごとのシグネチャーサイドクエスト1〜2本 | ライターが選んで直接作る |
量産できるという事実が、量産すべきだという決定に自動的につながってはいけません。silvermarkのサイクルで見たように、ツールはNPC 6人のうち5人まではうまく量産します。しかし、その都市の「封印が冷えていく」という核心の緊張を背負うシグネチャーNPCの1人は、ライターが直接作り込みます。自動化の境界が明確であれば、量産ツールはむしろその核心領域を守るツールになります。
| 失敗パターン | なぜ失敗するか | 処方 |
|---|---|---|
| lore_seedsを1〜2個しか書かない | AI出力が平均的なRPGに平準化される | 3個以上を強制(§6.2.2) |
| ルールブックなしでAIに丸ごと量産を依頼 | 「都市を30個作って」→似たような村が30個 | 第1段のルールブックは省略できない(§6.2.3) |
| lintなしでライターレビューだけに依存 | レビュアーが細かいルール違反の処理に時間を消耗 | まず1次の自動検証を(§6.2.5) |
| 命名の重複チェック漏れ | 1,500人の名前の重複チェックは人の頭では不可能 | forbidden_namesの自動付与(§6.2.2) |
| ライター満足度を計測しない | 処理量は増えてもライターの時間を奪えば拒否される | メインコンテンツの時間を明示的に保証(§6.2.7) |
5つ目がもっとも見落とされがちです。silvermarkのグレムを破棄したような判断をライターが楽しんで下せるためには、ライターに量産のレビュー以外に、自分の手で作り込む時間が残されていなければなりません。処理量だけ計測してライターの時間の計測を省くと、ツールはKPI上は成功しても、人は去っていきます。
一人ならここまで:ルールブックのコードはなくてもかまいません。自分のゲーム(または好きなゲーム)の都市・地域を1か所選び、§6.2.2の形式のメタデータを手で書き(lore_seeds 3個が核心です)、§6.2.4の紹介文プロンプトをそのまま貼り付けて一度回してみましょう。出てきたNPCの中からトーンの合わない1人を自分で選び、「このNPCは都市のトーンとずれている。破棄してやり直し」と反論してみると、レビューゲートがどんな判断の束なのかが体で分かります。
チームであれば、次の一歩から始めてみましょう。メタデータのyamlフォーマット1枚とforbidden_namesの自動付与スクリプトから作ります。ルールブックの骨格(generate_skeleton)とlintはその次です。入力フォーマットと名前の重複チェックの2つがあるだけでも、AIの本文量産が「似たような村30個」に崩れていく2つのよくある失敗を先に防げます。
6.3では、NPC Persona/Squadパイプラインを扱います。6.2のgeneratorがドレンやミラのようなNPCを個別に量産するのに対し、Persona/SquadはそのNPCたちをグループ単位で束ねます。1つの狩り場の5人のNPCが、互いに無関係な人形の集まりではなく、小さな社会として機能するようにする方法です。
主な読者: NPC・狩り場コンテンツに責任を持つMMORPGプランナー(中規模(10〜50人)チーム) 一人・趣味の読者向けミニ版: §6.3.10「一人ならここまで」
6.2のgeneratorで一つの狩り場にNPCを5人量産し、ゲーム内に並べてみた日のことを覚えています。名前・外見・短い背景はすべて埋まっていて、座標だけ打って配置しました。ところが、実際にその狩り場を歩き回ってみると、妙に死んでいました。5人が同じ空間にいるのに、互いに一度も言及しません。2人が同じ岩の上に重なって立っていました。誰かが商人の役割を担う必要があったのに、5人全員が学者でした。ドレンもミラも、個別にはまともなNPCだったのに、束ねてみると人形の寄せ集めでした。
これが人形博物館の状態です。個々のNPCは全部作ったのに、グループとして生きていません。本章では、その5人を小さな社会として束ねるパイプラインを扱います。中心となる分解はPersonaとSquadです。オフィスにたとえると、Personaは社員個人の名刺で、Squadは一つのチームの組織図です。名刺を50枚積み上げても、組織図がなければ会社は回りません。そして本章の背骨は最後の段階、すなわち束ねたグループが「互いに知り合いのように話し、動くか」をAIと一サイクル最後まで検証する場面です。
著者による実運用メモ 本章のSquadパイプラインは、著者が会社のR&Dフォルダで運用しているNPC Persona/Squadツールを匿名化したものです。yaml構造・検証項目・voice_lintのしきい値は実際のツールを忠実に移し、都市・NPCの名前は6.2と同じく書籍用に置き換えています。出力本文は、実際のセッションを再構成したものです。
Personaは個々のNPCのアイデンティティです。名前・外見・voice_profile・役割を持ちます。6.2のgeneratorが作るのがPersonaです。ドレン・ベイル、ミラ・コストが、それぞれ一つのPersonaです。
Squadは、そのPersonaたちをグループとして束ねる単位です。一つの狩り場に5人がどんな役割で分布し、互いにどんな関係で、どう動くのかを定義します。
| 単位 | 含む内容 | 作る主体 |
|---|---|---|
| Persona | 名前・外見・voice_profile・役割 | generator(6.2) |
| Squad | 役割分布・関係・動線 | Squadパイプライン(本章) |
この二つを分離しないと、二つの問題が同時に詰まります。Personaだけを量産すれば人形博物館になり、Squadから作ろうとすれば埋めるPersonaがありません。分離すれば、各単位の運用がシンプルになります。ただし、分離は断絶ではありません。要は二つの単位の間に再利用・検証の経路を敷いておくことで、それが本章の本論です。
このPersona→Squad分解は、単なる整理ではなく、もっと先へ進む道を開きます。NPCグループが役割・関係・数値として定型化されていてこそ、のちにワールド状態(プレイヤー行動の蓄積)がNPCの数値を揺らし、その数値がクエストの発現条件になる動的な反応性まで進めます。本章ではその進歩的適用の入口に触れるだけにとどめ、正面からは「人がレビューする保守的な量産」までを扱います。
Squadの骨格は、狩り場1か所につきメタデータ1ページから始まります。6.2の都市メタデータと同じ思想です。人は役割分布と関係の意図だけを決め、埋める仕事はルールブックとAIが担います。
# city_021_hg_3.squad.yaml
squad_id: city_021_hg_3_squad
hunting_ground: city_021_silvermark_hg_3
type: hunting_ground_residents
size: 5
roles:
- role: quest_giver
count: 1
voice_traits: [authoritative, scholarly]
- role: lore_keeper
count: 1
voice_traits: [scholarly, withdrawn]
- role: merchant
count: 1
voice_traits: [practical, dry]
- role: bystander
count: 2
voice_traits: [varied]
relationships:
- between: [quest_giver, lore_keeper]
type: mentor_and_former_student
- between: [merchant, bystander_1]
type: regular_customer
movement_pattern: stationary_with_shifts
最も重要なスロットはrelationshipsです。関係が0件なら、5人は最後まで赤の他人のままです。関係が多すぎると(5人に5件以上)、ユーザーが覚えることが増えて、かえって埋もれます。著者の経験では、5人のSquadに核心となる関係2〜3件が最も安定しています。voice_traitsは、5人が互いに異なる声を持つようにするための装置です。5人全員をscholarlyで埋めると、検証段階でvoiceの平準化として弾かれます。
まず、ルールブックがSquad骨格の標準を決めます。狩り場のregion・typeごとに、サイズ・役割分布・関係密度・動線パターンのデフォルト値がコードに入力されています。
# npc_squad/templates.py (抜粋)
SQUAD_TEMPLATES = {
("west", "hunting_ground_residents"): {
"size_range": (4, 6),
"role_distribution": {
"quest_giver": 1,
"merchant": 1,
"lore_keeper": (0, 1),
"bystander": (1, 3),
},
"relationship_density": 2, # 推奨される関係数
"movement_pattern": "stationary_with_shifts",
},
("east", "outpost_squad"): {
"size_range": (3, 4),
"role_distribution": {
"commander": 1,
"scout": 1,
"support": (1, 2),
},
"relationship_density": 1,
"movement_pattern": "patrol_loop",
},
}
この段階は決定論です。西部の住民Squadでquest_giverだけが5人という事故は、コードのレベルで不可能です。役割分布がルールを外れれば、その場で止まります。
次に、各スロットへPersonaを埋めます。道は三つです。プールに合うPersonaがあれば再利用し(登場ウェイト+1)、なければ6.2のgeneratorで新しく作り、メインクエストの核心人物ならライターが直接書きます。silvermarkのhg_3 Squadでは、quest_giver・lore_keeperを6.2ですでに量産したミラ・ドレンで埋め、merchantとbystander 2人を新しく生成しました。ここまでは6.2のgeneratorサイクルと同じです。本章の本当の仕事はその次、束ねたグループが本当にグループとして機能するかを検証する場面です。
抽象的に「AIが関係を補強する」とだけ書いても、このパイプラインが何を吐き出すのかは分かりません。silvermark hg_3 Squad一つの後半サイクルを、関係テキストの生成から破棄・再リクエストまで、一度最後まで追いかけます。
Squad骨格に入力された関係タグ(mentor_and_former_student)は抽象のままで、ゲーム内では見えません。これを一行の描写に変えてNPCのセリフ・イベントに埋め込むのが第3段です。プロンプトは、そのままコピーして使える形です。
[L0 コンテキスト] world_premise + tone_manifesto (キャッシング)
[L1 コンテキスト] city_021_silvermark.lore (学者ギルド支配, scholarly_strict)
[Persona 1] quest_giver — ミラ・コスト、ギルド文書庫の司書、30代、インクの染み
[Persona 2] lore_keeper — ドレン・ベイル、鐘楼の観測補助、50代、数字でしか話さない
[関係タグ] mentor_and_former_student
この二人(師匠–かつての弟子)の関係を、ゲームのセリフに使う背景として1~2行だけ描写して。
ドレンは数字、ミラは文書 — 二つの口調がぶつからないように。トーンは厳格な学者風で、
神秘主義や「古い友人」のような常套句は抜いて。本文のみ。
[第3段 AI出力 — 関係の一行](実際のセッションを再構成)
ドレンは20年前、ミラに封印陣の観測記録の表記法を教えた。今は立場が逆転し、ドレンが測定した数値を、ミラが文書庫の帳簿に書き写している。二人は毎週火曜日、観測値と帳簿が食い違う一マスをめぐって短く言い争う。
この出力は良い出来です。mentor_and_former_studentが具体化され、ドレンの「数字」とミラの「文書」が衝突せずに一つの場面(数値を帳簿へ書き写す)に束ねられ、scholarly_strictのトーンも維持されています。同じプロンプトが、merchant–bystander_1のregular_customer関係にも繰り返されます。
NPCが一日中同じ場所に立っていれば、また人形に戻ります。ルールブックが動線パターンを埋めます。stationaryは1か所固定(警備・ボス)、stationary_with_shiftsは8時間ごとに位置を微調整(一般)、routine_loopは時間割ベース(住民)、event_drivenはトリガー時のみ移動(クエストNPC)。これは決定論なので、AIは呼びません。
ここで、束ねた5人が本当にグループとして機能するかを叩きます。6.2のlintが個々のNPCを見たのに対し、このlintはグループの一貫性を見ます。
[第5段 Squad lint出力](実際の形式)
[PASS] 役割分布: quest_giver 1 · lore_keeper 1 · merchant 1 · bystander 2 (ルール充足) [PASS] 関係密度: 2件 (推奨 2, 充足) [WARN] voice多様性: scholarly系列 3/5 — quest_giver·lore_keeper·bystander_2 のvoice_profileコサイン類似度 0.83 (しきい値 0.80 超過)。平準化リスク。 [WARN] 動線衝突: 14:00~16:00 区間 merchant·bystander_1 座標半径 1.5m 重複 [FAIL] 関係露出: 関係2件が定義されたが、5人のセリフのどこにも他メンバーへの言及 0件。 関係がデータにのみ存在 — ゲーム内可視性 0。
lintが3件を拾いました。3件とも自動では破棄せず、ゲートに上げます — 疑わしい候補は機械が拾い上げ、生かすか捨てるかは人が決めるという、§6.2.5と同じ設計です。
[第6段 ライターレビュー — 判定と破棄]
ライターは、alert 3件を次のように処理しました。
- voiceの平準化(WARN) → bystander_2を破棄。学者の都市とはいえ、5人全員が学者口調では狩り場が単調になります。bystander_2を
practical, dryトーンの雑用係として再生成をリクエスト(quest_giver・lore_keeperが二人とも学者なのは都市のアイデンティティなので維持)。- 動線衝突(WARN) → ルール補正。merchantのshift開始オフセットを+2時間ずらして14:00の重複を解消。AI呼び出しなしで動線パラメーターのみ調整。
- 関係露出0(FAIL) → 最も重要な件。関係を2件も定義しておきながらゲーム内で一度も見えないなら、そのデータは死んだデータです。ライターは核心の関係1件(ドレン–ミラ)を選び、セリフに埋め込むことを決定。
3件のうち2件はルール・再生成で閉じ、最後のFAILがこのパイプラインの核心です。ライターは、ドレンの会話分岐を一行追加するようリクエストしました。
ドレンのセリフに、ミラとの関係がさりげなくにじむ一行だけ差し込んで。
説明調ではなく脇道で。学者風のトーン、セリフ一行のみ。
[再リクエストの出力]
「あの図面は文書庫にある。ミラに聞いてくれ。……20年前は私があの子に読み方を教えた側だったが、近ごろは逆でな。」
この一行が入った瞬間、二人のNPCの関係がマスターデータからゲーム画面へ移ってきます。入力(Squadメタ)→骨格→Personaの充填→関係補強→動線→一貫性検証→破棄・露出の決定という一サイクルが、ここで閉じます。
この一周が、本章のShow基準です。「SquadでNPCを社会として束ねた」という文は、関係露出0件のFAILを人が一行のセリフで閉じる場面を一度でも見なければ、空虚です。
上のサイクルを、一枚の図として載せておきます。要点は、第1・2・4・5段が決定論(ルールブック・lint)で第3段だけがAIだという点、そして人の手は最上部の入力と最下部のゲートにしか触れないという点です。
flowchart TB
P["Personaプール
(6.2 generatorの産出)
ドレン・ミラ・..."] --> FILL
A["入力: Squadメタデータ
(人手10〜15分/狩り場)
役割分布・関係2〜3件の意図"]
A --> B["第1段 決定論: templates.py
役割分布・関係密度・動線の標準"]
B --> FILL["第2段: Personaの充填
再利用 / generator / ライター直書き"]
FILL --> C["第3段 AI: 関係の一行補強
L0キャッシング + voice_traits衝突チェック"]
C --> D["第4段 決定論: 動線合成
shift・routine・event_driven"]
D --> E{"第5段 決定論: Squad lint
役割分布・voice多様性
・動線衝突・関係露出"}
E -->|"alert"| F["第6段 ライターレビューゲート
(5〜10分/狩り場)
破棄・ルール補正・関係露出の決定"]
F -->|"再リクエスト"| C
F -->|"通過"| G["ビルド反映"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class B,D,E code;
class C ai;
class A,F human;
class P data;
class G pass;
人の手が触れる場所は二か所だけです。最上部で役割・関係の意図を決める場面と、最下部でlintが拾えないトーン・ナラティブを判定する場面。その間の骨格・動線・検証はルールブックが、関係の本文はAIが回します。
第5段lintの관계 노출(関係露出)項目が、最も頻繁にFAILになります。関係がデータの中だけで生きているからです。関係をゲームの中へ引き出す装置は三つです。
第一に、セリフでの言及。§6.3.4のドレンのセリフのように、NPCが他のメンバーに一行だけ触れます。最も安く、最も効果が大きい方法です。
第二に、動線の交差。毎週火曜日にドレンとミラが文書庫に一緒にいる場面が、ゲーム内で観察されます。ユーザーが偶然見かければ「あの二人、何かあるのか」と気づきます。第4段の動線と関係が一致していれば、自然に生まれます。
第三に、分岐条件。quest_giverの頼みを断ると、lore_keeperの好感度も一緒に下がります。この第三の装置が、§6.3.1で述べた進歩的適用の入口です — 関係が単なる描写を超えて、ゲームの状態に影響を与え始める場面です。
三つの装置をすべて使う必要はありません。5人のSquadで核心の関係2〜3件だけを第一・第二の装置で露出させるだけでも、狩り場の体感は大きく変わります。やりすぎると、ユーザーが覚えることが増えます。ライターはレビュー段階で露出させる関係を選んで差し込み、残りはデータのままにしておきます。
ツール導入の前後を比較します。加工した数値は使いません。時間・比率はsilvermarkを含む初期の狩り場数か所を直接レビューしながらカウントした値で、「導入前」の列は手作業時代のライターの推定です。
| 項目 | 導入前(手作業・推定) | 導入後(実測) |
|---|---|---|
| 狩り場1か所のSquadを束ねる | 約3〜4時間 | 約25分(メタ12分 + AI 5分 + レビュー8分) |
| 関係露出(セリフ・動線)の件数 | 狩り場あたり0〜1件 | 核心2〜3件のうち露出1〜2件 |
| 動線衝突(同じ座標に2人以上) | 狩り場あたり2〜3件 | lintで事前に遮断、0〜1件 |
| voice平準化による破棄 | —(チェックなし) | 5人中0〜1人を再生成 |
サンプルが狩り場数か所と小さいため、精密な母集団の比率ではなく、方向性を示す値として読むのが正しい姿勢です。最も大きな変化は、表には載りません。lintの관계 노출FAILが強制的にライターへ「この狩り場、関係が一つも見えません」を突きつけるため、量産物が人形博物館のままリリースされることが構造的に減りました。破棄0%・露出0件が目標ではないという点(§6.2.6)は、ここでも同じです。Squadが束ねる仕事の大部分を吸収しつつも、ドレンの最後のセリフ一行のような核心は、ライターが直接練り上げる時間が残っていなければなりません。
Squadが安定すると、自然についてくる運用がPersonaプールです。同じPersonaが複数の都市に登場できます。学者ギルド所属のNPCに都市3〜4か所で出会うのは、むしろ自然です。世界が狭く見えるのではなく、つながって見えます。
persona_pool:
- id: persona_doren_vale
voice_traits: [terse, numeric]
appearance_count: 3
appearance_cities: [city_021, city_018, city_023]
signature: false
- id: persona_mira_kost
voice_traits: [scholarly, withdrawn]
appearance_count: 2
signature: false
再利用比率には、健全な範囲があります。
| 再利用比率 | 状態 |
|---|---|
| 20%未満 | NPC分量の爆発的増加、識別の負担 |
| 30〜50% | 健全な運用範囲 |
| 70%以上 | NPCのマンネリ化、多様性の損傷 |
ただし、一つのNPCをあまりに多くの都市に登場させると「この人、また出てきた」になります。一つのPersonaの最大登場都市数は、5か所と上限を置きます。ボス部屋・シグネチャー人物は再利用禁止(signature: true)。同じPersonaの2回目の登場からは、視覚的なバリエーション(ライト・小道具)を強制します。この30〜50%という範囲は精密な数値ではなく、運用ガイドラインです — チーム・ゲームの規模に応じて補正が必要です。
[方向標識 — ペルソナプールを分布として見るなら(まだ時期尚早)] プールが数百NPCまで育ったチームなら、一歩先へ進む方向があります — §8.2.7の「次元ベクトル」の節と同じ位置づけの方向標識です(処方ではありません — 概念の直観は付録M)。§6.3.4のvoice_lintは、すでに二つのPersonaのコサイン類似度(0.83のような値)で「近さ」を見ています。同じ埋め込みをプール全体にかければ、マンネリ化をライターの印象ではなく分布密度で診断できます — 「学者口調が一角に群れている」状態が、その領域の点密度として見えます。そうすれば、低密度領域に「また似たような学者」を新しく置く代わりに、近い二つのPersonaの間を補間したバリエーションで多様性を埋める道が開けます。ただし、同じ呼吸で添えるべき注意点が二つあります。補間で生成したPersonaは、二つのNPCをぎこちなく混ぜた「死んだ中間値」になりやすく、結局は人がvoiceをもう一度生かし直すことになります。そして上のマンネリ化比率(30〜50%)は精密値ではなく運用ガイドラインなので、それを埋め込み距離に換算した瞬間、緩さが精密なふりをして隠れる危険があります — 距離の値はライターの判定を助ける信号であって、判定そのものではありません。
| 失敗パターン | なぜ失敗するか | 処方 |
|---|---|---|
| Personaだけ量産してSquadを無視 | NPC 50人が全員いるのに狩り場が死んでいる | Squad骨格のルールブック導入(§6.3.3) |
| 役割分布ルールなしの自由生成 | 商人だけ5人、学者0人のような分布事故 | role_distributionの強制(§6.3.3) |
| 関係タグだけ置いて一行描写を省略 | 関係が抽象のままゲームに見えない | 第3段のAI関係補強(§6.3.4) |
| 関係露出のチェックなし | 関係を定義してもセリフ・動線への露出0件 | 第5段の관계 노출lint(§6.3.4) |
| 動線衝突チェックの欠落 | 同じ時間に同じ場所へ2人、リリース後に頻発 | 座標・時間帯の自動チェック(§6.3.4) |
| 再利用比率0%または70%+ | 0%は量産の爆発、70%+はマンネリ化 | プール運用 + 登場上限(§6.3.8) |
四つ目が、最も見落とされやすい失敗です。関係を定義する仕事と、関係をゲームに見えるようにする仕事は別の作業で、チェックがなければほぼ必ず後者が抜けます。silvermark hg_3でlintが関係露出0件をFAILとして突きつけなかったら、ドレンとミラはマスターデータの上でだけ師弟関係だったでしょう。
一人ならここまで: ルールブックもlintもなくて構いません。自分のゲーム(または好きなゲーム)の一つの場所にいるNPC 3〜5人を選び、§6.3.2の形式で役割と関係2件を手で書いてみましょう。次に§6.3.4の関係補強プロンプトをそのまま貼り付けて一行の描写を受け取り、最後に自分へ問いかけてみてください — 「この関係はいま、ゲームのセリフのどこに見えているか?」。一か所もなければ、それがまさにlintの
관계 노출 0FAILです。NPC一人のセリフに他のメンバーへの一行を差し込み、そのFAILを手で閉じてみると、Squad検証が何を拾う作業なのか、体で分かります。
チームなら、次の一歩から始めましょう。Squadメタデータyamlの様式1枚と、第5段lintのうち관계 노출チェック1行から作ります(各NPCのセリフテキストに、他のメンバーの名前・役割が登場するかをgrep)。役割分布チェック・動線衝突チェックはその次です。関係露出チェックが一つあるだけでも、量産した狩り場が人形博物館のままリリースされるという最もよくある失敗を、先に防げます。
setup → prompt → verifyで要約すると — setup: Squadメタyamlに役割・関係を定義し、templates.pyで骨格を組みます。prompt: §6.3.4の形式で関係の一行を受け取りつつ、voice_traitsの衝突禁止・常套句禁止を強制します。verify: 第5段lintを回して관계 노출FAILを確認し、核心の関係1件をセリフに埋め込んで自分で閉じます。
관계 노출 0(関係露出0)のFAILを人が一行のセリフで閉じることが、このパイプラインの心臓です。3つのツールをそれぞれ完成させたその週、私は一度に都市generatorを回し、NPC generatorを回し、アイテムgeneratorを回しました。3つとも、それぞれは問題なく動いていました。都市は7か所、NPCは110人、武器は60個が生成されました。ところが数日後、チェックの席に着いてようやく、同じ落とし穴を3回踏んでいたことに気づいたのです。
都市port_harmanは「没落した漁村(몰락한 어촌)」として生成されたのに、その都市に配置されたNPCたちのペルソナは「繁栄する貿易港の裕福な商人(번성하는 무역항의 부유한 상인)」でした。都市generatorとNPC generatorが、互いに異なるlore_seedsを見ていたからです。アイテムgeneratorは、その都市の推奨レベルが12〜18なのに、レベル40の伝説武器をショップに並べていました。3つのツールはそれぞれ正しく、まとめられていなかったがゆえに間違っていたのです。
本章は、ツールを1つ作る話ではありません。6.2の都市generator、6.3のNPC Squad、そしてアイテムgeneratorを1本の生産ラインにまとめる運用の話です。ツールが3つなら落とし穴も3つになる、のではありません。落とし穴はツールとツールの隙間から新たに生まれるのです。
都市・NPC・アイテムのgeneratorを別々に回すと、各ツールの出力が他のツールの入力と食い違います。解決策はツールをより賢くすることではなく、共有メタデータを上流で一度確定し、その下にツールを並ばせることです。6.2 ch2の都市generatorが単一ツールの模範だとすれば、本章はそのツールをラインの1ステーションへと格下げする作業です。
ライン全体はこう流れます。
flowchart TD
SEED[月: lore_seedsシート
region·climate·faction·level_range]:::human
SEED --> CITY[火: 都市generator
L1ルールブック骨格 + L2 AI本文]
CITY -->|city_manifest.json| NPC[火: NPC generator
都市のloreを継承]
CITY -->|level_range| ITEM[火: アイテムgenerator
レベル帯・陣営を継承]
NPC -->|persona_pool| SQUAD[火: Squad配置]
ITEM --> SQUAD
SQUAD --> LINT[水: 統合lint
generator間の整合性]
LINT -->|alert| REVIEW1[水: 1次チェック
ライター本人]
REVIEW1 --> REVIEW2[木: 2次サンプル
リードナラティブ]
REVIEW2 --> BUILD[金: ビルド反映
+ 翌週のseed準備]
BUILD -.回収.-> SEED
classDef human fill:#fde68a,stroke:#b45309,color:#000;
鍵となるのはcity_manifest.jsonという矢印です。都市generatorは都市を作りながら、その都市のアイデンティティ(没落した漁村なのか、繁栄する貿易港なのか)をmanifestとして書き出し、NPC generatorとアイテムgeneratorがそのmanifestを入力として受け取ります。私があのとき踏んだ落とし穴は、この矢印がなかったことが原因です。ツールをまとめるとは、ツールとツールの間にこの1行の契約を入れることなのです。
都市generatorが都市を1つ作るたびに一緒に吐き出すcity_manifest.jsonの実際の形はこうです。このファイルがNPC・アイテムgeneratorの入力になります。
{
"city_id": "port_harman",
"display_name": "ハルマン港",
"lore_seeds": ["没落した漁村", "古い交易の残響", "塩不足"],
"region": "南部沿岸",
"dominant_faction": "漁民ギルド",
"level_range": [12, 18],
"tone": "衰退·粘り強さ",
"forbidden_names": ["ハラン", "ハルメン"],
"neighbors": ["salt_marsh", "old_pier"]
}
NPC generatorはlore_seedsとtoneを継承して、「没落した漁村の粘り強い人々」を作ります。アイテムgeneratorはlevel_rangeを継承して、12〜18レベルの武器だけを並べます。forbidden_namesはすでに隣の都市で使われた名前なので、双方が避けます。3つのツールが、同じ1枚の契約書を見るのです。
このmanifestを作るときに私がClaudeに与えたプロンプトは次のとおりです。量産ラインの上流をまとめる最も重要な1回の呼び出しなので、全文をそのまま載せます。
あなたはMMORPG都市generatorのmanifest作成器です。以下のライターのメタデータを受け取り、city_manifest.jsonを生成してください。
ライター入力: - city_id: port_harman - lore_seeds: 몰락한 어촌, 옛 무역의 잔향, 소금 부족 - region: 남부 연안 - level_range: 12-18
規則: 1. dominant_factionはlore_seedsから推論すること。ただし隣の都市(salt_marsh、old_pier)の陣営と重複してはならない。隣の都市の陣営は[염전 조합, 밀수단]である。 2. forbidden_namesには、すでに隣接都市で使用されたNPC名・地名を入れること。提供リスト: [하란, 하르멘]。 3. toneは2語以内。lore_seedsと矛盾しないこと。 4. 推測した値とライターが明示した値を区別して表示すること。
Claudeの生の出力はこうでした。要約せず、そのまま載せます。1か所で規則を破っています。
{ "city_id": "port_harman", "display_name": "ハルマン港", "lore_seeds": ["没落した漁村", "古い交易の残響", "塩不足"], "region": "南部沿岸", "dominant_faction": "密輸団", // ← 推論: 塩不足 + 古い交易 = 密貿易 "level_range": [12, 18], "tone": "衰退·強欲", "forbidden_names": ["ハラン", "ハルメン"], "neighbors": ["salt_marsh", "old_pier"] }(推論した値: dominant_faction、tone、display_name / ライター明示: その他) 注意: dominant_factionを「밀수단」に選びましたが、規則1で隣の都市old_pierがすでに「밀수단」です。規則違反です。代替候補として「어민 길드」を提案します。
ここで重要なのは、AIが自ら規則違反を報告したという点です。「밀수단(密輸団)」はlore_seeds(塩不足・古い交易)から見れば魅力的な推論ですが、ライン全体の視点では衝突です。隣の都市がすでに密輸団なのです。私はAIの提案を受けて、dominant_factionを「어민 길드(漁民ギルド)」に、toneを「쇠락·끈질김(衰退・粘り強さ)」に修正しました。「탐욕(強欲)」は密輸団という前提から出てきた単語なので、漁民ギルドには合いませんでした。
この一度の検証・拒否・再指定が、ラインの上流を守ります。manifestが間違っていれば、その下のNPC 110人と武器60個が、すべて間違った前提の上に生成されます。上流で5分を使えば、下流で3時間を節約できるのです。
単一generatorのlintは、自分の出力しか見ません。都市lintは都市がルールブックを守ったかを見て、NPC lintはペルソナがvoiceの一貫性を守ったかを見ます。しかし、私が最初に踏んだ落とし穴は各ツールの中ではなく、ツールとツールの間にありました。だからラインには、単一lintの上にもう1層が必要です。都市・NPC・アイテムを一緒に読み、相互に検証する統合lintです。
統合lintが実際に捕まえる項目はこうです。
| 検査 | 何を比較するか | あのとき見逃したもの |
|---|---|---|
| ロア整合 | city.lore_seeds ↔ npc.persona | 漁村なのに裕福な商人 |
| レベル帯整合 | city.level_range ↔ item.required_level | 12〜18の都市にレベル40の武器 |
| 陣営衝突 | city.faction ↔ neighbor.faction | 密輸団の都市が2つ隣接 |
| 名前重複 | 全city・npc・itemの名前プール | forbidden_namesの未収集 |
この統合lintを回したときの実際の出力の一部です。自動破棄はしません。人が判定できるように、alertを上げるだけです。
[統合lint] port_harman ライン検査 — 3 alert ALERT-1 (ロア整合) port_harman city.lore_seeds = ["没落した漁村", ...] npc[merchant_04].persona = "繁栄する貿易港の裕福な商人" → 矛盾の可能性。意図された変形か確認が必要。 ALERT-2 (レベル帯整合) port_harman city.level_range = [12,18] item[blade_legend_07].required_level = 40 → 推奨レベル帯超過 28。ショップ配置の再検討。 ALERT-3 (名前重複) — 情報 npc[fisher_02].name = "ハラン" city.forbidden_names = ["ハラン", ...] → forbidden_namesと衝突。NPC名の再生成を推奨。
ALERT-1を見て、私は少し考え込みました。NPCが「繁栄する貿易港の裕福な商人」であることが、無条件に間違いだとは限りません。かつて栄え、いまは没落した都市なら、「かつて裕福だった、いまは貧しい商人」はむしろ良い物語です。そこで私はALERT-1を破棄ではなく「意図された変形」と判定したうえで、NPCのペルソナを「かつて栄えた貿易港の面影にすがる老いた商人」へと1行修正するよう依頼しました。ALERT-2は明白な事故なので、武器を削除しました。ALERT-3は名前だけ再生成しました。
自動lintが事故を防いだのではありません。自動lintは事故を人の目の前に引きずり出し、判定は人が下しました。これが6.1で述べたL2(ルールブック+AI補助)の核心です。AIが骨格とalertを作り、人が最後の判定をします。ALERT-1のように「間違っているように見えて、実は良い物語」を見分けることは、ルールブックにはできません。
ツールをまとめたら、次に必要なのはリズムです。ラインは1週間単位で回すのが最も安定していました。1週間という長さは、チェックが急増しない程度に短く、回収が滞らない程度に長いのです。私の机のカレンダーの1マスともかみ合います。
| 曜日 | ラインのステーション | ライターの時間 |
|---|---|---|
| 月 | lore_seedsシート作成(manifestの上流) | 半日(5〜7都市 × 15〜20分) |
| 火 | 都市→NPC→アイテムgeneratorの連鎖実行 | ライターの介入なし |
| 水 | 統合lint + 1次チェック(本人) | 1時間(5〜10分/都市) |
| 木 | 2次サンプルチェック(リードナラティブ) | 2〜3分/都市 |
| 金 | ビルド反映 + 翌週のseed準備 | 短時間 |
火曜日がラインの心臓です。都市generatorがmanifestを書き出すと、NPC generatorがそれをくわえ、アイテムgeneratorがそれをくわえ、Squadが配置まで行います。この連鎖が、ライターの介入なしにバックグラウンドで回ります。ライターはその間、メインクエスト(L0完全手作業)を書きます。ツールをまとめた本当の報酬がここにあります。ツールがばらばらなら、ライターは火曜日に3回手を入れなければなりませんが、まとまっていれば一度も触れずに済みます。
ライター1人が、1週間に都市5〜7か所、それに付随するNPC・武器まで量産します。4週間で都市20〜28か所。30か所という目標に、6週間で到達しました。
ラインが健全かどうかは、印象ではなく数字で見ます。毎週自動集計される4つの指標です。
| 指標 | 正常範囲 | 逸脱時のシグナル |
|---|---|---|
| 統合lint通過率 | 80〜95% | 60%未満ならmanifestの上流が壊れている |
| generator間の衝突 | 都市あたり3〜5件 | 10件以上ならgenerator間の契約が壊れている |
| 人のチェックによる破棄率 | 10〜20% | 30%以上なら量産パラメータが間違っている |
| ライター1人のサイクル時間 | 5日 | 7日以上なら認知負荷が過大 |
最もラインらしい指標は2つ目の、generator間の衝突件数です。単一ツールだけを使っているときには、この数字は存在しません。この数字が突然10件を超えたら、ツールが1つ壊れたのではなく、ツール間の契約(manifest)が壊れたのです。たいていは、都市generatorのmanifestスキーマを変更したのに、NPC generatorが古いスキーマを読んでいるときに起きます。この指標がなければ、その事故はリリースまで見えません。
4つの指標は毎週、四半期の振り返りへ入力されます。傾向が悪化したら、翌週の量産都市数を5〜7か所から3〜5か所に減らし、原因を調べます。
複数のツールをまとめると、単一ツールにはなかった事故が生まれます。よく見た3つを記しておきます。
第一に、契約不一致の事故。都市generatorのmanifestに新しいフィールドを追加したのに、NPC generatorがそのフィールドを知らない。generator間の衝突指標が急増します。ツールを別々に開発していると、片方だけ更新されがちです。対応は、manifestスキーマにversionフィールドを入れ、下流のgeneratorがバージョン不一致を即座にalertとして上げるようにすることです。人を責め立てるのではなく、契約を強制します。
第二に、上流汚染の事故。manifestが間違った前提で生成されると(§6.4.2の「密輸団」のように)、その下のすべてが汚染されます。人のチェックによる破棄率が30%を超えるのに、破棄された出力を見るとNPC個々の品質には問題がない。個々は問題ないのに、前提が間違っているのです。対応は、manifest生成の段階にチェックをもう1つ入れることです。下流の110個をチェックするより、上流の1個をチェックします。
第三に、モデルドリフトの事故。LLMが自動アップデートされ、出力の特性が変わります。都市・NPC・アイテムの3つのgeneratorが同時に揺らぎます。直近1週間の変更点を点検し、破棄サンプルを5個分析し、プロンプトやコンテキストを調整します。1週間のモニタリングの後、回復を確認します。
3つの事故への共通の対応は同じです。人を非難せず、契約を補強します。ライターがlore_seedsを1行しか書かなかったことが原因なら、「3行書いてください」と言う代わりに、manifest lintに強制チェックを追加します。とはいえ、人の責任がゼロという意味ではありません。システムの補強とは別に、事故のパターンは振り返りで共有します。
ラインをまとめる本当の目的は、ライターをなくすことではなく、ライターがシグネチャーに集中できるようにすることです。ツールがばらばらのときにライターの時間がどう散らばり、まとめた後にどう集まるのかを、1枚に描いておきます。
メインとシグネチャーに、ライターの時間の80%が集まります。ただし、この配分はひとりでに維持されるものではありません。ラインを導入すると、ライターの時間がチェックへすべて流れていく傾向があります。そこで毎月時間配分を測定し、メインが50%を下回ったら量産都市数を減らして、メインの時間を回復させます。時間配分はポリシーとして守らなければなりません。
都市・NPC・アイテムのラインが安定したら、同じ骨格をダンジョン・図鑑・ライブイベントへ拡張します。鍵は新しいパターンを作らないことです。「ダンジョンは都市と違うから、別の構造で」という誘惑が常にやってきます。しかし、ラインの骨格(共有manifest → generator連鎖 → 統合lint → 人のチェック)はまったく同じです。入力メタデータの様式と、ドメインのルールブックだけを差し替えます。
ダンジョンなら、dungeon_manifest.jsonにboss_pattern・encounter_flowのようなフィールドが追加され、ボスの動線といったドメインルールが統合lintに1行加わります。骨格は同じに、ルールだけ違うように。同じ骨格を維持すれば、ライターが新しいツールをまた覚える必要がなく、統合lintのインフラがそのまま再利用できます。ただし、ドメインの特殊性を無視しろという意味ではありません。ダンジョンには、都市にはない動線ルールが間違いなく必要です。
私のプロジェクトで、この統合ラインを6か月稼働させた結果です。都市・NPC・アイテムのgeneratorを別々に回していた時期と比較します。以下の絶対値は正確な集計ではなく著者の推定(未検証)であり、方向と比率は実測の傾向に従っています。
| 指標 | ツール分離の時期 | ライン統合後 |
|---|---|---|
| 量産都市(6週間) | 18か所 | 28か所 |
| 火曜日のライター介入回数 | 都市あたり3回 | 0回 |
| generator間の衝突(リリース後発見) | 四半期8〜12件 | 四半期2〜4件 |
| ライター1人の四半期あたりメインクエスト | 3本 | 8本 |
| 上流チェック時間 / 下流チェック時間 | 0 / 3時間 | 5分 / 1時間 |
最も重要な変化は最後の行です。ツールが分離していたときは、上流チェックが0で、下流チェックが3時間でした。ラインをまとめてmanifestを上流でチェックするようにしたら、上流の5分が下流の2時間を消しました。事故がツールの隙間から漏れなくなったので、リリース後の整合性事故も四半期8〜12件から2〜4件に減りました。
そして、トレードオフが明示的になりました。以前は「量産は危険だ」という抽象的な論争が四半期ごとに繰り返されていました。いまは「衝突 -8件 / メイン +5本」という具体的な比較の上で意思決定しています。
1) ツールをまとめず、別々に回すケース。落とし穴はツールの中ではなく、ツールとツールの間に生まれます。
2) manifestなしでgeneratorをつなぐケース。共有の契約がなければ、下流が上流と食い違います。
3) 統合lintを単一lintで代替するケース。単一lintは、generator間の衝突を見られません。
4) サイクルを5日から3日に圧縮するケース。5日がチェックの安全マージンです。
5) 上流(manifest)をチェックせず、下流をチェックするケース。下流の110個より、上流の1個を見てください。
6) 事故を人の責任だけに帰すケース。契約の補強とルールの自動化が答えです。
7) ラインを整えた後に「使わない」ケース。1週間サイクルを強制することが、ツールと同じくらい重要です。
setup. 都市generator(6.2)とNPC generator(6.3)を準備しましょう。2つが共有するcity_manifest.jsonのスキーマを1つ決めます。フィールドは最低限lore_seeds·region·faction·level_range·forbidden_names·tone·versionです。
prompt. 本文のmanifest作成器プロンプトをそのまま使ってみましょう。鍵は最後の2つの規則です。「隣の都市の陣営と重複するな」(generator間衝突の防止)と、「推測した値と明示した値を区別して表示せよ」(チェック可能性)。都市を作ったらmanifestを書き出し、NPC・アイテムgeneratorがそのmanifestを入力として受け取るようにつなぎましょう。
verify. 統合lintを一度回してみましょう。ロア整合・レベル帯整合・陣営衝突・名前重複の4つを相互に検査します。alertが出たら自動破棄せず、人が判定します。「間違っているように見えて、実は良い物語」(没落した貿易港の老いた商人)を見分けるのは、人の役目です。
一人ミニ版. ツールが都市・NPCの2つだけでも、ラインは成立します。スプレッドシート1枚に都市ごとのlore_seeds・level_range・forbidden_namesを書き、NPC generatorのプロンプトにその行をまるごと貼り付けるだけで、manifestの役割を果たします。統合lintは、都市-NPCのロア整合1行だけでも、あの落とし穴を防げます。大げさなインフラがなくても、「ツールとツールの間に1行の契約を入れる」という原則1つで、ラインは始まります。
ダンジョン47番ルームの出口が塞がっていました。ビルドは通過し、QAも通過していました。ユーザーがボスルームの直前で壁を前に立ち尽くしているスクリーンショットがコミュニティに投稿されたのは、ライブ開始から3日目のことでした。そのルームは2四半期前に手作業で作ったルームをコピーして貼り付けたもので、コピーの過程で東側の通路の1本が、接続情報を持たないままビジュアルだけ残っていました。誰もそれを検証しませんでした。検証するツールがなかったのです。
この章は、その事故がビルド段階で自動的に遮断される仕組みを作る話です。核心は、空間を描く手先の器用さではなく、空間に付随するデータをルールで運用するやり方にあります。
レベルデザインの仕事場は製図室に近いものです。図面の1枚1枚は人の手から生まれますが、図面同士の一貫性・再利用・検証は、図面キャビネットの運用ルールが決めます。手で描いたダンジョン1個なら誰でも作れます。ダンジョン100個を、一貫した難易度カーブと行き止まりのないグラフで運用するのは、手先の器用さではなくシステムの問題です。
著者が企画ディレクターとして働くプロジェクトA(国内+東南アジア向けMMORPG、中規模(10〜50人)チーム、モバイル優先)で、このシステムの名前はProcedural_Level_Design_Masterという1つのドキュメントです。この章では、そのドキュメントが何を統合し、AIがどこまで手を入れ、どこで止まるのかを扱います。毎回のランごとにダンジョンが新しく生成されるモバイルローグライトRPGの企画をリードし、プロシージャルな空間をルールで運用してきた経験が、この章の土台にあります。
レベル自動化は二つの方向に分かれます。一つは、空間そのものをプロシージャルに生成することです。BSP分割(Binary Space Partitioning、空間を再帰的に二分割してルームを配置する古典的手法)、wave function collapse、ドランクンウォークグリッドのような伝統的PCG(Procedural Content Generation)がここに属します。もう一つは、空間のメタデータ(ルームタグ・接続性・難易度ラベル・イベントスロット)を運用することです。
伝統的PCGは一つ目に強みがあります。ローグライクやサンドボックスのように「毎回新しいマップ」がゲーム性の核心であるジャンルでは、一つ目が正解です。しかしMMORPGは違います。ユーザーは同じダンジョンを数十回周回します。動線を覚えてしまうほど周回します。だからダンジョンは手で磨き上げた固定空間であるべきで、自動化が入る場所は空間そのものではなく、その空間を運用可能にするメタデータです。
メタデータがなぜ運用の背骨なのかは、成果物別に見れば明らかです。
| 成果物 | メタデータがないと |
|---|---|
| 数十個のダンジョンプール | どのルームがどこにあるか検索不能、再利用不能 |
| 難易度カーブの検証 | ルームごとの難易度ラベルがなく、カーブを描けない |
| クエスト・ボス位置の自動配置 | イベントスロットのメタがなく、手動で座標入力 |
| アートチームとの同期 | ルームタイプ→アートセットのマッピングがなく、ビジュアルの不一致 |
| ユーザー動線・滞在時間の測定 | ルームIDベースのテレメトリーが不可能 |
メタデータのないダンジョンは、ビルドは通っても運用ができません。本は山ほどあるのに索引のない図書館のようなものです。この章が「空間メタデータの運用」に集中する理由です。
Procedural_Level_Design_Masterは、四つの標準を1つのドキュメントに束ねます。ルームメタデータ様式、ルームタグ辞書、接続性ルール、検証チェックリストです。この四つが散らばっているときに何が起きるかから見てみましょう。プランナー5人がそれぞれ別のファイルで様式を参照すると、typeフィールドをある人はcombat、ある人はCombat、ある人はbattle_roomと書きます。検索が壊れ、統計が壊れ、最終的に自動化が壊れます。
この四つの標準はLayerで整理すると、それぞれの居場所がはっきりします。様式・辞書・ルールは生成を支配するルールブック(L1)に、生成されたルーム本体はコンテンツ(L2)に、シートの値はデータ(L3)に、検証はビルド・QAゲート(L4)にあります。
マスタードキュメントが四つの標準を統合するというのは、「本文を1つのファイルに詰め込む」という意味ではなく、「L1の場所にルールを集める」という意味です。だからこそ、後で出てくる自動化がLayerの境界の上に載せられるのです(分離が崩れると何が起きるかは7.1.11で扱います)。
ルーム1つは次の様式に従います。この様式が自動化の入力インターフェースです。
room_id: dungeon_021_room_07
dungeon: dungeon_021_silvermark_library
type: combat_room # combat / puzzle / lore / safe / boss
size: medium # small / medium / large
difficulty_label: hard_for_level_28
tags: [scholar_theme, vertical_layout, water_hazard]
connections:
- target_room: dungeon_021_room_06
type: door
direction: south
- target_room: dungeon_021_room_08
type: passage
direction: east
event_slots:
- slot: enemy_spawn_1
constraints: [scholar_enemy, level_28]
- slot: lore_object_1
constraints: [scholar_lore]
movement_complexity: 4 # 1~5
estimated_clear_time_sec: 90
art_pack: scholar_library_v2
各フィールドには、1つ以上の自動化の利用先があります。typeはダンジョンプールの統計と難易度計算に、tagsは検索・再利用・アートセットマッピングに、connectionsはグラフ検証(行き止まり検査)に、event_slotsはクエスト・ボスの自動配置に使われます。利用先のないフィールドは様式に入れません。入力コストだけが増えて、価値がないからです。
タグはメタデータの検索キーです。無限に増殖すると検索が壊れます。引き出しにラベルが200枚も貼られていたら、何がどこにあるのか探せません。だから5カテゴリー×カテゴリーあたり約6個のenum、合わせて約30個で運用します。
| カテゴリー | enum数 | 例 |
|---|---|---|
| theme | 8 | scholar_theme, ruins_theme, forest_theme … |
| layout | 5 | vertical_layout, horizontal_corridor, open_arena … |
| hazard | 6 | water_hazard, fire_hazard, falling_hazard … |
| interaction | 4 | puzzle_required, lever_activation … |
| narrative | 7 | flashback_trigger, dialogue_zone … |
1つのルームでタグ5個を超えないようにします。正常は3〜4個です。新規タグを追加するには、四段階のゲートを通過しなければなりません。四半期あたり5ルーム以上で使われる見込みがあること、既存タグの組み合わせでは表現できないこと、検索・アートセットマッピングでの活用が明確であること、運用1か月後も5ルームを維持していること。最後の条件が核心です。一時的に作ったタグが1回使われて捨てられると、辞書が汚染されます。
ここまでの標準が一つの流れとしてどうつながるのか。その接続線こそが、この章を支える骨格です。ルールブックから始まり、AI補助のバリエーションを経て、ガードレール検証で終わるパイプラインです。
flowchart TD
A["L0 ビジョン — ダンジョンコンセプト・ペーシング意図"] --> B["L1 ルールブック\nタグ辞書・接続性ルール・スロットルール"]
B --> C["ルーム骨格の配置\nプランナーの手作業+エディター"]
C --> D["メタデータ自動抽出\nroom_id・connections・type・size"]
D --> E["AI補助バリエーション\ntags抽出・art_packマッピング提案"]
E --> F{"辞書強制チェック\n辞書外タグ?"}
F -->|辞書外| E
F -->|通過| G["プランナーレビュー\ntags・difficulty_label確定"]
G --> H["L4 グラフ検証\n到達性・行き止まり・ループ・分岐"]
H -->|違反| C
H -->|通過| I["難易度カーブ検証\nルーム難易度ラベルの合算"]
I --> J["アートセット整合性ゲート"]
J -->|通過| K["ビルド — ダンジョンプール登録"]
J -->|不一致| E
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class B,D,F,H,I,J code;
class E ai;
class C,G human;
class K pass;
このパイプラインの三つの性格を押さえておきましょう。第一に、ルールブック(L1)がすべての生成の上流にあります。第二に、AIはルールブックが定義した辞書の中だけでバリエーションを作ります。Fゲートが辞書外の出力を差し戻します。第三に、検証(H・I・J)がビルド直前のゲートとして固定されているため、違反はコードで遮断され、人の注意力には依存しません。47番ルームの事故は、Hゲートがなかったから起きたことなのです。
ルームメタのconnectionsフィールドは、ダンジョン全体を1つの有向グラフにします。グラフになれば検証は自動です。
| 検査 | 違反時の処理 |
|---|---|
| 開始ルーム→ボスルームの到達可能性 | ビルド失敗として遮断 |
| 行き止まり(出口1個+non-safe_room) | alert(プランナーが検討) |
| 双方向接続の整合性(A→BがあるのにB→Aがない) | 自動補正 |
| ループ長(2〜3ルームの短いループ) | alert |
| 分岐幅(同時に4個以上の分岐) | プランナーが検討 |
測定スクリプトは次の形です。標準的なグラフアルゴリズム(最長経路・平均出次数・ループカウント・最短経路)の上に、ダンジョンの語彙をかぶせた薄いラッパーです。
# level_graph_metrics.py
def measure(dungeon):
graph = build_graph(dungeon.rooms)
return {
"depth": longest_path_length(graph),
"branching_factor": avg_out_degree(graph),
"loop_count": count_loops(graph),
"dead_ends": count_dead_ends(graph),
"boss_reachability": shortest_path(graph.start, graph.boss),
}
五つの指標が、他のダンジョンと比較可能な形で出力されます。ダンジョンプールの多様性指標として使います。ただし、指標が多様だからといってダンジョンが面白いという意味ではありません。指標は事故を遮断するためのものであって、面白さを保証するためのものではありません。行き止まり0件は面白さを保証しません。面白さはプランナーのインサイトから生まれ、グラフ検証はそのインサイトが事故に埋もれないよう、底を支えるだけです。
自動化の中で、人が最も手を離したくなるのがtagsの入力です。ルーム100個にタグを付ける作業は退屈ですし、ルームのスクリーンショットだけでは人間でも迷います。繰り返しが多く、判定基準が明確なこういう仕事こそ、AIがドラフトを支えるのに向いた場所です。この節では、その作業を実際に回したワークフローを、プロンプト、却下されたAIの出力、人による再依頼まで含めて、加工なしで広げます。
1回目のプロンプト:
[入力]
- ルームのスクリーンショット: (添付)
- ルーム面積: 18m × 12m, 天井高さ 9m (垂直2層構造)
- 配置された敵: scholar_phantom × 3, water_elemental × 1
- 隣接ルームの type: lore_room(西), combat_room(東)
- ルーム内に浅い水たまりあり
このルームに合うタグを3~5個、下の辞書からだけ選んで。辞書外のタグは作らず、
タグの enum だけをカンマ区切りで。
[タグ辞書 30個]
theme: scholar_theme, ruins_theme, forest_theme, cave_theme,
crypt_theme, swamp_theme, mechanical_theme, celestial_theme
layout: vertical_layout, horizontal_corridor, open_arena,
maze_layout, bridge_layout
hazard: water_hazard, fire_hazard, falling_hazard,
poison_hazard, crush_hazard, dark_hazard
interaction: puzzle_required, lever_activation, key_gate, hidden_passage
narrative: flashback_trigger, dialogue_zone, lore_object_zone,
ambush_setup, ritual_site, vista_point, rest_anchor
Claudeの1回目の出力(生のまま):
scholar_theme, vertical_layout, water_hazard, ambush_setup, two_story_layout
ここで二つの問題がそのまま現れています。一つは、two_story_layoutが辞書にないタグだという点です。モデルが「垂直2層構造」という入力に引きずられて、辞書外の単語を作り出しました。もう一つは、タグが5個と上限に達しているのに、vertical_layoutとtwo_story_layoutが意味的に重複している点です。layoutカテゴリーで2枠を使ってしまったわけです。
人による却下の判断。 two_story_layoutは辞書強制違反なので、自動却下の対象です(パイプラインのFゲート)。ambush_setupは判断が分かれます。敵が3体配置されてはいますが、スクリーンショット上で待ち伏せの構図なのか、単純な戦闘ルームなのか曖昧です。これはモデルが過剰解釈した可能性があります。隣接ルームが両方ともlore/combatなので、ナラティブ上の待ち伏せトリガーとしては根拠が弱い。却下です。
2回目の再依頼:
two_story_layout は辞書になく、vertical_layout と重複してる。外して。
ambush_setup も外して — 敵がいるからといって待ち伏せではない。
// (意図: narrative タグはナラティブ意図が明示されるときだけ。単純な戦闘は type で既に表現済み)
空き枠は無理に埋めなくていい。3~4個が正常。もう一度タグ付けして。
Claudeの2回目の出力:
scholar_theme, vertical_layout, water_hazard
今回は3個に減り、すべて辞書の中にあり、カテゴリーの重複もありません。人はこの結果を受け入れました。ルーム様式のtags: [scholar_theme, vertical_layout, water_hazard]は、こうして確定します。
このワークド例題の教訓は2行です。第一に、AIは入力の1つのディテール(「2層」)に過剰適合して辞書の外へ出ます。辞書強制ゲートがコードレベルでこれを捕まえなければなりません。第二に、AIは空き枠を埋めようとする傾向があります。「無理に埋めるな」と明示しないと、5枠すべてを埋めようとします。どちらの失敗もよくあるもので、どちらの処方も、プロンプトではなくルールブック(辞書+上限)で強制してこそ安定します。
プランナーがルーム1個のメタを手で埋めると、5〜10分かかります。ダンジョン1個(20〜30ルーム)なら2〜5時間、ダンジョン100個なら200〜500時間です(著者の推定、未検証。ルームあたり平均入力時間×ルーム数で換算した上限値)。すべて手で埋めていたら、プランナーはメタデータ入力の奴隷になります。
そこで、領域ごとに埋める主体を分けます。
| 領域 | 埋める主体 |
|---|---|
| room_id・dungeon・connections | エディターの自動抽出(L3) |
| type・size | ルーム面積・接続数ベースの自動分類 |
| tags | AI補助+プランナーレビュー(7.1.7) |
| event_slots | ルームtype別のルールブック |
| difficulty_label | ルーム内の敵データを合算して自動計算 |
| art_pack | ルームtype・ダンジョンthemeのマッピング |
プランナーが手で確定するのは、tagsのレビューとdifficulty_labelの最終承認くらいです。残りはツールが埋め、人はレビューします。自動化の目的は、プランナーを入力作業から引き上げ、ペーシング・シグネチャールーム・再利用ポリシーの判断へ戻すことです。
マスター標準の最大の効果はルームの再利用です。タグで検索可能なルームが30個あれば、ダンジョン5〜10個を組み合わせで作れます。ところが再利用率が高くなると、ダンジョンは陳腐化します。そこで、再利用にはガードレールを併設します。
| ガードレール | 定義 |
|---|---|
| 1つのルームは最大5個のダンジョンに登場 | 登場頻度を自動追跡 |
| 2回目の登場時はビジュアルバリエーションを強制 | ライト・小物の変更 |
| ボスルーム・シグネチャールームの再利用禁止 | flagで強制 |
| 再利用ルームへのネガティブフィードバックを追跡 | ユーザーテレメトリー |
再利用はコストを下げる手段であって、目的ではありません。再利用率そのものをKPIにした瞬間、ユーザー体験は単調になります。0%(全ルーム新規)なら量産コストが爆発し、70%を超えるとダンジョン同士の区別がつかなくなります。経験上、30〜40%の区間がコストと多様性のバランスポイントです(方向性の観察であり、正確な閾値はプロジェクトごとに異なります)。
| パターン | 処方 |
|---|---|
| メタ様式を5人が5通りに解釈 | MasterドキュメントでL1統合 |
| タグが50〜100個に増殖 | 30個の辞書+四段階ゲート |
| 行き止まり検査なしでビルド | グラフ検証をビルドゲートに |
| プランナーが全メタを手作業 | エディター抽出+AI補助 |
| AIが辞書外タグを生成 | 辞書強制ゲートで自動却下 |
| 再利用0%または70%超 | 30〜40%区間+バリエーションガードレール |
ここまでルールブック・生成・検証として解いてきた7.1.2〜7.1.6の構造そのものが、Layer分解の結果物です。Layer分解がプロシージャル生成・自動化の前提であるという一般論(L0アンカー→L1ルールブック→L2本文→L3数値→L4ゲート、ひとかたまりだと生成が崩れる)は、§6.6で扱いました。ここではそれをレベルメタデータの運用に適用します。
この分離がないと、ルーム配置・BSP・ペーシング・ナラティブトリガーが1つのファイルに混ざり、ルームを1マス動かすたびに、ペーシング意図・イベントスロット・接続性グラフが同時に壊れます。製図室・資材倉庫・検品室が1つの机に積み上がっていて、図面を1枚抜くと資材の送り状と検品表が一緒に抜けてしまう状況です。だから、7.1.7のAI補助が機能したのもLayerのおかげです。ルームID・接続性はエディター(L3自動抽出)で、タグはAI(L1辞書強制)で、difficulty_labelは合算(L3→L4)で埋まります。自動化はLayerの境界の上に載るのであって、ひとかたまりの上に載せれば、最初の四半期のうちに事故が爆増してツールそのものが廃棄されます。
ただし、最初から五段の引き出しを完璧に揃えなければならないという意味ではありません。分離は漸進的に、インターフェースは狭く、が原則です。最初の四半期は、L1ルールブック(タグ辞書+接続性ルール)とL3シート(ルームメタシート)だけ分離しても、自動化が入る場所は生まれます。L0ペーシング意図とL4検証ゲートは、四半期を重ねながら埋めていきます。標準が統一されてこそ自動化の入る場所が生まれ、自動化が進むほど、プランナーはルーム1マスの手作業ではなく、ペーシング・シグネチャー・再利用の判断に集中できるようになります。
setup. ダンジョンを1つ選び、ルームごとにroom_id · type · connections · tagsの4フィールドだけを持つYAMLシートを作ってみましょう。タグは、5カテゴリー約30個のenum辞書を先に紙1枚に固定します。
prompt. ルームのスクリーンショット+面積+敵の種類+隣接ルームのtypeを入れて、「この辞書からタグを3〜5個だけ選べ、辞書外のタグは禁止、空き枠は埋めるな」と依頼してみましょう(7.1.7のプロンプトそのまま)。
verify. (1)AIの出力に辞書外のタグがあれば、却下して再依頼しましょう。(2)connectionsでグラフを作り、開始→ボスの到達性と行き止まりを検査します。違反が1つでも出たら、そのルームはビルド不可としてマークしましょう。
ツールのインフラがない1人開発者なら、マスタードキュメントを1枚のMarkdown(マークダウン)から始めてみましょう。タグ辞書30行、接続性ルール5行、検証チェックリスト5行で十分です。グラフ検証は、ルームが10個以下なら紙に矢印を描いて行き止まりだけ目で確認しても、効果の80%が得られます。核心はツールではなく、「ルームにデータを付け、そのデータをルールで検査する」という習慣そのものです。ツールは、ルームが50個を超えて手作業での検査が苦しくなったときに付ければ十分です。
見習い魔法使いが1体、プレイヤーに張り付いて剣を振り回していました。遠距離の魔法キャスターとして設計したNPCです。HPは紙のように薄く、近接では一発もらっただけで死ぬのに、そいつは距離を取る気配がありませんでした。ビルドログには何のエラーもありません。エディターでBehaviorTreeを開き直してみても、ノードは問題なくつながっています。1時間にらみ続けた末に、原因を見つけました。後退分岐の距離条件が5ではなく0.5になっていたのです。5メートル以内に入られたら逃げるべきところが、0.5メートル、つまりほぼ目の前まで来ないと後退分岐が発動しなかったわけです。
たった数字一つでした。グラフィカルなノードエディターでは、その数字はノード内側のパネルを開かなければ見えず、変更履歴にも残りませんでした。誰がいつその値を変えたのか、追跡する方法がなかったのです。その日以降、筆者のプロジェクトAはBehaviorTreeをグラフィカルではなくjsonで扱い始めました。本章は、そのjsonを人とAIが共に編集し、機械が自動で検証する1サイクルの記録です。
BehaviorTreeは、敵NPCの戦闘・移動・反応を定義する事実上の標準構造です。セレクター(selector)が優先順位どおりに分岐を試行し、シーケンス(sequence)が条件とアクションを順番に束ねます。構造自体は単純です。問題は規模です。
筆者のプロジェクトAでは、敵NPC 1体のBTはおよそ50〜200個のノードで構成され、運用対象のNPCは100体を超えていました。掛け合わせると、BTノードの総量は数万単位になります。この規模になると、「この後退パターンを変えたら、どのNPCに影響が出るのか」という質問に人が答えられなくなる瞬間が来ます。机の上にノートが100冊開いてあって、1冊目の1行を直すと、残り99冊のどこに波及するのかを目で追いかけるようなものです。
筆者がグラフィカルBTからjsonへ移行するにあたって要求したことは、次の4つでした。
商用ゲームエンジンの組み込みBTエディターは、統合が楽で視覚的なデバッグに強いものです。ただし、バイナリ(binary)アセットとして保存される傾向があるため、テキストのdiffと変更影響の追跡には弱くなります。筆者のプロジェクトAは、運用BTが100体を超える運営型ゲームを前提としていたため、独自のjson BTフォーマットとエディターを自社開発する道を選びました。はっきりさせておきましょう。これはすべてのチームにとっての正解ではありません。運用BTが50体未満なら、エンジン組み込みのエディターをそのまま使うほうがほぼ常に安上がりです。自社開発の正当化については、本章の最後で改めて扱います。
まず成果物の形を見てみます。以下は、学者ギルドの遠距離支援型NPCのBTの一部です。要点は2つです。すべての行動がテキストなのでgitが1行単位で追跡できること、そして共通パターンをsubtree_refで参照していることです。
{
"bt_id": "bt_scholar_archer_v3",
"category": "ranged_combatant",
"tags": ["scholar_faction", "ranged", "support"],
"description": "学者ギルドの遠距離支援型。距離維持 + 後退優先。",
"root": {
"type": "selector",
"children": [
{
"type": "sequence",
"name": "low_hp_retreat",
"children": [
{"type": "condition", "fn": "hp_below", "param": 0.3},
{"type": "subtree_ref", "id": "subtree_retreat_to_ally"}
]
},
{
"type": "sequence",
"name": "kite_pattern",
"children": [
{"type": "condition", "fn": "enemy_in_close_range", "param": 5},
{"type": "action", "fn": "move_away", "param": {"distance": 8}}
]
},
{"type": "subtree_ref", "id": "subtree_ranged_attack_pattern"}
]
}
}
このツリーを図に展開すると、セレクターが上から3つの分岐を順に試行する構造です。冒頭のあのバグ(enemy_in_close_rangeのparamが5か0.5か)が、jsonでは一目で見える1行になる点に注目してください。
flowchart TD
R["selector
(上から優先順位)"]
R --> A["sequence: low_hp_retreat"]
R --> B["sequence: kite_pattern"]
R --> C["subtree_ref:
subtree_ranged_attack_pattern"]
A --> A1["condition: hp_below 0.3"]
A --> A2["subtree_ref:
subtree_retreat_to_ally"]
B --> B1["condition: enemy_in_close_range 5"]
B --> B2["action: move_away dist=8"]
style C fill:#e8f0fe,stroke:#4285f4
style A2 fill:#e8f0fe,stroke:#4285f4
style B1 fill:#fce8e6,stroke:#ea4335
| 要素 | 役割 |
|---|---|
bt_id |
git diff・変更追跡のキー |
category・tags |
検索・再利用の単位 |
subtree_ref |
共通パターンの参照(1か所の修正 → 多数のBTに反映) |
description |
プランナー・シナリオライター共有用 |
赤く塗ったenemy_in_close_range 5が、冒頭で人の1時間を食いつぶしたあのノードです。jsonならコードレビュー一発で捕まります。
100体を超える敵の行動には、繰り返し現れる塊があります。「味方の後ろへ後退」「遮蔽物へ後退」「遠距離攻撃パターン」といったものです。これをBTごとにコピーして入れてしまうと、後退ロジックを一つ直すたびに100か所を手作業で探して直すことになります。そこで共通パターンは別のsubtreeファイルとして切り出しておき、subtree_refで参照するだけにします。
subtree_library/
├── retreat_patterns/
│ ├── subtree_retreat_to_ally.json
│ ├── subtree_retreat_to_cover.json
│ └── subtree_retreat_random.json
├── attack_patterns/
│ ├── subtree_ranged_attack_pattern.json
│ ├── subtree_melee_combo.json
│ └── subtree_aoe_attack.json
└── reaction_patterns/
├── subtree_react_to_ally_death.json
└── subtree_react_to_player_taunt.json
こうしておけば、「このsubtreeを直したら誰に影響が出るのか」という質問が、人の推測ではなくスクリプトの出力になります。影響トラッカーは単純です。すべてのBTを開き、該当するsubtreeを参照しているBTのbt_idを集めるだけです。
# bt_impact_tracker.py
import json, glob
def has_subtree_ref(node, target_id):
if isinstance(node, dict):
if node.get("type") == "subtree_ref" and node.get("id") == target_id:
return True
for child in node.get("children", []):
if has_subtree_ref(child, target_id):
return True
return False
def find_affected_bts(subtree_id):
affected = []
for bt_file in glob.glob("bts/*.json"):
bt = json.load(open(bt_file, encoding="utf-8"))
if has_subtree_ref(bt["root"], subtree_id):
affected.append(bt["bt_id"])
return affected
# 使用
affected = find_affected_bts("subtree_ranged_attack_pattern")
# → ["bt_scholar_archer_v3", "bt_ranger_v2", "bt_sniper_v1", ...]
筆者のプロジェクトAでは、この関数をプルリクエスト(Pull Request)の段階に組み込んでおきました。誰かがsubtreeファイルに手を入れると、影響を受けるBTのリストが自動的にPRコメントに付きます。レビュアーは「後退パターンを1行直しただけで遠距離の敵12体が全部変わる」という事実を、マージ前に目にすることになります。
ここからが本章で最も重みのある部分です。新しい敵NPC「見習い魔法使い」のBT草案をAIに任せ、その出力を人が検証・却下・再依頼する1サイクルを、整えずにそのまま載せます。滑らかに刈り込まないのには理由があります。AIが最初の出力で何をどう間違えたのか、その失敗の手触りこそ、本章が伝えたいことのすべてだからです。
[敵NPCコンセプト]
学者ギルドの見習い魔法使い。HPが低く魔法ダメージが高い。
近接回避を優先。味方の後ろへ後退した後に魔法攻撃。
[category] ranged_combatant
[参考BT] bt_scholar_archer_v3
(構造は類似。ただし弓攻撃の代わりに魔法攻撃へ差し替え)
[定義済み関数enum]
condition: hp_below, enemy_in_close_range, ally_nearby, mana_below
action: move_away, cast_spell, retreat, wait
subtree_ref id (使用可能リスト):
- subtree_retreat_to_ally
- subtree_ranged_attack_pattern # 弓ベース。魔法用ではない
- subtree_magic_attack_pattern
このコンセプトでBT rootノードのjsonだけ書いて。subtree_refを最大限使って直接ノードは最小に、
関数は上のenumの中だけで。コンセプトにない行動は追加しないこと。説明なしでjsonのみ。
{
"bt_id": "bt_scholar_apprentice_v1",
"category": "ranged_combatant",
"tags": ["scholar_faction", "magic", "fragile"],
"description": "学者ギルドの見習い魔法使い。近接回避 + 味方の後ろへ後退 + 魔法攻撃。",
"root": {
"type": "selector",
"children": [
{
"type": "sequence",
"name": "low_hp_retreat",
"children": [
{"type": "condition", "fn": "hp_below", "param": 0.4},
{"type": "subtree_ref", "id": "subtree_retreat_to_ally"}
]
},
{
"type": "sequence",
"name": "avoid_melee",
"children": [
{"type": "condition", "fn": "enemy_in_close_range", "param": 6},
{"type": "action", "fn": "move_away", "param": {"distance": 10}}
]
},
{
"type": "sequence",
"name": "mana_recover",
"children": [
{"type": "condition", "fn": "mana_below", "param": 0.2},
{"type": "action", "fn": "wait", "param": {"frames": 120}}
]
},
{"type": "subtree_ref", "id": "subtree_ranged_attack_pattern"}
]
}
}
出力を受け取り、lint(次節で扱います)にかけ、目で読みました。3か所が引っかかりました。
最後の分岐が間違っています。 subtree_ranged_attack_patternは弓ベースの攻撃パターンです。enumのコメントに「마법용 아님(魔法用ではない)」と書いておいたにもかかわらず、AIは名前が似ている弓パターンを持ってきました。見習い魔法使いはsubtree_magic_attack_patternを使うべきです。— 却下。
コンセプトにない行動を追加しています。 mana_recover分岐(mana_below → wait 120frame)は、コンセプトに一行も書かれていない行動です。プロンプトで「明示されていない行動の追加は禁止」と釘を刺したにもかかわらず、AIが「魔法使いならマナ管理が自然だ」と判断して差し込んだものと見られます。もっともらしいものの、仕様違反です。— 却下。
後退のしきい値が疑わしいです。 hp_below 0.4は、参考BT(0.3)より早く後退を始めます。見習い魔法使いのほうが弱いのは確かなので、意図的かもしれません。ただ、AIが根拠を示したわけではないため、プランナーが決めるべき事項です。— 保留、プランナー確認。
3か所だけ直して。最後の分岐subtree_ranged_attack_patternは弓パターンだから
subtree_magic_attack_patternに替えて、mana_recover分岐はコンセプトにないから丸ごと削除、
hp_below 0.4は参考BT(0.3)に戻して — 0.4にする根拠があればdescriptionに1行で。
残りはそのまま、jsonのみ。
{
"bt_id": "bt_scholar_apprentice_v1",
"category": "ranged_combatant",
"tags": ["scholar_faction", "magic", "fragile"],
"description": "学者ギルドの見習い魔法使い。近接回避 + 味方の後ろへ後退 + 魔法攻撃。HPが低いため後退しきい値0.3を維持(archerと同一)。",
"root": {
"type": "selector",
"children": [
{
"type": "sequence",
"name": "low_hp_retreat",
"children": [
{"type": "condition", "fn": "hp_below", "param": 0.3},
{"type": "subtree_ref", "id": "subtree_retreat_to_ally"}
]
},
{
"type": "sequence",
"name": "avoid_melee",
"children": [
{"type": "condition", "fn": "enemy_in_close_range", "param": 6},
{"type": "action", "fn": "move_away", "param": {"distance": 10}}
]
},
{"type": "subtree_ref", "id": "subtree_magic_attack_pattern"}
]
}
}
2次出力はlintを通過し、プランナーによるレビューでavoid_meleeの距離6をarcherに合わせて5へ1段下げた後、シミュレーションゲートへ回しました。ここで学べることは明確です。AIはBTの骨格を素早く埋めてくれますが、「名前が似ている誤ったsubtreeを引っ張ってくる」「もっともらしい追加行動を仕様なしに差し込む」という2種類の事故をほぼ毎回起こします。この2つの事故は、人の目とlintゲートでしか捕まえられません。だからAIの出力は草案であって、最終版ではないのです。
BTはユーザー体験に直結します。敵が目の前で逃げないという事故がそのままリリースされれば、レビュースコアとなって返ってきます。だからマージ前に、機械が先に検査します。
| 検査 | 違反時 |
|---|---|
| 到達不能ノード | alert(セレクターから永遠に到達しない分岐) |
| 無限ループの危険 | ブロック(脱出条件のないsequence反復) |
subtree_refの参照先が存在しない |
ブロック |
| アクション・条件関数がenum外 | ブロック |
| ノード数の急増(>500) | alert(BT分割を勧告) |
| 同一category内のBT応答時間のばらつき | alert(バランスリグレッションの疑い) |
最後の項目がこのlintの特異な点です。同じranged_combatantに括られたBT 5体のシミュレーション平均応答時間が大きく開いたら、それは誰かが1体のバランスを知らないうちに壊したというシグナルです。静的検査では捕まえられない「空気」を統計で捕まえる仕掛けです。
静的lintの次はシミュレーション検証です。BTを実際のゲームビルドなしにシミュレーターで1,000回走らせ、統計を取ります。
| 測定 | 正常範囲 |
|---|---|
| 平均生存時間(標準プレイヤー相手) | categoryごとの基準値 |
| 攻撃パターンの多様性(エントロピー) | 0.6以上 |
| 後退・接近行動の比率 | categoryごとの基準値 |
| 行動1回あたりの平均所要frame | 60 frame以下 |
ビルドを焼かなくても、5〜10分のうちに「このBTはあまりに早く死んでいないか」「一つの行動だけを繰り返していないか」を確認できます。異常シグナルが出たらjsonを直し、シミュレーションを回し直します。このサイクルが日単位から分単位へ縮むことこそ、json化の実質的な利益です。
flowchart LR
P["人/AIが
BT jsonを編集"] --> L{"静的lint"}
L -->|ブロック| P
L -->|通過| R["プランナーによるレビュー"]
R -->|却下| P
R -->|承認| S{"シミュレーション1,000回"}
S -->|異常シグナル| P
S -->|正常| M["マージ + ビルド反映"]
style L fill:#fef7e0,stroke:#fbbc04
style S fill:#fef7e0,stroke:#fbbc04
style M fill:#e6f4ea,stroke:#34a853
筆者のプロジェクトAにおける導入前後を表にまとめます。絶対値はチーム規模・ゲームジャンルによって変わるため、著者の推定(未検証)です。ただし、方向と比率は実際の運用で観察したとおりです。
| 項目 | 導入前(エンジン組み込みBTを直接) | 導入後(json+エディター) |
|---|---|---|
| 新しい敵1体のBT作成 | 1〜2日 | 2〜4時間 |
| BT変更の影響把握 | 推測・経験に依存 | 自動(subtree影響リスト) |
| 変更後の検証 | 実ビルドが必要 | シミュレーション5〜10分 |
| 敵NPC 100体の運用 | プランナー3人フルタイム | プランナー1〜2人 |
| リリース後のBT事故(異常行動) | 四半期あたり10〜15件(著者の推定) | 四半期あたり2〜4件(著者の推定) |
最も意味があるのは、最後の2行が同時に動いたという点です。普通、人員を減らせば品質は落ちます。ここではプランナーの数が減りながら、事故も減りました。人が手で追跡していた変更影響と検証を、機械が引き受けたからです。自動化の価値は「速くなる」ことよりも、この「減りながら同時に良くなる」ことにあります。
本章を読んで「うちもjson BTエディターを作ろう」と結論づけられては困ります。筆者のプロジェクトAが自社開発を選んだのは、特定の条件がかみ合ったからです。
| オプション | 長所/短所 |
|---|---|
| エンジン組み込みBTをそのまま使用 | 統合が容易/json変換・diffに弱い |
| 外部BTライブラリーの借用 | 標準化の利点/学習曲線・カスタマイズの限界 |
| 独自json BTエディター+ランタイム | 自由度・追跡性は最高/開発コストが大きい |
プロジェクトAが3番を選んだ根拠は4つでした。
開発コストは1〜2か月。運用BTが100〜300体に達し、運営(ライブオプス)の期間が長くて初めて回収できます。30〜50体の規模では回収できません。自社開発の投資回収(ROI、Return On Investment)は、規模と運営期間の両方が保証されるときにだけ成立するという意味です。小さなチームなら、本章からは「jsonで保存する」「subtreeで参照する」「AIの出力はlint+レビューゲートを通す」という原則だけを持ち帰り、ツールは組み込みエディターや外部ライブラリーの上に載せて使うほうが正しいのです。
| パターン | 処方 |
|---|---|
| BTをバイナリアセットだけで管理 | jsonで保存してgit追跡を生かす |
| subtreeなしでBTごとに同じパターンをコピペ | subtreeライブラリーに切り出して参照する |
| BTの影響追跡を手作業で | 影響分析スクリプトをPRに組み込む |
| シミュレーションなしで実ビルドだけで検証 | ビルドと分離されたシミュレーターを運用する |
| AI出力のBTをレビューなしで使用 | lint+プランナー+シミュレーションの三重ゲートを通す |
| 自社開発のROIを測らない | 100体以上・運営(ライブオプス)のときにだけ自社開発する |
小さなチームが今日から試せる最小サイクルです。
setup — 運用中の敵NPC 1体のBTを、手作業でjsonに書き起こしてみましょう(bt_id、category、tags、root)。共通の後退・攻撃パターンを一塊、subtree_library/に切り出してsubtree_refで参照します。
prompt — 似た系統の新しい敵1体をAIに任せてみましょう。上のワークド・トランスクリプトのプロンプト骨格(コンセプト+category+参考BT+使用可能な関数enum+「明示されていない行動の追加は禁止」+「jsonのみ」)をそのまま使えば大丈夫です。
verify — AIの出力を、(1)enum外の関数・存在しないsubtreeをはじくlint、(2)人の目、(3)シミュレーションまたはインゲームでの短い検証、この3つのゲートに通した後にだけマージしましょう。AIが「名前の似た誤ったsubtree」と「もっともらしい仕様外の行動」を差し込んでいないか、必ず確認してください。
エディターを作る余力がなければ、ツールはテキストエディターとgit、そして30行のbt_impact_tracker.pyが1つあれば十分です。組み込みエディターで組んだBTを一度jsonにエクスポートしてgitに上げ、subtreeだけを別ファイルに切り出して参照しましょう。影響追跡スクリプトをコミットフックに掛ければ、一人でも「この後退パターンを直したらどの敵が変わるのか」を、推測ではなく出力として見られます。この一つの習慣だけで、冒頭の「数字一つに1時間」はコードレビューの1行に縮みます。
ダンジョンレビューの席で、新人のレベルデザイナーが自分のダンジョンを1つ画面に映しました。狭い通路、後方から追いすがる速い敵、分岐点での回避の決断。よくできたダンジョンでした。問題は、それが私たちがすでに11個の別のダンジョンで作ってきたものと微妙に違っていたという点です。敵の追跡速度、罠が作動するタイミング、分岐点が現れる時点。どれ1つとして同じではありませんでした。新人は「追跡ダンジョン」という同じ名前の体験を作ったと信じていましたが、ユーザーが受け取った感覚はダンジョンごとにばらばらでした。
その日、私たちが決めたことは単純でした。「通路追跡」という体験を一度だけ正確に定義し、その定義を固定化しておくこと。次に誰かが追跡ダンジョンを作るときは、ゼロから組むのではなく、その固定化された定義を取り出して使うこと。これがパターンライブラリーの始まりでした。
ルームが空間の単位で、BehaviorTreeが行動の単位だとすれば、パターンは空間と行動とイベントを1つに束ねた運用単位です。パターン1つが複数のダンジョンで再利用されれば量産の負担が減り、さらに重要なことに、ユーザーが受け取る体験がダンジョンの間で一貫します。
料理本のレシピを思い浮かべるとぴったりです。レシピ1枚には材料、調理手順、火加減、完成写真が一緒に載っています。店が変わっても、同じレシピに従えば同じ味になります。ただし店ごとに多少のバリエーションは許容します。パターンも同じです。空間(ルーム)、行動(BT subtree)、出来事(event)、結果(報酬・難易度)、そしてデザイナーによる意図の説明が1セットで入ります。
パターンが1つ定義されると、ダンジョンごとに同じ体験を一貫して作れるようになります。一度検証されたレシピが、複数の店で同じ味を出すように。ただし同じレシピでも、店ごとに多少のバリエーションは持たせます。このバリエーションをどう管理するかが、パターン運用の半分です。後ほど扱うoverridesがその場所です。
パターンライブラリーの核心は、パターンをルールブックとして固定化したうえで、それを組み合わせてダンジョンを生成するという点です。デザイナーが白紙の画面からダンジョンを組むのではなく、検証済みのパターンを選んで配置し、一部だけバリエーションを加えます。
flowchart TD
A[ゲーム内の良い体験の瞬間を観察] --> B[空間・NPC・イベントに分解]
B --> C[ルームテンプレート・subtreeにマッピング]
C --> D{シミュレーション + ユーザーテスト通過?}
D -- いいえ --> B
D -- はい --> E[ライブラリーにパターンを登録
usage_count = 0]
E --> F[(パターンライブラリー
5カテゴリー · 30〜50個)]
F --> G[ダンジョンデザイン: パターンインスタンス呼び出し]
G --> H[配置 placement + バリエーション overrides]
H --> I{バリエーション比率20%以下?}
I -- はい --> J[ダンジョン完成
パターン usage_count +1]
I -- いいえ --> K[別パターンとしての分離を検討]
K --> B
J --> L[パターン影響追跡
パターン1つの修正 → 使用ダンジョンを自動集計]
L --> F
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class L code;
class F data;
class J pass;
このフローの左半分(観察→分解→マッピング→検証→登録)はパターンを作る過程で、右半分(呼び出し→配置・バリエーション→完成→追跡)はパターンを消費する過程です。作る仕事はまれにしか起きず、消費する仕事は頻繁に起きます。ライブラリーがうまく運用されれば、この非対称が量産効率につながります。
著者のプロジェクトAはアクションRPG系なので、5つのカテゴリーでパターンを分類しています。この分類はジャンルに依存します。ホラーゲームなら待ち伏せとナラティブビートの比重が違うでしょうし、パズルゲームなら環境を活用した戦闘が中心に来るでしょう。分類そのものを絶対視せず、自分のゲームの核心体験が何かをまず決めてから、カテゴリーを定めるべきです。
| カテゴリー | 核心体験 | 例 |
|---|---|---|
| pursuit | 追跡・逃走 | 通路追跡、峡谷からの逃走 |
| ambush | 待ち伏せ・奇襲 | ルーム進入時の待ち伏せ、視界の死角での待ち伏せ |
| puzzle_combat | 環境を活用した戦闘 | レバー・罠 + 戦闘 |
| boss_phase | ボスフェーズ | ボスフェーズ1〜3のパターン |
| narrative_beat | ナラティブビート | 回想トリガー、仲間の登場 |
5つのカテゴリーの中で、パターンはおおよそ30から50個の間を維持します。この数字には理由があります。パターンが100個を超えると、デザイナーがライブラリー全体を頭の中に収められなくなります。その瞬間、ライブラリーは検索に時間のかかる倉庫になり、デザイナーはいっそ新しく組むほうを選びます。ライブラリーがそっぽを向かれ始めると、一貫性という本来の目標が崩れます。だからこそ、パターン数の上限を意識的に管理することが、カテゴリー設計と同じくらい重要です。
パターン1つはYAMLファイル1枚で固定化されます。以下はプロジェクトAで実際に使っている形式を匿名化したものです。会社固有のアセット名とダンジョン番号は伏せましたが、フィールド構造と運用方式はそのままです。
---
pattern_id: pattern_corridor_pursuit_v2
category: pursuit
description: 狭い通路で速い敵が後方から追跡、プレイヤーは分岐点で回避を決定
tags: [horizontal_corridor, scholar_theme_compatible]
rooms:
- room_template: corridor_long
size: medium
connections_required: 2
- room_template: junction_3way
size: small
connections_required: 3
npc_behaviors:
- subtree_ref: subtree_aggressive_chase
count: 2
- subtree_ref: subtree_ranged_support
count: 1
events:
- type: trap_activation
trigger: room_1_midpoint
- type: enemy_spawn
trigger: room_1_entry
difficulty_modifier: 1.2 # 一般ルーム比1.2倍の負担
reward_modifier: 1.3
clear_time_estimate_sec: 60
art_pack_compatible: [scholar_library, generic_dungeon]
narrative_slots:
- slot: dialogue_during_chase
constraints: [short_dialogue, fear_emotion]
usage_count: 12 # 12個のダンジョンで使用
last_modified: 2026-05-18
deprecated: false
---
このファイルが、12個のダンジョンの一部分ずつを同時に定義しています。usage_count: 12という1行の重みはそこから来ます。このパターンを修正すれば12個のダンジョンが一斉に影響を受けるという意味であり、だからこそパターンファイルに手を入れることは、ルーム1つを直すこととは別の重みを持ちます。
subtree_aggressive_chaseやsubtree_ranged_supportのような参照は、7.2のBehaviorTreeエディターで定義したsubtreeをそのまま指しています。パターンがBTを直接抱え込まず、参照だけするのが核心です。BTを直せば、そのBTを参照するすべてのパターンが自動的についてきます。空間(ルームテンプレート)と行動(subtree)はそれぞれのライブラリーで管理され、パターンはその2つを結ぶ組み合わせ表の役割だけを担います。clear_time_estimate_secやdifficulty_modifierのような数値は著者の環境の運用値にすぎず、普遍的な定数ではありません。自分のゲームのシミュレーションとユーザーテストで直接測定して埋めるべきです。
ダンジョンをデザインするときは、パターンをゼロから組みません。ライブラリーから呼び出し、どこに置くかを指定し、このダンジョンでだけ変える部分をoverridesで上書きします。
---
dungeon_id: dungeon_021_silvermark_library
pattern_instances:
- instance: corridor_pursuit_1
pattern_id: pattern_corridor_pursuit_v2
placement:
- room_id: dungeon_021_room_03
as: corridor_long
- room_id: dungeon_021_room_04
as: junction_3way
overrides:
- field: npc_behaviors.0.subtree_ref
value: subtree_scholar_chase # 学者テーマの変種
- field: events.0.trigger
value: room_1_2nd_third # トリガー位置の微調整
---
ここでダンジョン021は「通路追跡」パターンをそのまま使いつつ、追ってくる敵を一般の敵から学者テーマの変種に変え、罠が作動する位置を通路の中間から少し後ろにずらしました。パターンの80%はそのまま、20%だけバリエーションを加えています。
この比率には、運用経験から得た根拠があります。バリエーションが少なすぎると(0%に近いと)、ダンジョン同士が写し合ったようで飽きられます。バリエーションが多すぎると(50%を超えると)、それはもう同じパターンではありません。同じパターンを呼び出したと信じていても実際の体験はまったく別物という、新人が持ってきたあのダンジョンとそっくりの状況に逆戻りします。だから私たちは運用ルールを置いています。1つのインスタンスのoverridesがパターンのフィールドの半分を超えたら、それはバリエーションではなく新しいパターンの兆候です。別パターンとして分離すべきときが来たのです。
pattern_corridor_pursuit_v2を修正すると、12個のダンジョンが影響を受けます。人がこれを手で追跡すると、必ず1つや2つは漏らします。だから、パターンとダンジョンの関係を自動で洗い出す小さなツールを置きます。
# pattern_impact.py
import json
from glob import glob
def find_dungeons_using(pattern_id):
affected = []
for d in glob("dungeons/*.json"):
dungeon = json.load(open(d, encoding="utf-8"))
for inst in dungeon.get("pattern_instances", []):
if inst["pattern_id"] == pattern_id:
affected.append({
"dungeon": dungeon["dungeon_id"],
"instance": inst["instance"],
"has_overrides": bool(inst.get("overrides")),
})
return affected
この関数が返すリストの核心はhas_overridesフラグです。overridesのないダンジョンはパターンをそのまま使っているので、自動更新しても安全です。overridesのあるダンジョンは、そのダンジョン固有のバリエーションがパターン修正と衝突しうるため、人による追加チェックが必要です。
修正の重みを人がいちいち感じ取る代わりに、ツールが「今回の修正でダンジョン12個が影響を受け、そのうち4個はバリエーションがあるので直接見る必要がある」と5分以内に報告するようにします。パターン変更への恐れを減らしてくれることが、このツールの本当の価値です。影響範囲が見えなければ、デザイナーはパターンをそもそも直そうとしなくなり、ライブラリーは更新されないままよどんでいきます。
ここで、最もよく受ける質問に正面から答えます。「パターンの作成もAIに任せればいいのではないですか?」
答えははっきりしています。だめです。パターン1つを作成する仕事は、デザイナーのインサイトが背骨です。良い追跡体験とは何か、分岐点がなぜそこでなければならないのか、罠がなぜ通路の中間ではなく2/3地点で作動してこそ緊張が生きるのか — これは、ゲームを直接触り、ユーザーの反応を見てきた人の判断です。AIにパターンをゼロから組ませると、すべてのパターンが無難で平均的な形に収束します。ライブラリーは「間違っていないパターン」で埋め尽くされますが、「記憶に残るパターン」は消えていきます。
だからといって、AIにやることがないわけではありません。パターン作成の5段階のうち2か所で、AIは強力な補助になります。
| 段階 | 産出物 | AIの役割 |
|---|---|---|
| 1. ゲーム内の良い体験の瞬間を観察 | ノート | デザイナー単独 |
| 2. その瞬間を空間・NPC・イベントに分解 | ドラフトyaml | デザイナー単独 |
| 3. 既存のルームテンプレート・subtreeにマッピング | 候補マッピング | AI補助(候補の推薦) |
| 4. シミュレーション + ユーザーテスト | 検証 | AI補助(シミュレーション実行) |
| 5. ライブラリーに登録 | usage_count = 0 | デザイナー単独 |
3段階目がAI補助の核心です。デザイナーが「狭い通路で速い敵が後方から追跡する」というドラフトを書いたとき、すでに数十個ずつ積み上がったルームテンプレートとsubtreeのライブラリーの中からどれがこの意図に合うかを、人が全部漁るのは非効率です。ここでAIにマッピング候補を推薦させます。以下は、実際に使っているプロンプトを匿名化したものです。
[入力]
- デザイナーのパターンドラフト yaml (下に添付)
- ルームテンプレートライブラリー (名前 + タグ + サイズの一覧)
- subtree ライブラリー (名前 + 行動要約の一覧)
このパターンを実装するルームテンプレートとsubtreeをライブラリーから選んで推薦して。
それぞれ2~3個ずつ、適合度1~5点とタグ・サイズ基準の一行の根拠を付けて。
ライブラリーにないものはでっち上げず推薦だけ、全部3点以下なら新規作成が必要だと言って。
このプロンプトにClaudeが実際に返した出力は次のとおりです。どこでためらい、どこで断言しているかがそのまま現れるよう、一文字も手を加えずに載せます。
ルームテンプレート候補:
1. corridor_long (適合度 5)
根拠: タグ horizontal_corridor が一致、size medium が「狭い通路」の意図に合致。
2. corridor_narrow_short (適合度 3)
根拠: 狭くはあるが長さが短く、追跡距離の確保が難しい。分岐点を配置する空間が不足。
3. hall_wide (適合度 1)
根拠: 広いホールは「狭い通路」の意図と正反対。追跡の圧迫感が生きない。
subtree 候補:
1. subtree_aggressive_chase (適合度 5)
根拠: 後方追跡の行動が定義済み、移動速度の重み付けあり。
2. subtree_ranged_support (適合度 3)
根拠: 追跡そのものではないが、圧迫の補助用に1体配置可能。
3. subtree_patrol_basic (適合度 2)
根拠: 巡回行動は追跡の意図と合わない。トリガー後に追跡へ切り替えるノードがない。
分岐点(junction)ルームテンプレート:
ライブラリーで 3way 分岐 + small サイズの条件を同時に満たす項目を
見つけられませんでした。junction_3way が最も近いものの size タグが空のため
適合度を確定できません。デザイナーの確認が必要です。
最後の段落が、この出力で最も価値ある部分です。AIは分岐点のルームテンプレートについて「確実ではない」と述べ、存在しない答えをでっち上げませんでした。デザイナーはこの報告を受けて、junction_3wayのsizeタグが実際に空であるという事実を発見し、まずそのメタデータから埋めました。もしAIが空のタグを無視して適合度5を付けていたら、デザイナーは誤った信頼のままそれを採用していたでしょう。
これがAI補助の正しい形です。AIは候補を広げ、不確実さを表示し、選択と責任はデザイナーに残ります。マッピングの結果、適合度がすべて低ければ、そのときは新しいテンプレートを作成する別の作業が発生し、その作成はまた人の仕事になります。
[方向標識 — パターンを「体験ベクトル」に圧縮してみるなら(まだ時期尚早)] 処方ではなく、研究動向として読んでください。§7.3.1がすでにパターンを「レシピ」と呼んでいます。1つのパターンは、ルームメタ・行動subtree・event・difficulty/reward_modifier・clear_timeが1セットになった座標値に近いものです。この束を「体験ベクトル」に圧縮すれば、適合度がすべて低いときに新規を組むという上記のフローを、いちいち漁る代わりに圧縮空間の空白領域として捉えられ、§7.3.8のdeprecated判定も、近接する重複を座標距離で補強できます。ただし3つの留保が付きます。difficulty/reward_modifierは§7.3.4のとおり著者の運用値であり、ゲームごとに軸のスケールが違うため圧縮空間をそのまま移植できないこと。補間はパターンの「生成」ではなく空白の「標識」までであること。そして、その標識の上でパターンを実際に組む仕事は依然としてデザイナーのインサイトが背骨だという、本節の原則を超えないこと。この発想は§8.2.7の次元ベクトル圧縮と同じ場所にあり、概念の直観は付録Mにあります — 土台が十分に積み上がったチームが数年後にのぞき込む領域として残しておきます。
ライブラリーは、埋めることより空けることのほうが難しいものです。運用を1年ほど続けると、作ったままほとんど使われないパターンが溜まります。放っておくとライブラリーの検索コストが上がり、デザイナーがパターンを選ぶとき、死んだ選択肢までなめる必要が出てきます。だから定期的に整理します。
| 条件 | 処理 |
|---|---|
| 6か月間usage_countの増加が0 | deprecated候補に分類 |
| 検討会議で廃棄を決定 | deprecated: trueを表示 |
| 既存の使用ダンジョン | そのまま保存(歴史的保存) |
| 新規ダンジョン | 該当パターンの使用禁止 |
核心は、廃棄が削除ではないという点です。すでにそのパターンを使っているダンジョンはそのまま残します。ライブサービスで稼働中のダンジョンに手を入れることのほうが、新規での使用を止めることより危険だからです。deprecated: trueは「今後新しく使わない」という標識にすぎず、過去を消す命令ではありません。
机の引き出しの使わない道具を四半期に一度取り出して整理するように、ライブラリーも四半期に一度整理する日程を組んでおきます。この日程がなければライブラリーは一方向にだけ膨らみ、いつしかデザイナーがそっぽを向く倉庫になります。
著者のプロジェクトAでパターンライブラリーを1年運用して観察した変化です。下の表の時間数値は著者の環境での推定(未検証)であり、方向と相対比率だけが実際に観察されたものです。
| 項目 | 導入前 | 導入後 | 備考 |
|---|---|---|---|
| ダンジョン1個のデザイン時間 | 約2週間 | 約1週間 | 著者の推定、方向は明確 |
| ダンジョン間の体験の一貫性 | 分散が大きい | 安定 | ユーザー評価ベース、定性 |
| パターン1個あたりの使用ダンジョン平均 | — | 約8個 | 量産効率の核心指標 |
| 新規デザイナーのオンボーディング | 約2か月 | 約3週間 | 著者の推定、最も大きな体感効果 |
| パターン変更の影響把握 | 1〜2日の手作業 | 自動5分レポート | pattern_impact.py導入の効果 |
最も印象的だった変化は、最後から2行目の新人オンボーディングです。パターンライブラリーは、意図せずデザインの教科書の役割を果たしました。新人が「このゲームの追跡体験はこう作る」をパターンファイル1枚で読んで理解できるようになり、先輩が横について説明する時間が大きく減りました。最初に新人が持ってきた、ばらばらなダンジョンの問題が、ライブラリーそのものによって解消されたわけです。
「パターン1個あたりの使用ダンジョン平均約8個」という数字は、同じパターンを8回再利用したという意味であり、これが量産効率の正直な物差しです。ただし、この8という値は著者のゲームのダンジョン規模とパターン設計に依存します。ダンジョン数が少なかったり、毎回違うコンセプトを要求されたりするゲームでは、この値はずっと小さくなります。
最後に、この章全体をひっくり返す話をしてこそ正直というものです。パターンライブラリーは万能ではありません。ライブラリーを構築・運用するコストが回収されない環境は、確かにあります。
| 条件 | 勧告 |
|---|---|
| ダンジョン5個未満 | 手作業で十分、ライブラリー不要 |
| デザイナー1人 | 頭の中がそのままライブラリー |
| リリース1回、運営(ライブオプス)なし | 再利用の機会自体が少ない |
| 毎回まったく違うコンセプト | 再利用比率が低くROI未回収 |
ライブラリーのROI(Return on Investment、投資対効果)は、3つの条件がそろったときに回収されます。運営(ライブオプス)があり、デザイナーが3人以上で、ダンジョンが20個を超える場合です。運営型MMORPGが典型的な適用対象である理由はここにあります。上の表のどれかの行に自分のプロジェクトが当てはまるなら、ライブラリーを建てる前に立ち止まって考え直すべきです。ツールは問題があるときにだけ価値があり、ダンジョン5個のプロジェクトにとって、パターンライブラリーは問題よりコストのほうが大きいのです。
| 症状 | 処方 |
|---|---|
| パターンが100個を超えてデザイナーが覚えきれない | 30〜50個に整理、四半期ごとにdeprecatedを整理 |
| パターンの影響追跡を手作業で行う(漏れが発生) | pattern_impact.pyのような自動追跡ツール |
| overridesが80%以上(実質的な再利用ではない) | バリエーションが大きすぎる → 別パターンに分離 |
| パターン作成をAIに丸ごと委任 | 作成はデザイナーのインサイト、AIは3・4段階の補助のみ |
| usage_countを測定していない | 自動集計 + 四半期の振り返りで検討 |
| 新人にライブラリーの説明がない | オンボーディング資料にライブラリーツアーを含める |
この表の2行目と4行目が、最もよく足を引っ張ります。影響追跡を自動化しなければデザイナーがパターン修正を恐れてライブラリーが固まり、作成をAIに委任すればライブラリーが平均に収束します。どちらの失敗も、ライブラリーの生命である「検証された体験の再利用」を殺してしまいます。
第7部は、レベル分野を3つの層で積み上げてきました。7.1でルームメタデータとタグと接続性の標準を立て(空間)、7.2でJSONベースのBehaviorTreeエディターとsubtreeとシミュレーションを扱い(行動)、本章でその2つをイベントとともに束ねて再利用するパターンライブラリーに到達しました(運用単位)。空間と行動を別々に扱う運用の中で、同じ場所の決定が毎週違う形で揺れていたあの問題を、パターンという束として固定化して解決したことが、第7部全体の幹です。
この流れは、Layer統合設計とそのままかみ合います。ゲーム全体の空間のトーンというビジョンが上にあり、その下にレベル生成ルールとBTルールというシステムがあり、ルームとBTとパターンライブラリーがコンテンツ層を成し、ダンジョンインスタンスとパターン使用統計がデータとして積み上がり、lintとシミュレーションとユーザーテレメトリーがビルド・QAでこれを検証します。パターンライブラリーはこの5層のうちコンテンツ層の背骨でありながら、上に向かってはシステムのルールに従い、下に向かってはデータの統計を生成する連結リングです。
patterns/とdungeons/の2つのディレクトリーを作ってください。pattern_impact.pyをそのまま保存してください。デザイナーが自分でパターンのドラフトyamlを書きます(この部分は人の仕事です)。そのうえで、AIにはマッピングだけを任せましょう。本文のマッピングプロンプトをそのまま使い、入力に自分のドラフトと2つのライブラリー一覧を添えてください。核心となる制約の2行を落とさないでください。
- ライブラリーにない新しいテンプレートをでっち上げないでください。推薦だけにしてください。
- 適合度がすべて3以下なら、新規作成が必要だと明記してください。
find_dungeons_using("pattern_...")を実行し、その2つのダンジョンが正確に捕捉されるか確認してください。一人で小さなゲームを作るなら、ライブラリーシステムは過剰です。代わりに、いちばん気に入っているダンジョン区間を1つ選び、その体験をyaml1枚に書き留めておくだけでも十分です。次のダンジョンを作るときに、その1枚を開いてコピーし、20%だけ直してみましょう。パターンライブラリーの本質 — 検証された体験の再利用 — は、ファイル1枚でも機能します。規模が大きくなったら、そのときにカテゴリーと追跡ツールを足せばよいのです。
本章の学習目標(難易度🟡実務・前提:四則演算・表計算):戦闘バランスを公式の居場所と数値の居場所に分離し、決定論・追跡可能性という2つの性質を根拠に、どこまでAIに任せ、どこから人がルールブックでロックすべきかを区別できるようになります。
午前2時、ライブサーバーのタンク職の生存率が89%に達したというアラートが届きました。ボスを最後まで倒せないタンクはいない一方で、死なないタンクが多すぎる。誰かが手を入れた痕跡を探そうと、マスターデータを開きます。防御係数の1行が目に入ります。DEF / (DEF + 1000)。この1000という数字がいつ、誰の手で、どんな根拠で1200から1000に引き下げられたのか、シートのどこにも書かれていません。チャットログを漁り、ビルド履歴を漁り、最後は3年前に退職したバランス担当者の記憶にたどり着いてようやく終わる追跡が始まります。
この場面は、戦闘バランスの運用を経験した人なら誰でも一度は通ります。そして、この場面の本当の原因は、その1000という数字が間違っていたことではありません。その数字が公式の居場所に住んでいたのに、公式が変わった履歴がどこにもなかったことにあります。戦闘バランスの公式は、ゲームの中で最も決定論的であるべき領域であり、最も追跡可能であるべき領域です。この2つの性質がなぜ、AIをこの場所に入れてはいけない理由になるのか。それが本章の背骨です。
専門外の方のための一言。 この部で出てくるz-score・シミュレーション・曲線になじみがなくても大丈夫です。持ち帰っていただきたいのは、ただ一つです — 「同じ入力には常に同じ出力でなければならないルール(公式)には、AIを入れない」。決定論が必要な場所と探索が必要な場所を分けるこの判断は、会計規定・精算ロジック・契約条項のような「間違えてはいけないルール」を扱うあらゆる職種に、そのまま持ち込めます。数式そのものは8.1.2からゆっくり見ていただいてかまいません。
ゲームデザインを長く続けていると、2種類のドキュメントが手に残ります。頻繁に変わるドキュメントと、ほとんど変わらないドキュメントです。戦闘バランスでほとんど変わらない側が公式です。「ダメージをどう計算するか」は四半期に1〜2回しか手を入れませんが、「このキャラクターの攻撃力はいくつか」は週に5〜6回手を入れます。頻度の違う2つの流れを1つのファイルに束ねると、頻繁に開け閉めする手の力で、たまにしか開け閉めしない紙のほうが破れてしまいます。
著者が運営するプロジェクトAでは、戦闘バランスは2つの居場所に分離されています。公式の居場所(ここではCombatFormulaと呼びます)と、数値の居場所(CombatBalance)です。公式の居場所に住む1行を、そのまま引用します。
final_damage = base_damage × dmg_multiplier × (1 − defense_factor) × variation
base_damage = skill_base × ATK × skill_coeff
defense_factor = DEF / (DEF + 1000)
variation = uniform(0.95, 1.05)
この公式はルールブックです。ボードゲームのルール冊子を思い浮かべてください。ルール冊子には「サイコロを振って出た目の数だけ進む」と書いてあり、「今回は運が良ければもう少し進んでもよい」とは書いてありません。同じ入力には、常に同じ出力。これが決定論(determinism)です。攻撃力180、防御力80、スキル係数2.1を入れたら、いつどこで何回計算しても同じダメージが出なければなりません。もし同じ入力に違う出力が出るなら、それはバランスツールではなくギャンブルマシンです。
この決定論という1つの性質こそ、AIをこの場所に入れてはいけない第一の理由です。これは後ほど改めて見ます。まず、公式がルールブックらしくあるためにはどんな形であるべきかを見ていきましょう。
戦闘公式の中核領域は、ダメージの1行では終わりません。少なくとも3行が1セットで住んでいます。
# ダメージ
final_damage = base_damage × dmg_multiplier × (1 − defense_factor) × variation
# クリティカル
crit_damage = final_damage × crit_multiplier
crit_chance = base_crit + (LUK × 0.1) # 上限 50%
# 回復
heal = base_heal × healing_power × (1 − sickness_factor)
この3行を自然言語ではなくコードブロックで書くことには理由があります。自然言語は解釈の余地を残します。「防御力が高いほどダメージが減る」という文は、線形に減るのか、曲線で減るのか、どこで止まるのかを語りません。DEF / (DEF + 1000)は、ただ一通りにしか読めません。解釈の余地を0にするのがルールブックの仕事です。
防御係数DEF / (DEF + 1000)の1行に、このゲームのバランス哲学の全体が詰まっています。この1行をグラフに描いてみると、なぜそうなのかが見えてきます。横軸が防御力、縦軸が受けるダメージを軽減する割合です。
この曲線は漸近線(asymptote)にゆっくり近づいていきます。防御力1000でダメージをちょうど半分に削り、その先はどれだけ上げても100%には届きません。無敵が不可能であることが、この1行に織り込まれています。灰色の点線のように線形だったら、防御力1000でダメージをすべて防ぎ、それ以上はマイナスダメージ(殴られるほどHPが回復する)という意味の通らない領域に突入します。だから線形は採用しませんでした。
ここで午前2時の事故に戻ってみましょう。誰かがこの1000を1200に上げたとします。曲線全体が右に押しやられます。同じ防御力で防げるダメージが減るので、ゲーム全体のタンクが弱くなり、アタッカーの時間あたりダメージが上がります。公式の定数1つがゲーム全体を揺るがします。 数値1つ(あるキャラクターの攻撃力)を変えるのとは、影響の大きさが違います。この差こそ、公式と数値を別の居場所に置くべき理由であり、公式の変更には必ず履歴が付いて回らなければならない理由です。
午前2時の追跡が地獄だった理由はただ一つ、変更履歴がなかったからです。プロジェクトAでは、公式の変更はコードを1行直す作業ではなく、決定1件を記録する作業です。公式の隣にはCombatFormula_Decisionsという別ドキュメントが付いて回り、そこにはこう書かれます。
## 決定 D17 (2026-04-22)
- 変更: defense_factorを DEF/(DEF+1000) → DEF/(DEF+1500)
- 事由: 高レベル帯(LV40+)でタンク生存率89%(ライブ測定)。ボス戦が間延びする原因。
- 試行 1: 800でシミュレーション → タンク死亡率が急増、ボス入場1分以内の全滅多数 → ロールバック
- 試行 2: 1200でシミュレーション → 生存率75% → 良好だが目標(60~70%)より高い
- 試行 3: 1500を採用 → シミュレーション生存率65%(目標範囲内)
- 影響 atom: combat_defense_formula, combat_tank_class_balance
- 事後測定(1週): ライブ生存率67%(シミュレーション予測65%比 +2%、範囲内)
この1件が、6か月後の「なぜこうなったのか」に答えます。さらに重要なのは、試行1と試行2が残っているという点です。800がなぜダメだったのか、1200がなぜ採用されなかったのかが記録されていれば、次の人が同じ失敗を繰り返さずに済みます。新しいバランス担当者が合流したとき、この決定ログ一式が最高のオンボーディング資料になります。
ここで正直に断っておくことが一つあります。上の試行1・2・3のシミュレーション数値(死亡率、生存率75%、65%)は、運用の流れを示すための著者の推定値(未検証)です。実際のゲームごとに曲線も目標範囲も異なります。ただし、「変更には試行が伴い、試行にはシミュレーションの根拠が伴い、採用の後には事後測定が伴う」という構造は、実際の運用そのままです。この構造のどこか1マスでも空けば、その空白が午前2時の追跡となって戻ってきます。
公式・数値・履歴という3つの居場所を一望すると、こうなります。
ここからは、D17がどう決定されたのかを最初からたどってみます。これが、決定論的なルールブックが実務で動く姿です。
flowchart TD
A["ライブ測定
タンク生存率89%を検知"] --> B["原因仮説
防御係数1000が後半で過保護"]
B --> C["変更候補の定義
1500 / 1200 / 800"]
C --> D["Damage Simulator
候補ごとに1,000回の決定論実行"]
D --> E["結果レポート
生存率・平均戦闘時間・勝率"]
E --> F{"バランス担当者の判断
目標60〜70%の範囲内?"}
F -->|"800: 死亡率が急増"| G["棄却 → ログ試行1"]
F -->|"1200: 75%、やや高い"| H["保留 → ログ試行2"]
F -->|"1500: 65%、範囲内"| I["採用 → 決定D17"]
I --> J["ビルド反映(不可逆)"]
J --> K["1週間後にライブ事後測定
67%、予測比+2%"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class D code;
class B,C,F human;
class A,E,K data;
class I pass;
class G fail;
この流れでは、シミュレーターの役割を正確に見る必要があります。Damage Simulatorは3つの候補をそれぞれ1,000回ずつ回します。ここでの1,000回は、同じ入力を1,000回繰り返すという意味ではありません。公式の中のvariation = uniform(0.95, 1.05)という±5%の乱数と、クリティカル率というもう一つの乱数のせいで、1戦1戦の結果が異なります。1,000戦を回して分布を見ます。平均生存率、最悪ケース、戦闘時間のばらつきを見るのです。
このシミュレーター自体が決定論的でなければならない、という点が重要です。同じ乱数シードを与えれば、1,000戦が一字一句違わず再現されなければなりません。そうしてはじめて、「1500で65%が出た」というD17の1行が、6か月後にもまったく同じように再現され、検証できます。シミュレーターが毎回違う結果を出すなら、決定ログは嘘になります。
著者がこのダメージシミュレーターを最初に作ったのは2008年です。当時はExcelマクロでしたが、今のプロジェクトAではbalance-simスキルとしてカプセル化されています。18年の間にツールの外側は変わりましたが、中に入っているルールブックは一度も確率的だったことがありません。これが核心です。
いよいよ、本章が最も言いたいことにたどり着きます。AIがゲームデザインのほぼすべての場所に入り込んでいる今、ただ一つ、絶対に入れてはいけない場所があります。戦闘公式と報酬曲線という、決定論の中核です。
LLMは本質的に確率的です。同じ質問に、毎回少しずつ違う答えを返します。それが良い文章とアイデアを生み出す力の源泉ですが、ルールブックの居場所には致命的です。「防御力80のキャラクターはダメージをいくつ受けるか」をLLMに答えさせると、今日は92、明日は94と答えかねません。ボードゲームのルール冊子で、ページをめくるたびにサイコロの目の意味が変わるようなものです。
報酬曲線はさらに危険です。「レベル30から31に上がるときの必要経験値」は、一度決めれば数十万人の進行速度を同時に規定します。ここに±2%の揺らぎが入るだけで、あるユーザーは同じ狩りをしても隣の人より遅く育ちます。公平性が崩れます。決定論は公正さと同義です。だから報酬曲線は人が手で決め、シートに入力し、二度と確率には任せません。
とはいえ、バランス領域全体からAIを追い出せという話ではありません。境界こそが核心です。
| 領域 | AI | 理由 |
|---|---|---|
| ダメージ・回復公式の計算 | 絶対禁止 | 決定論コア。同じ入力 = 同じ出力が崩れればギャンブルマシン |
| 報酬・経験値曲線 | 絶対禁止 | 数十万人の進行を同時に規定。揺らげば公平性が崩壊 |
| シミュレーター内部の演算 | 絶対禁止 | 再現不能になれば決定ログが嘘になる |
| シミュレーション結果の異常パターン検知 | 可 | 1,000件の結果から「このキャラクターは正常範囲外」をz-scoreで検知 |
| 変更候補の探索 | 可 | 「base_atk ±10%の範囲で候補を5つ提案」のような限定探索 |
| 決定ログの草案作成 | 可 | 会議内容 → Decisions項目の草案(人がレビュー) |
| 事後測定レポートの要約 | 可 | ライブデータの自然言語要約 |
線は明確です。AIは決定論コアの外側にだけ住みます。 計算しシミュレーションする内側はルールブックの領分であり、分析し、提案し、文章に起こす外側がAIの居場所です。この線を一度越えると、同じ入力に違う結果が出始め、その瞬間からバランスツールは信頼を失います。
この境界は、8.2で見る経済システムとまったく同じ構造です。経済でも、リソースの生産・消費の公式は決定論であり、インフレのパターン検知がAIの居場所です。バランス分野全体が同じ骨格で動いています。
ここまでは、人が候補を作り、シミュレーションが検証する保守的適用でした。もう一歩進めば、候補を作る仕事までツールが代行できます。ただし、ルールブックは依然として人と決定論のものです。
異常パターン検知が出発点です。1,000戦のシミュレーション結果からキャラクターごとの勝率・生存率の分布を見て、平均から標準偏差の何倍離れているかをz-scoreで測ります。zが2を超えるキャラクターは「正常範囲外」として自動的にマークされます。午前2時のタンクも、この検知に引っかかっていたはずです。
検知を候補の発議につなげるには、あと2つ必要です。1つ目は変更空間の定義です。CombatBalanceシートにtunable_rangeのような列を設け、「この数値はどの範囲までなら触ってよいか」を明示します。2つ目はシミュレーションの並列化です。候補10個 × 1,000戦 = 10,000戦をビルドゲートの時間内に回すには、並列インフラが必要です。
この3つ(z-score検知・変更空間の定義・シミュレーション並列化)がそろえば、バランス担当者の手に残る決定は「どの候補を採用するか」の一つに絞られます。候補を0から作る仕事と、5つの中から選ぶ仕事では、負担が違います。ここでもAIが触れるのは候補の発議とレポートの解釈だけで、シミュレーション内側の演算と採用の決定は、決定論と人の居場所です。
最後に可逆性に触れておきます。シートの修正もシミュレーションの実行も可逆なので、いくらでもやり直せます。不可逆なただ一つの場所が、ビルド反映です。ライブに出た数値は、ユーザーが目にした瞬間からコミュニティの反応として残り、ロールバックしても痕跡は消えません。だから、人によるチェックはすべて、ビルド反映の直前、可逆の段階で終わらせます。
setup. 戦闘公式を自然言語の説明から切り離し、コードブロックだけで書いたCombatFormulaドキュメントと、その隣に空のCombatFormula_Decisionsログドキュメントを作りましょう。数値は別のシート(CombatBalance)に切り出します。
prompt. 公式の変更ではなく、分析・草案にだけAIを使いましょう。たとえば、シミュレーション結果のCSVを渡して、こう依頼します。
添付した1,000回のシミュレーション結果からキャラクター別勝率のz-scoreを計算し、
z>2のキャラクターを表に整理して。各キャラクターについて
どの数値(攻撃力/防御力/スキル係数)が異常の原因である可能性が高いか、
根拠とともに推定して。数値そのものは修正しないこと — 候補のみ提案。
verify. AIが出した候補をそのまま信じてはいけません。候補の数値をCombatBalanceシートに自分で入力し、Damage Simulator(またはbalance-sim)に同じシードを与えて1,000回を回し直します。確認するのは2つです。(1)シミュレーション結果が目標範囲に収まるか。(2)同じシードでもう一度回して、一字一句違わず再現されるか。両方通れば採用し、採用したらすぐに_Decisionsへ理由・試行(棄却した候補を含む)・予測値を書きましょう。ビルド反映の1週間後、ライブの測定値をそのログに追記します。
チームもシミュレーターもない1人開発でも、骨格は同じように機能します。公式はコードのコメントか、別の.md1枚にコードブロックで記録し、そのファイルの一番下に## 변경 이력を置きましょう。公式の定数を1つでも変えたら、日付・理由・変更前の値を1行書きます。シミュレーターは30行のPythonループで十分です。乱数シードを固定し、公式にキャラクターの数値を入れて1,000回回し、平均勝率だけ出力するだけでも、「勘で変えた」から「根拠で変えた」へ進めます。AIは、その出力CSVを読んで「どのキャラクターがおかしいか」を要約することだけに使いましょう。公式の1行をLLMに計算させることだけは、規模に関係なくやめましょう。
第一想定読者:ライブ経済に責任を持つMMORPGのバランス/システムプランナー(中規模(10〜50人)チーム) 一人/趣味の読者向け縮小バージョン:§8.2.10「一人ならこれだけ」
ゴールドが漏れ始めたことに最初に気づいたのは、請求書ではなく取引所でした。リリース2か月目、強化石の相場がじわりと上がり、1か月後には2倍になりました。原因を探るために会議を設定しましたが、会議室から出てきたのはすべて「感覚」でした。ある人は新ダンジョンの報酬が過剰だと言い、ある人は狩り場の効率が上がったせいだと言い、ある人は単に高レベルユーザーが増えたからだと言いました。どれももっともらしく、だからこそ何も決まりませんでした。1時間を推測で燃やし、「ひとまず来週もっとデータを見よう」で終わりました。
問題は、リソースが1つではないことにあります。ゴールド・強化石・評判・名誉・ソウルストーンがそれぞれsource(入ってくる経路)とsink(出ていく経路)を持ち、その経路同士が互いを養い合います。強化石のボスはゴールドも落とします。ゴールドで買った装備が強化石を燃やします。リソース5種にフローが数十本も絡み合うと、頭の中の計算機では1つのリソースの1週間の収支すら正直に出せません。本章はその絡み合いをMachinationsのノードモデルへ移し、経済変更の意思決定を会議の推測ではなくシミュレーションゲートで通す方法を扱います。経済設計の一般理論は他の本に十分書かれていますから、本章はその理論をAIワークフローで回す場面だけに集中します。
著者の実運用メモ 本章の事例は、著者が会社のR&Dフォルダで運用中の経済パイロット文書(
Economy_Machinations_Pilot)と経済リサーチ作業領域を匿名化したものです。リソースの種類・source/sink構造・Pilot 4段階は実際の運用を忠実に写し、会社固有の名称・実数値は書籍用に置き換えるか、比率・方向のみで記しました。AI出力の本文は実際のセッションを再構成したものです。
経済リソースを表に書くと5行なので、単純に見えます。落とし穴はリソースではなく、リソースをつなぐフローの本数にあります。
| リソース | source(流入) | sink(流出) |
|---|---|---|
| ゴールド | 狩り、クエスト報酬、取引所での販売 | 装備購入、強化、修理、税金 |
| 強化石 | ダンジョンボス、イベント | 装備強化、合成 |
| 評判 | サイドクエスト | 勢力商店、転職 |
| 名誉 | PvP、ギルド戦 | PvP商店、ギルド施設 |
| ソウルストーン | ボス討伐 | キャラクターの復活、スキル習得 |
リソースは5種ですが、source・sinkは合わせて二十数個あり、しかもリソース同士が変換されます(ゴールドで強化石を買う取引所は、ゴールドのsinkであると同時に強化石のsourceです)。フローが互いを養い合った瞬間、「ゴールドを5%多く供給したら強化石の相場はどうなるか」のような質問は、1つのリソースだけを見ても答えが出ません。これがキャラクターバランス(8.1)と経済バランスの決定的に違う点です。キャラクターバランスは数式1行で閉じますが、経済は時間とともに累積する動的システムなので、1週間の収支が0に近くても、26週を累積すると取引所が崩れます。
だから経済作業の本質は「数字をうまく選ぶこと」ではなく、「フローが時間とともにどう累積するかをシミュレーションで見ること」です。そして、そのシミュレーションモデルを手で組んで修正する作業は退屈で、やるたびに抜け漏れが生まれます。反復的で抜けやすいドラフト作業、しかしレビューは人ががっちり握るべき仕事 — この手触りの作業こそ、AIと人の分業線が最もきれいに引ける場所です。
まず、本章が扱う経済循環の骨格を1枚で載せておきます。
%%{init: {"flowchart": {"defaultRenderer": "elk"}}}%%
flowchart LR
subgraph SRC["source (リソース生産)"]
H["狩り場"]
Q["クエスト"]
B["ダンジョンボス"]
PVP["PvP/ギルド戦"]
end
subgraph POOL["pool (リソース貯蔵)"]
G(("ゴールド"))
S(("強化石"))
end
subgraph SINK["sink (リソース消費)"]
UP["装備強化"]
RP["修理/税金"]
SH["勢力/ギルド商店"]
end
H --> G
Q --> G
B --> S
PVP --> SH
G -->|取引所での変換| S
G --> UP
S --> UP
G --> RP
UP -.->|強化石需要 ↑| S
G -.->|"純流入 > 純流出のとき
インフレが累積"| POOL
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class G,S data;
点線が本章の核心です。強化sinkが強化石の需要を引き上げて強化石の相場を押し上げ(UP -.-> S)、ゴールドの純流入が純流出を超えると、その超過分が毎週プール(pool)に積もってインフレとして累積します。この2本の点線を手計算で追跡するのは不可能なので、モデルが必要になります。
Machinationsは、経済のフローをノードグラフとして描き、その上でシミュレーションを回すツールです。§8.2.1のmermaidを、実際に回せるモデルへ移す場面です。
| ノード | 役割 | 上の図では |
|---|---|---|
| Pool | リソースの貯蔵庫 | ゴールド・強化石 |
| Source | リソースの生産 | 狩り・クエスト・ボス |
| Drain | リソースの消費 | 強化・修理・商店 |
| Converter | リソースの変換 | 取引所(ゴールド→強化石) |
| Trigger | 条件付きの発動 | イベント・昇級報酬 |
これらのノードで経済をモデリングし、シミュレーションを1,000回回すと、単一の結果ではなく分布が得られます。「26週後のゴールド相場の中央値+X%、上位10%のユーザーは+Y%」のような形です。ただし、Machinationsは万能ではなく、導入自体がコストです。
| 限界 | 処方 |
|---|---|
| ゲームのコードとは別に動くため同期がずれる | 実際のテレメトリー(telemetry)で毎月/四半期ごとに補正(§8.2.6) |
| ノードグラフが大きくなると可読性が崩壊する | リソース別サブグラフに分割し、単一リソースから(§8.2.4) |
| シミュレーションは単純化されたユーザーモデル | 実際の行動分布で補正し、誤差のしきい値を設定 |
| 結果の解釈がドメイン知識に依存する | シミュレーション数値→意思決定をつなぐゲートの標準化(§8.2.5) |
そのため、Machinationsは無条件で導入するツールではありません。リソース5種以上+リソース変換フロー+運営(ライブオプス)という3つの条件が重なったときに元が取れます。リソース2〜3種の単純な経済ならExcelで十分で、その場合のMachinations導入は、効果より運用負担のほうが先に届きます。
ツールの説明だけでは、これが実際に何を吐き出すのか分かりません。ゴールド1つをMachinationsモデルへ移す1サイクルを、入力プロンプトから人による拒否まで、最後まで追いかけます。入力プロンプトはそのままコピーして使え、出力は実際のセッションを再構成しました。
まず、ゴールドのsource・sinkをマスターデータから抜き出して表にします。新しく書くのではなく、抽出です。
# gold_flows.yaml — ゴールド単一リソースのフロー (現行データシート抜粋)
resource: gold
sources:
- id: hunting # 狩り場ドロップ
trigger: per_kill
note: レベル帯別ドロップ曲線はreward_curveルールを適用
- id: quest_reward # クエスト報酬
trigger: per_complete
- id: market_sell # 取引所での販売
trigger: per_trade
sinks:
- id: gear_buy # 装備購入
- id: enhance # 強化コスト
- id: repair # 修理
- id: tax # 取引所税金 (sinkでありゴールド回収の核心)
# ユーザー行動分布(時間あたり狩り回数・クエスト完了率)はまだ空 → AIが仮定したら明示させる
添付のgold_flows.yamlは、ゴールド単一リソースのsource 3個・sink 4個だ。
これをMachinationsモデルへ移すノード仕様のドラフトを作れ。
ルール:
1) 各フローをノード種類(Source/Drain/Pool/Converter)に分類せよ。
2) 各ノードに「1週基準・ユーザー1人あたり期待流量」の算式を提案しつつ、
ユーザー行動の仮定(時間あたり狩り回数・クエスト完了率など)が必要なら
その仮定を別の行に「★仮定」と明示せよ。仮定を本文に隠すな。
3) sourceの総流入とsinkの総流出を分離し、1週の純収支(net)を計算せよ。
4) 取引所税金(tax)がゴールドを経済から永久に除去する唯一のsinkであることを
反映し、他のsink(強化・修理)はゴールドがNPCへ移動するだけで回収では
ないことを区別せよ。
5) 君が「確信できない」仮定は別途★を付け、人の決定に渡せ。
出力形式: 表 (ノード | 種類 | 1週流量算式 | 仮定 | 確信度)。それ以外の説明は禁止。
このプロンプトで実際に長く書かれているのは、モデルを作れという指示ではなく、仮定をどこにどう書くかというルールです。経済モデリングでAIを自由に放つと、もっともらしい数字を、仮定を隠したまま吐き出します。そうなると、モデルが間違っていてもどこが間違っているのかを指させません。ユーザー行動の仮定を本文の外へ引きずり出し、別の行に立たせること — そこにこの依頼の重みがすべて載っています。仮定が見えてこそ、検証できます。
| ノード | 種類 | 1週流量算式 | 仮定 | 確信度 |
|--------------|-----------|----------------------------------------|------------------|-------|
| hunting | Source | 時間あたり討伐 × 平均ドロップ × 週間プレイ時間 | ★週15h、討伐80/h | 中 |
| quest_reward | Source | デイリークエスト数 × 報酬 × 7 × 完了率 | ★完了率 0.7 | 中 |
| market_sell | Source | (ユーザー間の移転 — 純増ではない) | 純経済には0寄与 | 高 |
| gear_buy | Drain | 購入頻度 × 平均価格 | ★週1.2回 | 低 |
| enhance | Drain | 強化試行 × 1回あたりコスト | ★週8回 | 中 |
| repair | Drain | 狩り時間 × 修理率 | | 高 |
| tax | Drain | 取引額 × 税率 (★唯一の永久回収sink) | ★税率 5% | 高 |
| ── 純収支 | net | Σsource − Σsink ≈ +(中活性基準で正) | 上の仮定の合算時 | ★低 |
★仮定の警告: market_sellはユーザー↔ユーザーのゴールド移動で、経済総量には0寄与。
これをsourceとして足すとインフレを二重計上する。モデルで別途処理が必要。
★確信できない点: gear_buy頻度(週1.2回)は推測。実際の購入telemetry
なしではsinkが過小/過大に推定されうる → 人の決定が必要。
出力で最も価値があるのは表ではなく、一番下の2行、「★가정 경고」(★仮定の警告)と「★확신 못 하는 점」(★確信できない点)です。AIが自分のモデルの弱点を2か所、自ら申告しました。良いプロンプトは、AIに「この仮定は信じられません」と言わせます。
この出力をそのままモデルに入れてはいけません。AIが申告した2つの★のうち1つが、実際にモデルを壊す欠陥でした。
market_sell(取引所での販売)を、AIは最初Sourceに分類しました。しかし、取引所での販売はユーザーAのゴールドがユーザーBへ渡る移転であって、経済にゴールドが新しく生まれるわけではありません。これをsourceの流入に足すと、インフレを二重に計上することになります。AIは★仮定の警告で自ら指摘してはいたものの、表の本文では依然としてSourceの欄に残していました — 申告はしたが、モデルからは外していない、半分だけ正しい出力です。これは、入力のyamlでmarket_sellの性格(ユーザー間の移転か新規生成か)を明示しなかった、人側のデータ欠陥でもありました。
そこで、再依頼します。
market_sellはユーザー↔ユーザーのゴールド移転であり、経済総量のsourceではない(入力
漏れの修正)。このノードをsource合算から外し、代わりに「取引所税金(tax)が
移転額の一部を永久回収するsink」としてのみモデルに反映せよ。純収支を
再計算し、market_sell除外がnetに与えた影響を1行で示せ。
AIはmarket_sellをsourceから取り除き、税金だけをsinkに残したモデルで答え直しました。その結果、純収支(net)は最初の推定より低くなりました — 取引所での販売をsourceに誤って入れていたとき、インフレを過大評価していたことが明らかになったのです。この1往復こそが核心です。人が最初から手で組めば半日かかり、ノード分類のミスを本人が見つけるのは難しいのに対し、AIドラフト+「仮定の明示」の強制+1回の拒否なら1時間以内で済み、AIが申告した★を人が判定する構造のおかげで、二重計上のような欠陥がモデルに入る前に捕まります(著者の推定 — 節約できる時間はチームやリソース数によって異なるため、絶対値よりも「手で最初から」と「ドラフト+人によるレビュー」という構造の違いとして読むのが正しいです)。
ゴールドのモデルが1つ閉じたからといって、全リソースを一度にモデリングしてはいけません。著者の運用でも、全体を一度に投入しませんでした。単一リソースから始め、検証・補正を経て拡張する4段階を踏みました。
| 段階 | 範囲 | 核心ゲート |
|---|---|---|
| 1. 単一リソース(ゴールド)のモデリング | source 3・sink 4、§8.2.3のセッション | ノード分類・仮定の明示 |
| 2. シミュレーションvs実測の比較 | シミュレーションの1週net vs テレメトリーの1週 | 誤差しきい値の通過可否 |
| 3. モデル精度の補正 | ユーザー行動分布(低/中/高アクティブ)の反映 | セグメント別誤差の再測定 |
| 4. リソース拡張(5種) | 強化石・評判・名誉・ソウルストーンを段階的に追加 | 変換フロー(取引所)の検証 |
2段階目の比較検証が、この4段階の心臓です。シミュレーションと実測がずれているなら、間違っているのはゲームではなくモデルです。ずれたモデルで意思決定を下せば、その決定はライブで事故になって返ってきます。だから、拡張(4段階目)は必ず2・3段階目の検証を通過した後にだけ行います。この順序が崩れると、つまり単一リソースの検証を飛ばして5種を一度に入れると、どのリソースのモデルが間違っているのかさえ切り分けて指させなくなります。
モデルが検証を通過したら、今度は経済に影響するすべての変更決定の前にシミュレーションゲートを立てます。会議で「感覚」によって通していた決定を、シミュレーションの通過に置き換える場面です。
| 決定の種類 | シミュレーション義務 |
|---|---|
| source・sinkの新規追加 | 必須 |
| リソース変換比率の変更(取引所レートなど) | 必須 |
| 新ダンジョン・イベント報酬の設計 | 必須 |
| 価格変更(±10%以上) | 必須 |
| 新規職業の効率検証 | 必須 |
| UI変更など経済と無関係 | 免除 |
ゲートが実際にどう機能するのか、§8.2.3で検証したゴールドモデルの上で、1つの決定を通してみます。
[シミュレーションゲート — イベント報酬の決定](実際の形式を再構成)
[変更案] 週末イベント: デイリーログイン報酬 +500 ゴールド [ゲート] source新規追加 → シミュレーション必須 [シミュレーション1000回の結果] - 1週のゴールド純収支: +6,900 → +10,400 (+50%) - 26週累積時のゴールド相場中央値 ~+28% (インフレ警告: ±10%超過) - 上位10%活性ユーザー: ~+41% (segment偏差が大きい) [判定] FAIL — 安定範囲(±10%/長期)超過 [補正案] イベントsourceに同時sinkを付着: イベント限定商店(ゴールド回収) 再シミュレーション → 26週累積 +9% (PASS)
ゲートの価値は最後の2行にあります。「報酬+500を配ろう」という決定が会議の推測だったなら、「大丈夫そうだ」で通っていたでしょう。シミュレーションゲートはその決定を26週で+28%のインフレに換算して見せ、sourceを追加するならsinkを一緒に付けよという補正案まで強制します。経済変更を推測ではなく、シミュレーションの通過/失敗で判定すること — これがゲートのすべてです。
ここで、よくはまる罠を1つ指摘します。セグメントの偏差です。中アクティブユーザー基準で+28%でも、上位10%は+41%です。ゴールドを最も多く稼ぐユーザー層がインフレを最も速く累積させるため、シミュレーションは平均だけを見ずに、セグメント別に回すべきです。平均だけを見ると、高アクティブユーザー発の相場崩壊を見逃します。
シミュレーションゲートが信頼されるには、モデルが実際のゲームとずれていてはいけません。ゲームは毎週変わるので、モデルも追いかけて補正する必要があります。リリース後は、実際のテレメトリーで毎月(変更が少ない時期には四半期ごとに)モデルを突き合わせます。
モデル補正サイクル (月間)
─────────────────────────────────
1. 実際のユーザーtelemetry 1か月分を抽出 (リソース別フロー集計)
2. segment(低/中/高活性)別のsource·sink実測流量を算出
3. Machinationsシミュレーションと項目別に比較
4. 誤差 >15% の項目 = モデルパラメータ調整 (その項目の★仮定が違っていた印)
5. 調整後に再シミュレーション → 翌月ゲートの基準モデルとして使用
核心は4番です。誤差が大きい項目は、すなわち§8.2.3でAIが★で申告した「確信できない仮定」が実際とずれていたという信号です。たとえば、AIが推測したgear_buyの頻度(週1.2回)が実測では週2回だったなら、その仮定をテレメトリーの値に置き換えます。この補正を止めるとモデルがゲームからゆっくり離れていき、どこかの四半期で、シミュレーションゲートが「通したのに実際にはインフレが来た」という事故を起こします。その瞬間、シミュレーション自体の信頼が事後的に崩れます。補正は運用の付随作業ではなく、ゲートを生かしておくための正規のサイクルです。
ここまでが経済モデリングの「保守的適用」です。人が変更を発議し、モデルで検証し、結果で決定します。もう一歩進むと、8.1.6で見た進歩的適用の3つの軸 — z-score検知・変更空間の定義・シミュレーション並列化 — が、経済インフラの上でも同じように開けます。
第一に、異常パターン検知。毎月の補正サイクル(§8.2.6)の誤差比較を人が目で行う代わりに、モデルと実測の偏差がしきい値を超えた項目を、コードが先に拾い上げます。強化石の相場が2倍になったことを取引所を見て知るのではなく、「強化石のsource流量がモデル比+30%乖離」というアラートが、会議の前に届きます。
第二に、変更空間の定義。「報酬を+500配る/配らない」の二分法ではなく、報酬の範囲(0〜+1000)と同時sinkの範囲を変更空間として定義しておけば、その空間の中でインフレ±10%を満たす組み合わせを探索できます。人は「どこからどこまで」を決め、その中の最適な組み合わせの探索は自動化します。
第三に、シミュレーション並列化。変更案1つを1,000回回す代わりに、変更空間の中の候補数十個を並列に1,000回ずつ回して、分布を一度に比較します。会議室で1案ずつ議論していた場が、候補マトリクスのシミュレーション結果の比較に変わります。
共通する思想は、人が変更を発議していた場を、コードが変更空間を探索する場へ移すことです。ただし、保守的適用(§8.2.3\~8.2.6)が安定して回り、モデルがテレメトリーで検証された後の話です。検証されていないモデルで変更空間を自動探索すると、間違ったモデルが間違った最適値を自信満々に出してきます。
[急進的適用 — 経済を「次元ベクトル」に圧縮して探索する](まだ時期尚早)
進歩的適用からさらに一歩進んだ領域です。断定ではなく、研究動向として読んでください(次元ベクトル・埋め込み(エンベディング)が初めてなら、付録Mの「地図」を1枚先に見ると、以下が読みやすくなります — 本書の5つの「方向標識」は、すべてその図の上で回っています)。ここまで来た経済モデルは、リソース5種にフロー数十本が絡んだ高複雑度のシステムで、§8.2.7の変更空間の探索も、結局はその数十本のフローを1つずつパラメータに取って回す方式です。急進的な発想は、この複雑度自体を次元ベクトルに圧縮し、その圧縮空間の上で解を探すことです。
遠く見える比喩が1つ、手がかりになります。定性的で手につかみにくい領域の代表としてよく挙げられる料理レシピを、ある研究(Epicure — Radzikowski·Chen, 2026, arXiv:2605.22391 · デモ epicure.kaikaku.ai)では、11の出典のレシピ414万件から標準食材1,790種を選り出し、食材同士の関係を数百次元のベクトルに圧縮しました。核心は、「味」という定性的な対象も、食材間の関係を座標に換算すれば、似たレシピはベクトル空間で近くに集まり、その間を補間して新しい組み合わせを探索できるという点です — Epicureもこの圧縮空間で、1つの食材を特定の料理圏の方向へ回転させて対応する食材を見つける補間探索を示しています。
経済も原理は同じです。source・sink・変換フローをそれぞれ次元に取ったベクトルで経済状態を表現すれば、「インフレ±10%以内で安定した経済」が、その空間の1つの領域として捉えられます。そうなれば、変更案を1つずつシミュレーションにかける代わりに、その安定領域の中/近くで解を直接探索する道が開けます。候補を逐一回して比較していた§8.2.7の並列シミュレーションが、圧縮空間上の探索1回に絞り込まれる可能性です。
なぜ「まだ時期尚早」なのか。第一に、何を次元に取るか(どのフローが独立で、どれが従属か)を決めること自体が、ドメインの難題です。第二に、圧縮は本質的に情報を捨てる作業なので、捨てた次元でライブの事故が起きえます。第三に、これらすべては、保守的適用のテレメトリー検証(§8.2.6)が固いときにだけ意味があります — 圧縮前のモデルがゲームとずれていれば、圧縮はその誤差まできれいに圧縮するだけです。だから本節は処方ではなく、方向標識です。いまやるべきことは保守的適用を正直に回すことであり、次元ベクトルは、その土台が十分に積み上がったチームが数年後にのぞき込む研究領域として残しておきます。
ツール導入の前後を比較します。以下の時間・頻度は導入初期の運用で体感した方向を写したものなので、精密な絶対値として読むのではなく、どちらへ動いたかとして読むのが正しいです。
| 項目 | 導入前(会議・手計算) | 導入後(シミュレーションゲート) |
|---|---|---|
| 経済変更の決定→適用 | 2〜4週間(推測・再議論の繰り返し) | 1〜3日(シミュレーション検証1回) |
| インフレ事故 | 四半期あたり1〜2件(事後発見) | 四半期あたり0〜1件(ゲートで事前遮断) |
| source・sink追加の頻度 | 四半期に1〜2回(怖くて保守的) | 月1〜2回(シミュレーションが安全を保証) |
| 経済会議の頻度 | 週3〜4回 | 週1〜2回 |
表の数字より、最後の行の意味のほうが大きいです。会議頻度の減少は、シミュレーションが議論を置き換えたからです。「私の考えでは強化石にインフレが来そうだ」が「シミュレーション結果は26週で+28%」に変わると、推測をめぐって1時間争っていた場が、5分の結果共有で終わります。これは、著者のシステムの振り返りに固定化された概念(atom automation_signal_value_over_time_savings — 自動化の価値は時間の節約ではなくシグナルの露出)と正確に同じ場所です。シミュレーションゲートの本当の産出物は、節約された時間ではなく、会議で推測が占めていた場所を数字が占めるようにしたことです。
ただし、1つだけは正直に書いておきます。表の「四半期あたり1〜2件→0〜1件」は精密な測定値ではなく、運用の体感としての方向です。インフレ事故は定義(相場±何%を事故と見なすか)によってカウントが変わるため、絶対件数よりも「事後発見から事前遮断へ移った」という構造の変化として読むのが正しいです。
| パターン | なぜ失敗するか | 処方 |
|---|---|---|
| 全リソースを一度にモデリング | どのリソースのモデルが間違っているか切り分け不能 | 単一リソースのPilotから(§8.2.4) |
| AIモデルの仮定をレビューなしで受け入れる | 取引所の二重計上のような欠陥がそのまま入る | 仮定の明示を強制+人による拒否(§8.2.3) |
| シミュレーションゲートなしで経済変更 | 事後のインフレ回復コストが莫大 | 義務シミュレーション項目の定義(§8.2.5) |
| 平均だけシミュレーションし、セグメントを無視 | 高アクティブユーザー発の相場崩壊を見逃す | セグメント別シミュレーション(§8.2.5) |
| リリース後にテレメトリー補正をしない | モデルがゲームから離れてゲートの信頼が崩壊 | 月/四半期の補正サイクル(§8.2.6) |
| 検証されていないモデルで変更空間を自動探索 | 間違ったモデルが間違った最適値を確信を持って出す | 保守的適用の安定後に進歩的適用へ(§8.2.7) |
2つ目が最も頻繁に見落とされます。§8.2.3で見た取引所の二重計上のように、AIはもっともらしいモデルを自信を持って吐き出しつつ、自分の仮定の弱点は★で申告するだけで、本文には欠陥を残しておきます。その★を人が判定しなければ、間違ったモデルが通過し、その上のすべてのシミュレーション決定が一緒に間違います。
一人ならこれだけ:Machinationsもテレメトリーもなくて構いません。自分のゲーム(または好きなゲーム)のリソースを1つ選んでsource・sinkを紙に書き出し、§8.2.3のプロンプトをそのまま貼り付けて、1週間の純収支モデルのドラフトを受け取ってみましょう。AIが★で明示した仮定を1つ選んで「この仮定は信じられない。根拠をもう一度示せ」と反論してみると、経済モデルがどんな仮定の束なのか — そして、その仮定が1つ崩れると結論がどうひっくり返るのか — が体に入ってきます。
チームなら、次の1ステップから始めましょう。全リソースではなく、最も問題のあるリソース1つ(普通はゴールドか強化石)を選んで§8.2.3の単一リソースモデルだけを先に立て、経済変更の決定1種類(例:イベント報酬)に§8.2.5のシミュレーションゲートをかけてください。リソース1つ+決定1種類だけでも、会議で推測をめぐって争っていた場を、数字1行に変えられます。
setup→prompt→verifyで要約すると — setup:問題のリソース1つのsource・sinkをyamlに抽出します。prompt:§8.2.3の形式でノードモデルのドラフトを受け取りつつ、ユーザー行動の仮定を★で明示させます。verify:AIが申告した★の仮定とノード分類(特にユーザー間の移転か新規生成か)を、人が直接拒否・再依頼します。
2008年のある未明、私は1枚のExcelを前に、同じ数字を3回検算していました。仕様書には、ある剣キャラクターの秒間ダメージ(DPS)が847と書かれていました。ところが、その日初めて回したシミュレーターは、同じキャラクターを同じスペックで入れたのに612を吐き出しました。27%の差。どちらか一方は嘘をついていて、私はどちらが嘘なのか、まだ分かりませんでした。
仕様書のDPSは、紙の上の約束です。スキル1発のダメージに発動頻度を掛けた算数です。シミュレーターのDPSは、その約束を1,000回実際に振ってみた結果です。クールタイム(クールダウン)が重なり、発動モーションに時間を食われ、クリティカルが期待値どおりに出ない — 紙が知らない摩擦が割り込んできます。この27%の隙間こそ、バランス担当プランナーの飯のタネです。紙を信じれば、リリース後に泣くことになります。
この章は、その道具一本の物語です。2008年に作り、今まで手放さずにきたDamage Simulator。仕様と算出が食い違う正確な地点をどう追跡したのか、そして18年後、その追跡にAIをどう組み込んだのかを、1本の実際のワークド・トランスクリプトで追いかけます。
まず、あの612対847の正体を解剖してみましょう。仕様書を書いた後輩プランナー(以下、チームメンバーA)に落ち度はありませんでした。彼はスキル表に書かれたとおりに掛け算をしただけです。
仕様上のDPS計算はこういう形をしています。1人のキャラクターが持つ3つのスキルを仮定します。
| スキル | 単発ダメージ | クールタイム | キャストタイム |
|---|---|---|---|
| 横斬り(횡베기) | 320 | 3.0s | 0.6s |
| 突き(찌르기) | 540 | 6.0s | 0.9s |
| 通常攻撃(평타) | 180 | 1.2s | 0.4s |
チームメンバーAの仕様計算は、「各スキルをクールタイムが明けるたびに漏れなく使う」という理想的な仮定の上に立っていました。横斬りは3秒ごとに320、突きは6秒ごとに540、通常攻撃が空き時間を埋めます。算数としてはきれいに847が出ます。紙の上では、キャラクターは手が何本もあるので、発動モーションが互いを邪魔しません。
シミュレーターが612を出した理由はただ一つ、手が1本だからです。0.9秒の突きを発動している間は、横斬りのクールタイムが明けても使えません。発動モーション同士が食い合うこのGCD(グローバルクールダウン)衝突が、仕様には存在しません。紙は摩擦のない真空で、シミュレーションは摩擦のある戦場です。24年この仕事を続けて学んだ最初の真実がこれです — 仕様DPSは上限値であって、実測値ではない。そしてユーザーは、上限値ではなく実測値でゲームを遊びます。
2008年の私は、この27%の隙間を手作業で埋めました。シミュレーションのログを1行ずつ目で読み、どのフレームでスキルがブロックされたのかを数えました。半日かかりました。18年経った今、私は同じログをClaudeに投げます。以下は実際に行った1セッションの全文です。要約せず、外した最初の試みまでそのまま載せます — その外し方こそ、この道具の使い方の半分だからです。
私が投げたプロンプトはこうでした。
添付したのは、私たちのDamage Simulator(
damage_sim/、Python)の単一キャラクター1,000ティック戦闘ログの一部です。仕様書上、このキャラクターのDPSは847ですが、シミュレーションの算出は612になります。27%のギャップの原因をログから探してください。ログのフォーマットは[tick] event skill=SKILL state=STATEです。推測ではなく、ログにある根拠だけを挙げてください。根拠が足りなければ、足りないと言ってください。[0.0] cast skill=찌르기 state=START [0.9] cast skill=찌르기 state=HIT dmg=540 [0.9] cast skill=횡베기 state=BLOCKED reason=GCD [1.5] cast skill=평타 state=HIT dmg=180 [1.9] cast skill=평타 state=BLOCKED reason=GCD [3.0] cast skill=횡베기 state=HIT dmg=320 [3.6] cast skill=평타 state=HIT dmg=180 ... (994 lines omitted) [997.2] cast skill=찌르기 state=HIT dmg=540
Claudeの生の出力はこうでした(手を加えていません)。
ログから確認できる事実:
state=BLOCKED reason=GCDイベントが繰り返し登場します。[0.9]では横斬りが、[1.9]では通常攻撃がGCD(グローバルクールダウン)によってブロックされています。- 突きのキャスト(0.0→0.9、0.9秒)が終わるまで、ほかのスキルは発動できません。キャストタイムがGCDを占有する構造に見えます。
- したがって、仕様上の「すべてのスキルをクールタイムごとに発動する」という仮定が崩れます。実際には発動モーション同士が衝突し、一部の発動が漏れています。
ただし、提供されたログは1,000行中7行だけです。27%という正確なギャップがGCD衝突だけで説明できるかどうかは、この7行では断定できません。BLOCKEDイベントの総発生回数と、それによって漏れたダメージの総量を集計してこそ、27%が検証できます。全ログのBLOCKED集計をいただければ、定量的に確認します。
ここで止まったことが重要です。AIは原因を指摘しましたが、27%を証明することはできず、自らその限界を口にしました。紙の上だけで考えるプランナーなら「GCDのせいですね」で会議を終わらせていた場面で、この道具は「7行では分からない」と踏みとどまりました。私はその要求が正当だと判断しました — これが人による検証です。そこで、全ログのBLOCKED集計を出して投げ直しました。
全1,000ティックの集計です。BLOCKEDは計412回、うちGCD起因が388回、クールタイム未完了起因が24回。BLOCKEDで漏れた推定ダメージは横斬り換算で約19,800。総戦闘1,000ティック(=秒)の間の仕様上の期待ダメージは847,000、シミュレーション実測の累積ダメージは612,300。この数字で27%のギャップを再検討してください。
Claudeの2回目の出力です。
集計で検証します。
- 仕様上の期待累積:847,000
- シミュレーション実測累積:612,300
- ギャップ:234,700(27.7%)
このギャップの構成: - GCD衝突による直接の漏れ(388回):推定約19,800は横斬り換算ですが、漏れたスロットにより高いダメージのスキルが入り得た機会費用まで合算する必要があります。漏れたスロットを平均ダメージで換算すると、漏れ分は19,800より大きくなります。 - ただし、234,700のすべてがBLOCKEDだけで説明されるわけではありません。仕様の847が「クリティカル期待値を平均としてすでに織り込んだ」値なのか、「クリティカルを含まない基本値」なのかによって、残りのギャップの解釈が変わります。仕様847のクリティカルの仮定を教えていただければ、残余ギャップを分解します。
また止まりました。そして、また正確な場所で止まりました。仕様の847がクリティカルをどう扱ったのか — これはログにない情報で、チームメンバーAの頭の中にしかありませんでした。AIは、自分が持つデータの境界を知っていました。私が確認したところ、仕様の847はクリティカル率20%・1.5倍を期待値として平準化して織り込んだ値で、シミュレーションはseed固定のうえで、実際のクリティカル発生回数が期待値より低く出た1回分でした。つまり27%のギャップは、GCD衝突(構造的・再現される)とクリティカルの分散(統計的・この1回の運)が混ざった値だったのです。
この分解が結論です。GCD衝突分は設計で直すべき本物の問題で、クリティカルの分散分はシードを変えて1,000回の平均を取れば消えるノイズです。二つを混ぜて「キャラクターが弱い」と上方修正をかければ、1,000回平均ではまともだったキャラクターが強くなりすぎます。紙も知らず、1回のシミュレーションも知らず、AIひとりでも分からなかったこの区分を作ったのは、ログの集計と仕様の隠れた仮定を突きつけた、人による検証でした。
先ほどのセッションが覗き込んだ、あの道具の入出力を1セットで広げてみましょう。シミュレーターは、結局のところ正直な関数です。同じ入力には同じ出力。入力は三つの流れから集まります。
flowchart LR
A["マスターデータ
(スキル表・キャラクターステータス)
read-only"] --> SIM
B["シナリオyaml
(戦闘条件・持続時間・
ターゲット構成)"] --> SIM
C["seed=42
(乱数固定)"] --> SIM
SIM["Damage Simulator
domain/formulas.py
run_combat() × 1000ティック"]
SIM --> R1["実測累積ダメージ
612,300"]
SIM --> R2["BLOCKEDログ
412回(GCD 388)"]
SIM --> R3["クリティカル発生
実測17.2% vs 期待20%"]
R1 --> REP["markdownレポート
仕様847 vs シミュ612
ギャップ分解:構造19% + 分散8%"]
R2 --> REP
R3 --> REP
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class SIM code;
class A,B,C,R1,R2,R3,REP data;
この図の核心は、矢印が一方向だということです。マスターデータはシミュレーターに読まれるだけです。シミュレーターはデータを決して書き換えません。18年間で最も多くの事故を防いだルールが、この一本の矢印の向きでした。シミュレーターが自分の中にデータをコピーして持ち始めると、ゲームデータが変わった翌日、シミュレーターは昨日の世界をシミュレーションします。そうして出てきたレポートで会議をすれば、会議全体が昨日の世界をめぐって争うことになります。
具体的な入力1セット(シナリオyaml)はこういう形です。
# scenarios/single_dps_check.yaml
scenario: single_target_dps
duration_ticks: 1000 # 1ティック = 0.1s 仮定、100秒戦闘
seed: 42 # 決定論 — 同じ入力に同じ出力
actor:
char_id: K_004 # ゲームデータシートから read
skill_rotation: optimal # GCD衝突時は最高期待ダメージ優先
target:
defense: 1200
hp: infinite # DPS測定用の無限体力ダミー
report:
compare_to_spec: 847 # 仕様DPSを入れてギャップを自動分解
そして、出力1セット(レポート抜粋)はこうです。
# Damage Simulator Report — K_004 single DPS
入力: scenarios/single_dps_check.yaml | seed=42 | data rev. 2026-06-05
## 仕様対比
- 仕様DPS: 847 (クリティカル20%·1.5x 期待値平坦化込み)
- シミュ実測DPS: 612 (このシードの1回分)
- ギャップ: -27.7%
## ギャップ分解
- 構造的(GCD衝突、再現される): -19.2% ← 設計検討の対象
- 統計的(クリティカル分散、この回): -8.5% ← 1000回平均で消える見込み
## 再現検証
- seed=42 再実行3回 → 612,300 / 612,300 / 612,300 (一致)
- seed 0~999 1000回平均DPS → 731 (クリティカル分散の消去後)
最後の行を見てください。seed 0〜999で1,000回回した平均は731でした。仕様の847と1,000回平均731のギャップ116(13.7%)こそが、GCD衝突という本物の構造問題の大きさです。1回分の612ではなく、この731が設計会議の入力にならなければいけません。紙の847でも、運の悪い1回の612でもなく、1,000回が合意した731。この数字を手にするまでが、バランス担当プランナーの仕事です。
この道具は18年生きてきましたが、同じコードで生きてきたわけではありません。ハンガーはそのままに、服だけを5回着せ替えました。ハンガーとは、先ほどのレポートの論理 — 仕様と実測を分けて見て、ギャップを構造と分散に分解し、再現で検証する手順です。この手順は、2008年のExcel VBA(Excelのマクロ言語)でも2026年のPythonでも、文字どおり同じです。
| 時期 | 服(技術) | ハンガー(変わらない手順) |
|---|---|---|
| 2008〜2011 | Excel VBA、1:1 | 仕様とのギャップ分解 |
| 2012〜2016 | C#コンソール、N:N | 〃 |
| 2017〜2020 | Python + Web | 〃 |
| 2021〜2024 | Python + ML | 〃(+ ユーザー分布の反映) |
| 2025〜 | Python + LLM補助 | 〃(+ ログ照会・仮説生成) |
5回の着せ替えに耐えた秘訣は、フォルダー構造に刻まれています。
damage_sim/
├── domain/ # ハンガー — 18年そのまま
│ ├── formulas.py # ダメージ公式·GCD衝突判定
│ └── metrics.py # ギャップ分解ロジック
├── adapters/ # ゲームデータ read-only
│ └── excel_reader.py
├── runners/ # 服 — 技術が変わるたびに交換
│ └── cli_runner.py
└── reporters/ # 服 — レポート出力形式
└── markdown_report.py
技術が変わったら、runners/とreporters/だけを書き直します。domain/のギャップ分解ロジックは、18年分の資産がそのまま生き残ります。2008年にExcelのセルで組んだGCD衝突の判定式が、関数シグネチャだけ変わって、今もformulas.pyの中で回っています。道具を一つの技術に釘付けにすれば、その技術と一緒に老いて死ぬ — それを私は、死んだ道具を何本も見送りながら学びました。
2025年に取り付けたLLMは、新しいハンガーではなく新しい手です。先ほどのセッションで見たとおり、AIはログを読んで仮説を立てる手であり、半日かかっていたログ追跡が数分に縮みました。しかし、ハンガーには触れさせません — ギャップが27%なのか、クリティカルが何パーセント出たのかを決定するのは、今もseed固定の決定論コアです。その場所にLLMが入った瞬間、リグレッション検証が不可能になり、道具は死にます。
バランスツールに一本だけ線を引くとしたら、私は決定論コアの境界に引きます。内側は同じ入力に同じ出力が鋼のように保証されなければならず、外側は人やAIが自由に仮説を投げて構いません。
内側(決定論 — AI禁止):
- ダメージ公式、GCD衝突判定、クリティカル発生、累積集計。
- seed=42で3回回して、612,300が3回同じに出なければなりません。これが崩れると、昨日のレポートと今日のレポートを比較できません。
外側(仮説・解釈 — AI歓迎): - 「なぜこのキャラクター構成の勝率が異常なのか」のような原因の問いかけ。 - ログからBLOCKEDパターンを探すこと、自然言語レポートのドラフト、シナリオyamlのドラフト。
先ほどのワークド・トランスクリプトは、正確にこの線の上で動いていました。AIは外側で「GCD衝突が原因」という仮説を素早く立てました。しかし、27%という数字、612という数字は最後まで決定論コアが計算した値であり、AIはその値を受け取って解釈だけをしました。そして二度も「このデータでは断定できない」と止まりました — 決定論コアには出せない情報(仕様のクリティカルの仮定)を要求しながら。この立ち止まりが、よい道具の証です。仮説を診断と取り違えないこと。
数値について、一つ正直に明かしておきます。この章の847・612・731・412回のような具体的な数字は、説明のために構成した例示値です。ただし、仕様DPSがシミュレーション実測より常に高く出るという方向、そのギャップが構造的衝突と統計的分散に分解されるという構造、seed固定がリグレッション検証の前提だという原則は、2008年から18年間、実際に運用しながら繰り返し確認してきたものです。比率の大きさはプロジェクトごとに違いますが、方向と構造は変わりませんでした。
setup. ゲームデータからキャラクターを1体選び、スキル表(ダメージ・クールタイム・キャストタイム)と仕様DPSを確保しましょう。シミュレーターがなければ、1,000ティックの単一ターゲット戦闘を回す最小スクリプトを書いてみましょう。核心は、seedを引数で受け取って固定できること、この一点です。
prompt. シミュレーションログ(BLOCKEDイベントを含む)と仕様DPSを一緒に投げましょう。
添付はキャラクター1体の1,000ティック戦闘ログとBLOCKED集計です。仕様DPSは[N]ですが、シミュレーションの算出は[M]です。ギャップの原因をログの根拠だけで分解してください。構造的原因(再現される衝突)と統計的原因(この回の分散)を区別してください。根拠が足りなければ足りないと言い、何がさらに必要かを指摘してください。
verify. AIが指摘した構造的原因を、シードを変えて1,000回平均で検証しましょう。平均でもギャップが残れば本物の構造問題、消えれば分散ノイズです。AIが「断定できない」と止まったら、それは失敗ではなく正常です — 止まった場所に人が入り、仕様の隠れた仮定を埋めます。
シミュレーターもMLもない一人開発者なら、Excel1枚とAIだけで同じ手順を回せます。スキル表をシートに書き、RAND()でクリティカルを振る1,000行のシミュレーションを1列に作りましょう。シード固定ができないので、F9で100回再計算して平均を目で確かめます。その平均と仕様DPSのギャップをAIに「構造原因と分散原因に分けてほしい」と投げましょう。道具は小さくても、ハンガー — 仕様とのギャップ分解、構造と分散の分離、再現検証 — は同じように立ちます。
金曜日の午後4時、アルファビルドの5:5 PvP自動シミュレーション1,200戦が終わりました。結果のJSONは4メガバイト。その中のどこかに「チームA勝率92%」という1行が記録されているのですが、平均勝率は52%でした。私はその1行を探すのに40分を費やし、なぜそうなったのかは結局わからないまま退社しました。
バランスは決定論の領域です。同じ入力に同じ数式を通せば、常に同じダメージが出ます。だからダメージシミュレーターはコードであるべきで、報酬カーブは人が手で引くべきです。ここはAIが足を踏み入れてはいけない場所です。ところが、その決定論コアの周辺、つまり1,200戦の結果から異常な1行を見つけ、なぜそうなったのか仮説を立て、何を変えるべきか候補を絞り、その候補を再びシミュレーションにかけるという仕事。この周辺の労働が、バランス担当者の1日の大半を食いつぶします。本章は、その周辺にAIを付ける話です。コアには手を触れないままで。
8.3で見たあの2008年製ダメージシミュレーター(エンジンと会社を3回乗り換えながらも、決定論コアはそのまま生き残ったツール)が、本章の出発点です。入力が同じなら出力も同じという性質、それがバランスツールの信頼のすべてです。同じビルドを2回回したのに勝率が違って出たら、そのツールは捨てるべきです。
そこでバランス作業の骨格を描いてみると、中央に決定論の塊があり、その入口と出口に人の手作業がぶら下がっている形になります。以下はその骨格を分解したものです。決定論の領域(青色)と、人・AIが介入する領域(オレンジ色)を色で分けてあります。
中央の青いボックス1つだけがコードです。残りの5つのオレンジのボックスはすべて人の判断・解釈・作成という労働であり、AIが入れる席はこの5か所だけです。LLMに「このキャラクターのDPSを計算して」と頼んだ瞬間、同じ入力に違う数字が返ってくる非決定論がコアへ染み込み、そのツールは18日も持たずに信頼を失います。
だから本章の背骨はシンプルです。コアを最後までコードとして守りながら、入口・出口の5か所にAIを付ける。そのうち最も手のかかる出口側、つまり1,200戦の結果から異常な1行を見つけて仮説を立てる仕事から自動化します。
冒頭のあの92%に戻りましょう。今回は人が40分さまよう代わりに、決定論の検知器がその1行を拾い出し、LLMが仮説を立て、再びシミュレーションが検証する1サイクルを、最初から最後まで追いかけます。要約はせず、ツールが実際に吐いた生の出力をそのまま載せます。
1,200戦の結果から「異常な」戦いを選び出すのはLLMではなく統計です。各指標の平均と標準偏差を求め、平均から標準偏差いくつ分離れているか(zスコア)で振り分けます。しきい値を超えれば外れ値(outlier)です。これは決定論であり、ハルシネーションが入り込む余地はありません。
def find_outliers(results, threshold=2.5):
# results: シミュレーション1試合ごとの {指標名: 値} 辞書のリスト
means, stds = compute_per_metric(results) # 指標別の平均・標準偏差
outliers = []
for r in results:
for metric, value in r.items():
if stds[metric] == 0: # 分散0 → 比較不可、スキップ
continue
z = abs(value - means[metric]) / stds[metric]
if z > threshold:
outliers.append((r["scenario_id"], metric, value, round(z, 2)))
return sorted(outliers, key=lambda x: -x[3]) # zの大きい順
実行すると次が出てきます。1,200戦のうち、しきい値2.5を超えたのはわずか3件でした。
[("pvp_5v5_S0417", "team_a_winrate", 0.92, 4.1),
("pvp_5v5_S0417", "match_duration", 41.0, 2.9),
("pvp_5v5_S0822", "team_b_winrate", 0.18, 2.6)]
最もzスコアが大きい最初の行、シナリオpvp_5v5_S0417の勝率0.92(z=4.1)が、冒頭で私が40分さまよった、まさにあの1行です。人が4メガバイトのJSONを目で追う仕事ではなく、統計が3件にまで絞り込んでくれます。ここまでがコア、ここからがAIです。
今度はその1行をLLMに渡します。ただし、「原因を診断して」ではありません。LLMはドメイン知識からあり得る原因の仮説をいくつか投げるだけで、どれが本物かは再びシミュレーションが決めます。プロンプトの全文は次のとおりです。コードブロック内は原文(韓国語)のままです。内容としては、外れ値の数値とあわせて、refgame_archer_07のスキル「표식」(マーキング=対象が受けるダメージ+12%のデバフ)、refgame_hybrid_21のスキル「추격」(追撃=デバフ対象の追跡時に移動速度+20%)、この2体が同じチームに置かれた頻度が下位5%であるという関連情報を渡し、確定診断を禁じたうえで、あり得る原因の仮説3〜5個と各仮説の検証用シミュレーション1行を求めています。
[outlier]
シナリオ: pvp_5v5_S0417 — 5:5 PvP
チームA構成: [refgame_archer_07, refgame_archer_07, refgame_archer_07,
refgame_hybrid_21, refgame_hybrid_21]
指標: team_a_winrate 0.92 (全体平均 0.52, z = 4.1)
付随指標: match_duration 41.0s (平均 28s, z = 2.9)
[関連情報]
- refgame_archer_07: 遠距離支援、スキル「표식」 — 対象に受けるダメージ +12% デバフ
- refgame_hybrid_21: 近接/遠距離混合ディーラー、スキル「추격」 — デバフ対象の追跡時に移動速度 +20%
- 2キャラクターが同じチームに配置されたことはほとんどない (組み合わせ頻度 下位5%)
要請:
このoutlierの可能な原因仮説を3〜5個提示してください。
- 各仮説に検証用の追加シミュレーション1行
- 確定診断は禁止。「〜かもしれない」レベルの仮説のみ。
回答形式: 番号付きリスト。前置き・結びなしで仮説のみ。
Claudeの生の出力をそのまま載せます(要約・整理はしません)。これも原文の韓国語のままです。
1. 「표식」デバフと「추격」の移動速度バフが乗算で累積し、
archer_07の3体が표식をばらまく → hybrid_21が途切れず追撃する
正のフィードバックループが形成された可能性がある。
検証: hybrid_21を通常の近接ディーラーに差し替えて同じシナリオを再シミュレーション。
2. 표식デバフの+12%がスタック可能(stackable)に誤って設定され、
archer_07の3体が+36%まで積み上げた可能性がある。
検証: 표식のmax_stackを1に強制して再シミュレーション。
3. 5:5で遠距離3 + 追撃型2の構成が、単純に射程の優位で
相手の進入を封鎖する構図かもしれない (シナジー無関係、組み合わせ自体が強い)。
検証: 표식のデバフ効果を0%にして同じ組み合わせを再シミュレーション。
4. match_durationが平均より長い(41s)ことから、チームAが速く勝ったのでは
なく「負けずに粘って後半逆転」する構造かもしれない。
この場合、勝率より後半スケーリング数値が原因かもしれない。
検証: 60秒時点の累積ダメージ曲線だけを別途抽出して比較。
5. (低確率) シミュレーションのシード偏向 — S0417シードでのみチームAの先攻が
繰り返された可能性。統計アーティファクトである可能性。
検証: 同じ組み合わせをシード50個で再シミュレーションして勝率分散を確認。
ここで止まるのが重要です。出力された仮説は、①「표식」デバフと「추격」の移動速度バフが掛け算で積み重なり、archer_07の3体が「표식」をばらまいてhybrid_21が途切れず追撃する正のフィードバックループが形成された可能性、②「표식」の+12%が誤ってスタック可能(stackable)に設定され、archer_07の3体で+36%まで積み上がった可能性、③遠距離3+追撃型2という編成が、シナジーとは無関係に射程の優位で相手の接近を封じる構図である可能性、④試合時間の長さ(41s)から見て、速攻勝ちではなく「負けずに粘って後半に逆転する」構造であり、後半のスケーリング数値が原因である可能性、⑤(低確率)シードの偏りによる統計的アーティファクトの可能性、の5つで、それぞれに検証用シミュレーションが1行ずつ添えられています。5番目の仮説が「これは単なるシードの偏りかもしれない」と自分への疑いを残したこと、これが良い出力です。LLMが「原因は『표식』のスタックバグです」と断定していたら、むしろその出力を疑うべきです。バランスにおけるLLMの仕事は診断ではなく、探索空間を狭めることです。
5つの仮説には、それぞれ検証用のシミュレーションが1行ずつ付いています。これを人が1つずつ回すのではなく、変更候補を束ねて並列で投げます。核心のコアであるsimulate_dpsは、次のような実行可能な形をしています。18年使い続けてきた、あの決定論関数の骨子です。
def simulate_dps(attacker, target, formula, ticks=600, seed=0):
"""1ペアの戦闘を決定論的にシミュレーション。同じ (入力, seed) なら同じ出力。"""
rng = Rng(seed) # シード固定 → 再現可能
hp = target.hp
total_damage = 0.0
for t in range(ticks): # 1 tick = 0.1秒と仮定
# 防御係数: 決定論の数式 (LLMは作らない)
def_factor = target.defense / (target.defense + formula.def_const)
raw = attacker.atk * (1 - def_factor)
# クリティカル: シード基盤 → 同じseedなら同じクリティカルのタイミング
if rng.roll() < attacker.crit_rate:
raw *= attacker.crit_mult
# デバフ(표식など)はformulaから決定論的に注入
raw *= formula.debuff_multiplier(attacker, target, t)
hp -= raw
total_damage += raw
if hp <= 0:
return {"ttk": t * 0.1, "dps": total_damage / ((t + 1) * 0.1)}
return {"ttk": None, "dps": total_damage / (ticks * 0.1)} # 時間内に倒せず
def run_candidates(base_scenario, candidates, seeds=range(50)):
"""仮説別の変更候補を50シードで並列シミュレーション。winrateの分散まで回収。"""
out = {}
for name, patch in candidates.items(): # patch = formulaの一部上書き
scen = base_scenario.with_patch(patch)
wins = [simulate_match(scen, formula=scen.formula, seed=s) for s in seeds]
out[name] = {
"winrate": mean(w["team_a_won"] for w in wins),
"winrate_std": pstdev(w["team_a_won"] for w in wins), # 仮説5の検証用
}
return out
仮説をcandidates辞書に移して一度に回します。キーは上から順に、基準(변경없음=変更なし)、仮説1(hybridを通常の近接アタッカーに入れ替え)、仮説2(「표식」のmax_stackを1に強制)、仮説3(「표식」のデバフ効果を0に)、仮説5(同じ編成でシードのみ50個)です。
candidates = {
"기준(변경없음)": {},
"가설1_hybrid교체": {"team_a[3:5]": "refgame_melee_03"},
"가설2_표식_max_stack1": {"skill.표식.max_stack": 1},
"가설3_표식_효과0": {"skill.표식.debuff": 0.0},
"가설5_시드분산확인": {}, # 同じ組み合わせ、seedsのみ50個
}
result = run_candidates(scenario_S0417, candidates, seeds=range(50))
結果は次のとおりです(実際に実行した形の出力。行末の矢印メモは順に「シードの偏りではない=仮説5棄却」「最も大きく下がった」「平均近くへ復帰」の意味です)。
기준(변경없음) winrate=0.91 std=0.04 ← シード偏向ではない(仮説5棄却)
가설1_hybrid교체 winrate=0.74 std=0.06
가설2_표식_max_stack1 winrate=0.63 std=0.05 ← 最も大きく下がった
가설3_표식_효과0 winrate=0.55 std=0.05 ← 平均近くへ復帰
読む順序がそのまま診断です。基準を50シードで回し直しても勝率0.91、分散0.04。仮説5(シードの偏り)は棄却されます。「표식」の効果を0にすると0.55と平均近くまで戻ります。原因が「표식」デバフ系統であることは間違いありません。そしてmax_stackを1に縛ったときの0.63までの下がり幅が最も大きかったので、核心は仮説2、すなわち「표식」デバフがスタックし、archer_07の3体で+36%まで積み上がっていたことです。LLMが投げた5つの候補すべてを人が検証したのではなく、3つ回しただけで決着がつきました。
ここでLLMがやったのは、「표식のスタックはバグだ」と言ったことではありません。その仮説を候補リストに載せたことだけです。採用するのは、シミュレーション結果を見たバランス担当者です。「『표식』のmax_stackを1に固定する。archer_07単独編成の勝率は0.63と依然として平均(0.52)より高いので、次のビルドで『표식』のデバフ数値を12%→9%に追加調整したうえで再測定する」。
この決定は人が下したものであり、その根拠(z=4.1の検知→5仮説→3シミュレーション→仮説2の確定)が1行で残ります。決定論コアは最後までコードのままで、LLMは40分の迷走を仮説5行に置き換えただけです。コアの中へは一歩も踏み込んでいません。
上のワークド・トランスクリプトは、実は5つの位置のうち3つ(異常検知・変更探索・異常解釈)を一度に踏んだものです。5つの位置をサイクルとして広げると、次のように回ります。
flowchart TD
A[シナリオ定義] -->|位置1: シナリオ自動生成| B[数値入力]
B -->|位置2: 変更候補の探索| C{決定論シミュレーション
simulate_dps}
C --> D[raw結果JSON]
D -->|find_outliers z-score| E[異常パターン検知]
E -->|位置4: LLM仮説3〜5| F[仮説 + 検証シミュレーション]
F -->|位置3: 自然言語レポート| G[バランス担当者レビュー]
G -->|位置5: 次の行動提案| H{採用 / 棄却}
H -->|採用| B
H -->|棄却| A
style C fill:#dbeafe,stroke:#2563eb,stroke-width:2px
style E fill:#dbeafe,stroke:#2563eb
青い2つのノード(シミュレーション、z-score検知)だけが決定論です。残りの、矢印の上のラベル(位置1・2・3・4・5)がAIの付く席です。サイクルが1周するたびに、採用された変更が再び数値入力へ入って次のシミュレーションを回します。このループを人が手で回すと1周に1日かかり、AI支援で回すと数時間です。
5つの位置を1つずつ短く押さえます。
位置1 — シナリオ自動生成。 「3:3の拠点占領戦、旗3本を1分間占領すれば勝利、リスポーン10秒」というコンセプト1行と既存のシナリオyamlを1〜2個渡すと、LLMが同じスキーマで新しいシナリオyamlを埋めます。バランス担当者は「コンセプトになかったルールを勝手に入れていないか」だけをチェックします。白紙からyamlを書いていた1〜2時間が、15分のチェックに縮みます。
位置2 — 変更候補の探索。 上のワークド・トランスクリプトのcandidates辞書がまさにこれです。「タンクの生存を+49%上げるにはどこを触るか」に対してLLMが候補を5つ投げ(base_def +50、def_constの調整など)、その候補をすべてシミュレーションにかけて、副作用が最も小さいものを選びます。候補は仮説、採用はシミュレーション。最も慎重に扱うべき位置です。間違った候補が検証時間を食いつぶすからです。
位置3 — 自然言語レポート。 シミュレーションのraw JSONから指標をスクリプトで抽出し(決定論)、その指標と変更のコンテキストだけをLLMに渡して「会議に持っていける1ページ」を書かせます。核心の変化3〜5行、影響を受けたキャラクターTOP 5、フォローアップ2〜3個。提供した指標以外の数値は書かせないよう釘を刺します。rawの整理30分が、チェック5分になります。
位置4 — 異常パターンの解釈。 上のステップ2〜3がこれです。zスコアが選んだ外れ値に、LLMが仮説を3〜5個付けます。確定診断の禁止が、この位置の生命線です。
位置5 — 次の行動提案。 分析が終わったら、「今回のビルドで即時対応/1週間モニタリング/1週間後の再検討候補」を優先順位付きのチェックリストにまとめます。バランス担当者の決定漏れを防ぐセーフティネットであって、決定そのものを代行するわけではありません。
5つの位置を一度にオンにするのが、最もよくある失敗です。効果が大きくリスクが小さい出口側からオンにします。
円の中の丸数字(①〜⑤)が導入の順序です。位置3(レポート)と位置4(異常解釈)が右上、つまりROI(Return on Investment、投資対効果)が高くリスクが低い位置なので、最初にオンにします。この2つを動かすだけで処理量が2〜3倍に増え、導入効果の70%以上がここで回収されます。位置2(変更提案)は右下の赤い位置で、間違った候補が検証時間を食いつぶしかねないため、最も慎重に、最後にオンにします。すべてのチームが5つ全部をオンにする必要もありません。位置3・4だけでも、1人のバランス担当者の1日が変わります。
導入期間の現実的な感覚はこうです(著者の推定、未検証。チーム規模・ツールの成熟度によって大きく変わります)。位置3は1〜2週間、位置4を加えて2週間、位置1を加えて1か月、位置5を加えて2週間、位置2は最後に1〜2か月。一度に全部オンにするな、という言葉の言い換えです。
著者のプロジェクトAで、5つの位置を6か月かけてオンにした後の変化は次のとおりです。絶対値は著者の推定(未検証)であり、信頼してよいのは方向と比率だけです。環境によって倍率は大きく変わります。
| 項目 | 導入前 | 導入後(方向) |
|---|---|---|
| バランス担当者1人あたりの週間シミュレーションサイクル | 5〜7件 | 25〜35件(約5倍) |
| レポート作成(1件あたり) | 30〜40分 | チェック5分 |
| シナリオ作成(1件あたり) | 1〜2時間 | チェック15分 |
| 外れ値の発見→診断 | 1〜2日 | 4〜6時間 |
| 測定結果→次の変更決定 | 2〜3日 | 1日 |
ここで重要なのは倍率ではなく、時間がどこへ移ったかです。人の時間が、rawデータの整理から意思決定へ移動しました。バランス担当者の数が減ったのではなく、1人が扱えるゲームの領域が広がったのです。処理量5倍を人員削減と読むと、導入の意味が見当違いの方向へ流れます。
コストは小さいです。プロンプトキャッシングを適用すれば、5つの位置全体での月あたりのLLMコストはおよそ75ドル前後(著者の推定)で、バランス担当者1人の人件費の100分の1を超えません。だから導入の本当の決定変数は、LLMコストではなくチェックの負担です。AIが投げた仮説とレポートを、人が読んでふるいにかける時間を確保できるか。それがオンにするかオフにするかの基準です。
最後に、18年間同じ場所で繰り返されてきた罠をいくつか、処方箋とともに残します。
run_candidatesで検証した後にだけ採用します。バランスにおけるAIの席は明確です。決定論コアの外側、人がさまよっていた5つの位置。コアは最後までコードで守り、その周辺の手作業だけを軽くする。それが、18年物のシミュレーターがAI時代にも生き残る方法です。
simulate_dpsとfind_outliersの2つの関数だけ。シード固定で再現性を確保しましょう。run_candidatesで50シード並列シミュレーション → 平均近くへ戻す候補が原因です。人が採用し、根拠を1行残しましょう。ここまで、この部の4つの章は一つの敵と戦ってきました。ボス1体を何秒で倒すか、タンクが89%生存するか、ゴールドが漏れていないか。すべて単一ターゲットに向けたダメージ・生存・収支の話でした。ところがPvPでは、敵は人間です。人間はボスのように決まったパターンでは動かず、同じクラスでも腕前が違い、何より互いの弱点を狙ってきます。PvEのバランスが深く作り込まれていてもPvPが丸ごと抜け落ちているケースが珍しくないのは、このためです。単一ターゲットのDPS曲線は8.1〜8.4で最後まで扱いましたが、「チョキがパーに勝つ」という相性の網の目は、まだ一度も描いていません。
本章はその空白を埋めます。扱うのは三つ — クラス・組み合わせ間の相性を収める勝率マトリックス、誰を誰と当てるかを決めるマッチメイキング/MMR、そしてそれらすべての数値を嘘に変えかねないサーバー権威・アンチチートです。そして、この部全体を貫いてきた境界線はここでもそのままです。戦闘の計算式は決定論、マッチングと相性の検知はAI補助。一歩たりともぶれません。
PvEにおけるキャラクターの強さは絶対値です。剣士のDPSが800なら800であり、ボスはその800をそのまま受けます。ところがPvPでは、強さは相対的です。剣士の800は弓使いには十分でも、被ダメージを30%減らす盾兵が相手では560まで削られ、足りなくなることがあります。同じキャラクターの強さが、相手が誰かによって変わるのです。この一点が、PvPバランスをPvEとは根本的に異なる問題にします。
だからPvPバランスの単位は、1キャラクターの数値ではなくペアの関係です。「剣士 vs 弓使い」の勝率、「剣士 vs 盾兵」の勝率がそれぞれ別に存在し、これらの関係をすべて集めると1枚の表になります。横軸にも縦軸にも同じクラスの一覧が並び、セルごとに「行が列に勝つ確率」が書き込まれます。これが勝率マトリックスです。PvEにDPS曲線があるなら、PvPにはこのマトリックスがあります。
右側の表の読み方は単純です。「剣士 vs 盾兵」のセルが0.42なら、剣士が盾兵に勝つ確率は42%、つまり盾兵が有利な相性です。すべてのセルが0.50に近ければ完璧な均衡ですが、そんなゲームは面白くありません。じゃんけんのように循環する相性があってこそ、クラス選択に意味が生まれます。問題は、その循環がどこかで途切れ、一つのクラスが全員に勝つセルが生まれるときです。午前2時のタンクがPvEの事故だったとすれば、「盾兵 vs 全クラスで勝率60%超」はPvPの事故です。
ここであらかじめ釘を刺しておきます。これらのセルを埋める数字(0.58、0.42など)はすべて例示であり、実測ではありません。ゲームごとにクラス数もスキルも、目標とする均衡ラインも異なります。本章で信頼すべきは数字ではなく、マトリックスをどう埋め、どう点検し、その点検のどこにAIが付くのかという構造です。
勝率マトリックスのセル一つを埋める作業は、8.4で見たあの決定論的シミュレーションと正確に同じ道具です。「剣士 vs 弓使い」を1,000戦自動でシミュレーションし、剣士が何戦勝ったかを数えれば、それがそのセルの勝率です。クラスがN個ならセルはN×N個、各セルを1,000戦ずつ回せば表が1枚埋まります。このシミュレーションは最後までコードです — 同じシードを与えれば、同じマトリックスが一字一句違わず再現されなければなりません。そうであって初めて、「今回のビルドで盾兵が強くなった」という言葉が嘘になりません。
ここにPvPならではの罠が一つあります。PvEのシミュレーションでは敵(ボス)は固定パターンですが、PvPのシミュレーションでは相手も行動を選ばなければなりません。剣士がどう戦うかを決めるボット(bot policy)が、両側に必要です。そしてこのボットが間抜けだと、マトリックス全体が嘘になります — 操作が壊滅的なボット同士を当てると「スキルをいつでも適当に使うクラス」が勝つマトリックスが出てきますが、実際の熟練ユーザーの手にかかれば正反対になり得ます。だからPvPマトリックスには常に「このボットがどの水準のプレイを模倣しているか」というただし書きが付かなければなりません。ボットは「クールタイム(クールダウン)が戻ったら使う」「HP 30%未満なら後退する」といったヒューリスティックで組むのが普通で、このヒューリスティック自体は決定論です。
ボットポリシーの骨子を実行可能な形に写すとこうなります — 入力が同じなら同じ行動を選ぶ、ハルシネーションが入り込む余地のない関数です。
def bot_decide(me, enemy, cooldowns, t):
"""決定論ボットポリシー。同じ(状態)なら同じ行動。LLMが作るのではない。"""
# 1) 生存優先: HP 30%未満なら回避/後退
if me.hp_ratio < 0.30 and cooldowns["escape"] <= 0:
return Action("escape")
# 2) 相性スキル: 敵がデバフ免疫でなければマーク優先
if cooldowns["mark"] <= 0 and not enemy.has("debuff_immune"):
return Action("mark", target=enemy)
# 3) 射程管理: 近接の敵が張り付いたら距離を取る (遠距離クラス)
if me.is_ranged and dist(me, enemy) < me.kite_range:
return Action("reposition")
# 4) その他: クールダウンが戻った最大ダメージスキル
return best_ready_damage_skill(me, cooldowns)
def simulate_pvp_match(class_a, class_b, formula, seed=0):
"""1:1の1戦を決定論的にシミュレーション。ダメージは8.1の式をそのまま使用。"""
rng = Rng(seed)
a, b = spawn(class_a), spawn(class_b)
for t in range(MAX_TICKS):
for me, foe in ((a, b), (b, a)):
act = bot_decide(me, foe, me.cooldowns, t)
apply_action(act, me, foe, formula, rng) # formula = 決定論ダメージ式
if a.hp <= 0 or b.hp <= 0:
break
return {"winner": "a" if b.hp <= 0 else "b" if a.hp <= 0 else "draw",
"duration": t * TICK}
マトリックス1枚を丸ごと埋めるのは、この関数をセルごとに1,000回回す外側のループです。
def build_winrate_matrix(classes, formula, n=1000):
matrix = {}
for ca in classes:
for cb in classes:
if ca == cb:
continue
wins = sum(
simulate_pvp_match(ca, cb, formula, seed=s)["winner"] == "a"
for s in range(n)
)
matrix[(ca, cb)] = wins / n # caがcbに勝った割合
return matrix
ここまでがコアで、最後までコードです。AIはこの表を作る側ではなく読む側に付きます。Nが8ならセルは56個。人間が56個の勝率を目で追いながら「どこが壊れているか」を探すのは、午前2時の4メガJSONと同じ労働です。おかしいセルを選び出すのは、8.4のzスコア検知がそのまま担います。
def find_broken_cells(matrix, low=0.40, high=0.60):
"""均衡線(0.5)の外へ大きく外れたセルを決定論的に絞り込む。"""
broken = []
for (ca, cb), wr in matrix.items():
if wr > high or wr < low:
broken.append((ca, cb, round(wr, 2)))
return sorted(broken, key=lambda x: abs(x[2] - 0.5), reverse=True)
検知がセルを絞り込んだら、そのセルをLLMに渡します。ただし、8.4と同じ規律です — 確定診断は禁止、仮説と検証シミュレーションのみ。たとえば「盾兵 vs 魔法使い 0.68(zが最大)」の1行を与えて、こう要請します。
[壊れたセル]
盾兵 → 魔法使い 勝率 0.68 (均衡線 0.50、マトリックス内で z 最大)
付帯: このマッチの平均持続時間 38s (全体平均 22s)
[関連情報]
- 盾兵: 受けるダメージ -30% パッシブ "鉄壁"、沈黙スキル "盾強打"(2秒)
- 魔法使い: 全ダメージの70%が詠唱1.5秒のスキルに集中
- 両クラスのマッチ頻度は実測キューで上位 (人気の組み合わせ)
要請: この相性崩壊の可能性のある原因仮説3~5個 + 各検証シミュレーション1行。
確定診断は禁止。"〜かもしれない" の水準のみ。
LLMは「『鉄壁』の-30%と沈黙2秒が重なり、魔法使いが核心の詠唱スキルを一度も入れられないまま死ぬ、正のフィードバックかもしれない/検証:沈黙の持続を1秒に減らして同じセルを再シミュレーション」のように、探索空間を絞り込む仮説を投げるだけです。どれが本物かは、再びbuild_winrate_matrixを候補ごとに回して決めます。マッチ継続時間が平均の1.7倍だという手がかりまでLLMが仮説に織り込んでくれること — 人間が56セルを目で追っていては見落としやすいその結び付きこそ、この場面でAIが稼ぐ時間です。
勝率マトリックスを完璧に合わせても、ユーザーが「負けた」と感じる本当の原因は別にあります。誰と当たるかです。実力1500のユーザーが2200のユーザーに当たれば、クラス相性が5:5でも結果は決まっています。だからマッチメイキングは単なるサーバー機能ではなく、バランスの一部です。マトリックスがクラス間の公平性を受け持つなら、マッチメイキングは実力間の公平性を受け持ちます。
ほとんどの対戦ゲームはMMR(Matchmaking Rating、マッチメイキングレート)を持っています。勝てば上がり、負ければ下がる隠しレートで、近いレート同士を当てます。レートの更新は決定論的な計算式です — Elo(イロレーティング)が最も広く使われており、公開された標準であるため、本書で引用してよい数少ない数式の一つです。
# Elo: 公開標準の更新式 (でっち上げた値ではない)
expected_a = 1 / (1 + 10 ** ((rating_b - rating_a) / 400))
new_rating_a = rating_a + K * (score_a - expected_a)
# score_a: 勝てば1、負ければ0
# K: 更新強度の定数 (ゲームが決める値。通常16~40の範囲で選択)
# 400, 10: Eloの定義に埋め込まれた定数
この式自体は決定論であり、AIが入る場所ではありません。ところがマッチメイキングには、決定論的な計算式だけでは解けない緊張が一つあります。公平性 ↔ 待ち時間のトレードオフです。レートが正確に同じ相手だけを当てればマッチは公平ですが、そんな相手がキューにいなければユーザーは10分待つことになります。レート差を寛大に許容すれば早くマッチしますが、マッチは不公平になります。深夜帯、不人気クラス、高レート帯ほど、この緊張は激しくなります。
flowchart LR
A["マッチング要求
ユーザーMMR 1500"] --> B{"キューに±50の相手はいるか?"}
B -->|あり| C["即時マッチング
最も公平"]
B -->|なし| D["待ち時間に応じて
許容幅を拡大 ±50→±200"]
D --> E{"マッチング成立?"}
E -->|成立| F["マッチング
公平性 ↕ 待ち ↕ 均衡"]
E -->|タイムアウト| G["ボット投入 / キュー維持
ポリシー決定"]
C --> H["Elo更新(決定論)"]
F --> H
style H fill:#dbeafe,stroke:#2563eb,stroke-width:2px
style D fill:#ffedd5,stroke:#ea580c
style G fill:#ffedd5,stroke:#ea580c
青いノード(Elo更新)だけが決定論です。オレンジのノード — 許容幅をいつ、どれだけ広げるか、タイムアウト時に何をするか — が、AI補助の届く場所です。ただし、ここでもAIがリアルタイムのマッチング決定を下すわけではありません。それは高速かつ再現可能であるべきサーバーロジックなので、ルールベースのコードの場所です。AIが付くのは、そのルールをチューニングするための分析です。「先週のマッチングログで、どのレート帯・時間帯・クラスのマッチ品質(勝率の偏り・待ち時間)が悪かったか」を要約し、「許容幅の曲線をどう変えれば、どの帯域の待ち時間が減るか」の候補を提案する仕事です。8.4の位置3(レポート)・位置4(異常の解釈)・位置2(変更候補の探索)が、マッチングログへと舞台だけを移したものです。
マッチメイキングが勝率マトリックスと絡む地点も押さえておきます。マッチングアルゴリズムがクラスを考慮せずレートだけを合わせると、壊れた相性のセルがそのまま露出します。盾兵が魔法使いに68%勝つセルが生きたまま、マッチングが両者を頻繁に当てれば、魔法使いユーザーの体感の敗北はマトリックスの数値以上に積み上がります。だからマトリックスの点検とマッチングログの分析は別々に回るものではなく、同じサイクルの入口と出口です — マトリックスで壊れたセルを直し、マッチングログでそのセルが実際にどれだけ当たったかを確認します。
ここまでのすべての話 — マトリックス、MMR、シミュレーション — は、一つのことを暗黙の前提にしていました。ユーザーが報告してくる結果は真実だ、ということです。 PvEではこれはほとんど問題になりません。一人でボスを倒すのに、誰を騙すというのでしょうか。ところがPvPには相手がいて、勝てばレートが上がり、だからこそ騙す動機が生まれます。ダメージを改ざんし、位置を改ざんし、クールタイムを無視するクライアントが現れた瞬間、8.1の決定論的な計算式は紙の上だけの決定論になります。実際のサーバーでは、誰かの剣士が計算式より2倍強いダメージを叩き込んでいるのです。
だから対戦ゲームの第一のバランス規則は、マトリックスより先に来ます。結果をクライアントに決めさせないこと。 ダメージ計算、クールタイム判定、命中判定 — バランスに触れるすべての演算は、サーバーが権威を持ちます。クライアントは入力(どこへ移動するか、どのスキルを使うか)だけを送り、その入力が計算式に合っているか、クールタイムが戻っているか、射程内かは、すべてサーバーが検証し直します。クライアントが送ってきた「ダメージ999」はサーバーが無視し、サーバーが計算式で算出した値だけを適用します。
サーバー権威が崩れれば、バランス作業の全体が嘘になります。勝率マトリックスをどれほど精緻に合わせても、ライブで一つのクラスがダメージを改ざんしていれば、そのマトリックスは紙の上の約束にすぎません。だからアンチチートは別個のセキュリティ業務ではなく、バランスデータの信頼性の問題です。ライブの勝率がシミュレーションのマトリックスと大きく食い違うとき、最初に疑うべきは「計算式が間違っているのか」ではなく「このデータはクリーンか」でなければなりません。
ここでAIの場所が再び明確になります。チート判定そのもの — 「この入力を無効にする」 — は決定論的ルールの仕事です。0.1秒で30メートル移動した入力は物理的に不可能なので、ルールで弾きます。同じ入力には同じ判定が出なければならず、不当なアカウント停止(誤BAN)を生んではならないので、ここに確率的なLLMを置くことはできません。一方で、異常パターンを候補として絞り込む仕事にはAI補助が届きます。サーバーログから「このアカウントの命中率分布は人間の分布からzいくつ分外れている」「このアカウント群は同一の異常パターンを共有している」といった候補を集め、人によるレビューに上げます。8.1の表をPvPに移すと、境界はこうなります。
| 領域 | AI | 理由 |
|---|---|---|
| サーバーのダメージ・命中・クールタイム判定 | 絶対禁止 | 決定論コア。同じ入力=同じ判定が崩れれば公平性が崩壊 |
| Elo/MMRレート更新 | 絶対禁止 | 公開標準の決定論式。揺らげば順位が嘘になる |
| チート遮断(BAN)の判定そのもの | 絶対禁止 | 誤BANは許されない。同じ証拠=同じ判定 |
| 勝率マトリックスのシミュレーション | 絶対禁止 | 再現不能なら「このクラスが強くなった」が嘘になる |
| 壊れた相性セルの検知・解釈 | 可能 | zスコアでセルを絞り、LLMが仮説(確定診断は禁止) |
| マッチングログの品質分析・チューニング候補 | 可能 | 待ち/公平のトレードオフの変更候補を提案(シミュレーションで検証) |
| チート疑いパターンの候補抽出 | 可能 | 人によるレビューに上げる候補のみ。BANの決定は人間・ルール |
線引きは8.1と一字一句違いません。AIは決定論コアの外側にだけ棲みます。 執行する内側 — ダメージ、レート、BAN — はルールブックであり、検知し、解釈し、候補を押し出す外側がAIの場所です。
三つのテーマ — マトリックス、マッチング、サーバー権威 — は、別々に回る三つの仕事ではありません。一つの対戦バランスサイクルの三つの区間です。サーバー権威がクリーンなデータを保証し、そのデータでマトリックスを点検し、マッチングログで点検結果が実際のキューでどう体感されるかを確認し、再びシミュレーションで候補を検証してビルドに反映します。
flowchart TD
A["サーバー権威 + アンチチート
クリーンなライブデータ"] --> B["ライブ勝率マトリックス
(実測)"]
B --> C{"シミュレーションのマトリックスと
大きく食い違うか?"}
C -->|"食い違う → データを疑う"| A
C -->|"一致 → バランスの問題"| D["壊れたセルの検知(zスコア)"]
D -->|"LLM仮説3〜5"| E["変更候補 + 検証シミュレーション"]
E --> F["build_winrate_matrix
候補ごとに再シミュレーション(決定論)"]
F --> G["バランス担当が採用/棄却"]
G --> H["ビルドに反映(不可逆)"]
H --> I["マッチングログ分析
体感の検証 + チューニング候補"]
I --> A
style A fill:#dbeafe,stroke:#2563eb,stroke-width:2px
style F fill:#dbeafe,stroke:#2563eb,stroke-width:2px
style D fill:#ffedd5,stroke:#ea580c
style E fill:#ffedd5,stroke:#ea580c
style I fill:#ffedd5,stroke:#ea580c
青いノード(サーバー権威、シミュレーション再計算)が決定論、オレンジのノード(検知・仮説・マッチング分析)がAI補助です。このサイクルで最もよくある失敗は、C分岐を飛ばすことです。ライブのマトリックスがシミュレーションと食い違ったとき、すぐに計算式から手を付けると、実はチートで汚染されたデータを追いかけて、何の問題もないクラスを下方修正することになります。データのクリーンさを先に疑うこの一つの分岐が、8.1の「変更履歴」と同じ役割をPvPで果たします — 抜かせば、あの午前2時が戻ってきます。
最後に、PvPバランスにまつわる18年分の罠をいくつか、処方箋とともに残しておきます。
PvPにおけるAIの場所はPvEと同じです。決定論コア — ダメージ、レート、BAN、シミュレーション — の外側にある検知・解釈・候補です。コアはコードとサーバー権威で守り、人間が56セルを目で追い、マッチングログをさまよっていた手作業だけを取り除きます — この部全体が一つの骨格で動いているという最後の証拠が、本章です。
setup. 8.4のsimulate_dpsを1:1に拡張したsimulate_pvp_matchと、両側のボットを動かすbot_decide(ヒューリスティックな決定論)を作ってください。シードを固定し、同じマトリックスが再現されるかどうかからまず確認します。ダメージは必ず8.1の計算式をそのまま持ってきて使い、ボットが模倣するプレイ水準を1行で記録してください。
prompt. find_broken_cellsで均衡ライン(0.40〜0.60)の外のセルを絞り込んだあと、zが最大の1セルだけをLLMに渡します。
添付した壊れたセル(盾兵 vs 魔法使い 0.68、マッチ持続時間 38s/平均 22s)について
可能性のある原因仮説3~5個と、各検証用の再シミュレーション1行を提示してほしい。
関連スキル・パッシブ情報は下に添付。確定診断は禁止 — "〜かもしれない"のみ。
数値を直接修正せず、候補だけを提案すること。
verify. AIの仮説をそのまま信じないでください。各仮説の変更候補をbuild_winrate_matrixに入れて同じシードで再シミュレーションし、そのセルが0.50付近に戻りながらほかのセルを壊していないかを併せて確認します(PvPの変更は、1セルを直そうとして隣のセルを台無しにしがちです)。二つの条件を満たす候補だけを採用し、8.1と同じように決定ログに理由・棄却した候補・予測値を残してください。ビルド反映の1週間後、ライブの実測勝率をそのログに書き添えます。
クラスが二つだけでサーバーもない一人プロトタイプでも、骨格は同じです。マトリックスは2×2で十分で、シミュレーションは8.1の30行ループにボットポリシー1行(クールタイムが戻ったら最大ダメージのスキル)を足すだけで済みます。サーバー権威は「結果をクライアントに決めさせない」という原則だけをコード構造で守ればよく、本格的なアンチチートはユーザーが付く前には必要ありません。MMRも最初は省略し、マトリックスが一方に60%超傾いていないかだけを、1,000戦回して確認してください。AIはその結果を読み、「どのマッチが壊れていて、その原因として何があり得るか」を要約することにだけ使います。規模にかかわらず守るべき線はただ一つ — ダメージと勝敗はコードとサーバーが決め、LLMには絶対に任せないことです。
主な読者:HUD・UIを担当するUXプランナー(中規模(10〜50人)チーム) 1人/趣味の読者向け縮小バージョン:§9.1.8「一人ならこれだけ」
QAビルドで新しいデバフ通知をHUDに載せた日、デザイナーは「よく見える」と言いました。翌日、ユーザー掲示板には「デバフが見えなくて死んだ」という書き込みが上がりました。通知は画面中央に、グレーの背景に淡い黄色の文字で表示されていました。デザイナーのモニターでは見えていて、戦闘中に爆発エフェクトが画面を覆う6インチのスマートフォンでは見えませんでした。問題は、これが初めてではないという点でした。ビルドごとに、画面ごとに、同じ種類の事故が「今回は大丈夫だろう」で繰り返されていました。
本章では、その繰り返しを断ち切る一つの作業に集中します。完成したHUDスクリーンショット1枚を入力として受け取り、P0要素が視線の届く領域(上部ステータスバンド・左右下のアクションコーナー)を外れていないか、文字のコントラストが可読のしきい値を超えているかを自動で検出するlintゲートです。優先度表・視線の流れ・プラットフォーム分岐といったHUD設計の一般原則はすでに他の書籍に十分書かれているので、本章はその原則をビルドごとに自動で強制するレビューループにだけ紙面を使います。核心は、AIが画面を見て「この文字はコントラスト2.0:1なのでWCAGの4.5:1に未達」と、座標と数字で言うようにさせることです。「よく見えますけど」という水掛け論を、コードと標準に置き換えます。
HUDのレビューが毎回人によって違う結論になるのは、基準が「見える/見えない」という主観だからです。幸い、可読性・アクセシビリティの多くは、すでに標準化団体が数字で確定してくれています。でっち上げる必要はありません。
| レビュー項目 | 標準基準(出典) | 自動判定 |
|---|---|---|
| 通常テキストのコントラスト比 | 4.5:1以上(WCAG 2.1 SC 1.4.3) | 可能 — 前景・背景の色値から計算 |
| 大きいテキスト(18pt+)のコントラスト | 3:1以上(WCAG 2.1 SC 1.4.3) | 可能 |
| 非テキスト(アイコン・ゲージ)のコントラスト | 3:1以上(WCAG 2.1 SC 1.4.11) | 可能 |
| タッチターゲットの最小サイズ | 44×44 pt(Apple HIG)/ 48×48 dp(Material) | 可能 — 要素サイズで |
| 親指の到達領域 | 横持ち両手操作時に左下・右下コーナーが「容易」(左親指=移動、右親指=スキル)。業界で通用するthumb-zoneモデル | 部分 — 領域ルールで |
最後の行(親指の到達領域)だけは定量的な合格ラインではなく業界で通用するモデルで、上の4行はW3C・Apple・Googleが公開した合格ラインです。コントラスト比はとくに明確です。WCAGは2色の相対輝度を(L1+0.05)/(L2+0.05)で計算するよう、公式まで公開しています。グレー(#888)の背景に淡い黄色(#D4C84A)の文字のコントラストは、この公式に入れると約2.0:1になります — 4.5:1未達、つまり標準上は明白な不合格です。「デザイナーのモニターでは見えた」という反論が通らない場所です。
ここで一つはっきりさせておきます。MMORPG・RPGのモバイル画面は横持ち(landscape)が標準です。理由は情報量と操作です。同じインチ数でも横に持てば、1画面に収まる常時情報が縦より多く、両手の親指で左(移動)・右(スキル)を同時に操作できます。縦持ち片手グリップはカジュアルパズル・放置系には合いますが、同時情報が多く両手操作が必要なMMORPGには合いません。そのため本章のすべての視線・配置判定は、横持ち両手グリップを前提とします。画面は、上部の横長ステータスバンド、左右二つの下部アクションコーナー、その間の中央ゲーム領域、そしてゲーム領域の下の中央下部スロットバンド(消費・自動アイテム・クイックスロット)に分かれます。
この5行が、本章でAIに渡すレビュー用ルールブックです。「デバフがちょっと見えにくい気がする」ではなく「デバフのテキストはコントラスト2.0:1でSC 1.4.3違反」と言えるようになってはじめて、人がレビューしてもAIがレビューしても同じ判定になります。
プラットフォーム基準をPCと並べて置くと、レビューの出発点がはっきりします。プロジェクトAはモバイル優先+PC補助なので、両方の基準をルールブックに入れます。
| 基準 | PC(補助プラットフォーム) | モバイル(優先プラットフォーム、横持ち) |
|---|---|---|
| 画面・入力 | 27インチ+ / マウスの1px精度・ホバー・ショートカット | 6.xインチ横持ち / 両手親指、ホバーなし |
| 同時常時情報 | 30〜50種に対応可能 | 12〜16種が限界(著者の推定、未検証) |
| 視線・操作の到達 | 画面全域(カーソルがどこへでも届く) | 上部ステータスバンド+左下・右下コーナー+中央下部スロットバンドのみ「容易」 |
| 精度 | 1pxクリック | 最小44ptタッチターゲット(HIG) |
| 主なレビューリスク | 情報過密による認知負荷 | 狭い画面+指による遮蔽+中央埋もれ |
PCはマウスの精度・ホバーのツールチップ・大きな画面のおかげで、情報を多く表示しても視線と操作が届きます。モバイルは横持ちなので縦よりはましですが、PCほどは載せられず、押す要素が両側の親指コーナーに縛られ、ホバーがないためP0情報は常時表示でなければなりません。そのためモバイルHUDレビューの本質は「きれいか」ではなく、「P0が視線の届く場所(上部・両コーナー)にあり、文字が標準のコントラストを超えているか」です。その判定が人によってぶれないように標準で固定するのが、本章の仕事です。
実際にどう回すのか、1サイクルを最後までお見せします。以下は著者のプロジェクト(モバイル優先MMORPG、以下「プロジェクトA」)の戦闘HUDレビューセッションを忠実に再現したものです。入力プロンプトはそのままコピーして使えますし、出力は実際のセッションを再構成したものです。
スクリーンショットだけを投げると、AIは画面を「推測」します。そこで、ビルドがすでに知っている要素の座標・色・分類をマニフェストとして一緒に入れます。これは新しく書くものではなく、ビルド成果物から抽出するだけで済みます(抽出方法の現実は§9.1.4で正直に比較します)。
# hud_capture_manifest.yaml — QAビルドのスクリーンショットに同梱
screen: { w_pt: 844, h_pt: 390 } # 6.xインチ横持ち、pt単位(横持ちグリップ)
elements:
- id: hp_bar # HPバー
class: P0
rect_pt: [12, 18, 150, 16] # x, y, w, h — 上部左側
fg: "#FF5A5A" ; bg: "#1A1A1A"
- id: skill_slot_1 # スキルスロット(右親指)
class: P0
rect_pt: [760, 300, 40, 40] # ← 右下コーナー、サイズに注目
fg: "#FFFFFF" ; bg: "#202830"
- id: debuff_alert # デバフ通知(昨日追加)
class: P0
rect_pt: [400, 180, 70, 24] # ← 画面中央、位置に注目
fg: "#D4C84A" ; bg: "#888888" # ← コントラストに注目
- id: minimap
class: P1
rect_pt: [744, 20, 80, 80] # 右上
fg: "#A0C0FF" ; bg: "#101820"
添付のスクリーンショットはプロジェクトAの戦闘HUDで(横持ち両手グリップ)、yamlはその画面の要素別の座標・色・分類。両者を突き合わせてレビューして。
コントラストはfg/bgからWCAGで計算して数値まで書いて — テキスト4.5:1、アイコン・大きい文字3:1未達ならFAIL。
P0が上部ステータスバンドや左・右下コーナーを外れて画面中央に浮いていたらWARN(中央は戦闘エフェクトに埋もれる)。
操作要素が44pt未満か、親指コーナー・中央下部スロットバンドを外れていたらFAIL。
マニフェストにないのに画面に見えるものがあれば別途報告して、確信が持てないものは「あいまい」に分けてこちらに渡して。
表だけで出して(要素|検査|測定値|判定|備考)、説明は抜きで。
// (意図: P0=0.5秒以内に見つけるべき情報 / 消費・自動・クイックスロットは中央下部が正常)
レビュールールよりも、そのルールをどう出力するかに多くの行が割かれている点が重要です。AIに自由を与えると、「全体的にすっきりしていますね」のような褒め言葉を吐き出します。そこで計算式(WCAGコントラスト)、合格ライン(4.5:1・44pt)、出力形式(表)、測定値の表記義務、そして「確信が持てなければ人に渡せ」という逃げ道を、あらかじめ釘として打ち込んでおきます。この5本の釘が、褒め言葉を測定値に変えます。
| 要素 | 検査 | 測定値 | 判定 | 備考 |
|-------------|------------|--------------------------|------|-------------------------------------|
| hp_bar | コントラスト | #FF5A5A/#1A1A1A = 5.7:1 | PASS | 非テキストゲージ、3:1充足 |
| hp_bar | 配置 | 上部左側 (12,18) | PASS | P0 上部ステータスバンド |
| skill_slot_1| タッチ | 40×40 pt、右下コーナー | FAIL | HIG 44pt未達、4pt不足(コーナーはOK)|
| skill_slot_1| 配置 | 右下 (760,300) | PASS | 右親指の到達コーナー |
| debuff_alert| コントラスト | #D4C84A/#888888 = 2.0:1 | FAIL | 通常テキスト4.5:1未達 (SC 1.4.3) |
| debuff_alert| 配置 | 画面中央部 (400,180) | WARN | P0なのに中央 — 戦闘エフェクトに埋没 |
| minimap | コントラスト | #A0C0FF/#101820 = 9.8:1 | PASS | |
| minimap | 配置 | 右上 (744,20) | PASS | P1、上部ステータスバンド右側は許容 |
追加報告:
- マニフェストは4種だが、スクリーンショットの左下に黄色の点滅アイコンが
もう1つ見える(座標推定 ~70,330)。マニフェスト漏れの疑い。(あいまい —
画面だけでは何の要素か断定できない)
要約: FAIL 2件(skill_slot_1 タッチ、debuff_alert コントラスト)、WARN 1件(debuff_alert
配置)、あいまい 1件(未登録アイコン)。
出力でもっとも価値のある部分は、合格/不合格の表ではなく、いちばん下の「追加報告(추가 신고)」と「あいまい(애매)」です。AIがマニフェストにない点滅アイコンを画面から見つけ出し、それが何かは自分では断定できないと人に渡した場所です。良いプロンプトは、AIが「これは私には分かりません」と言えるようにします。
この出力をそのまま受け取ってはいけません。AIのレビューそのものを、人が一度レビューします。実際にこのセッションでは、1件が人の手でひっくり返されました。
debuff_alertのコントラストFAILと配置WARNは正しいです。グレーの背景に淡い黄色は§9.1.1で見たとおり標準違反ですし、P0通知を横画面の中央に置いたのも、戦闘エフェクトに埋もれる典型的なミスです。ここまではAIが正しかったのです。
問題はskill_slot_1のタッチFAILです。AIはマニフェストの40×40 ptをそのまま信じて「44pt未達」と判定しましたが、実際のビルドではこのスロットは見た目こそ40ptであるものの、タッチの当たり判定が四方に6pt拡張されていて、実際のタップ領域は52ptです。マニフェストのrect_ptは描画される矩形だけを含み、当たり判定を含んでいませんでした — つまり入力データの欠陥であって、AIの誤判定ではありません。AIは与えられたデータの中で正確に判定しましたし(コーナー位置の判定は正しかったです)、人はコードが知らないビルドの事情(当たり判定の拡張)を知っていました。このFAILは人が棄却します。
そこで二つのことを同時に行います。マニフェスト抽出スクリプトが当たり判定も取り出すように直し(データ欠陥の修正)、AIに再依頼します。
skill_slot_1は見た目のサイズは40ptだが、ヒットボックスが四方に6pt拡張されていて実際のタップ領域は52pt(マニフェストにhit_rectを追加した)。この基準でタッチをもう一度見て。
debuff_alert FAIL/WARNはそのままにして、コントラスト4.5:1を超える配色を3つ提案して(黄色系は維持、背景を暗く)。中央から上部ステータスバンド右側へ移す座標も1つ出して。
AIはskill_slot_1を当たり判定52pt基準でPASSに訂正し、デバフのコントラストのために背景を#2A2A00に暗くして7.8:1を作る配色3案と、通知を上部ステータスバンド右側(約600,18)へ移す座標を返してくれました。1往復で終わります。ビルドごとに画面を目でなぞると同じ事故が繰り返されますが、スクリーンショット+マニフェストをlintにかければコントラスト・配置・タッチの違反が数字として出てきて、人はコードが知らない例外(当たり判定)とあいまいなもの(未登録アイコン)だけを判定します(レビュー1画面が手作業では十数分、このループでは数分 — 著者の推定、未検証の仮説です。絶対時間よりも「目でなぞる」と「標準で測定する」の構造の違いとして読むのが正しいです)。
先のセッションでdebuff_alertがWARNを受けた理由、そしてP0情報をどこに置くべきかを1枚の図として残しておくと、以後のすべての配置判定が速くなります。横に持ったスマートフォンでは、画面は四つの場所に分かれます。上部の横長ステータスバンド(視線が最初に届き、指は行かない読み取り専用)、左下・右下の二つのコーナー(両手の親指が届く操作の場所 — 左親指=移動、右親指=スキル)、その間の中央ゲーム領域(戦闘が起きる場所)、そしてゲーム領域の下の中央下部スロットバンド(消費・自動アイテムとクイックスロット・スキルスロットを置く場所)です。下の図では、緑・アンバーがP0とスロットの安全な領域、赤がP0通知が埋もれるゲーム中央です。
ルールは単純です。P0情報(HP・MP・重要通知)は緑(上部の横長ステータスバンドまたは左右下のコーナー)の中に置きます。視線が最初に届くか、親指がつねにとどまる通り道だからです。逆にゲーム中央(赤)は戦闘そのものが起きる場所なので、ここにP0通知を置くと、エフェクトが画面を覆った瞬間に情報が埋もれます。一つ注意 — ゲーム中央と中央下部は別物です。ゲーム中央は危険ですが、その下の中央下部スロットバンド(アンバー)は消費・自動アイテムとクイックスロット・スキルスロットが住む場所です。自分が使うもの、自動で消費されるものを一目で見るために、両親指の間に置きます。そして読むだけの情報(HP/MP/ターゲットの体力)は上部に、押す要素(移動・スキル)は左右下のコーナーに、消費・スロットは中央下部に — この三つが指・視線の領域です。§9.1.2でデバフ通知がWARNを受けた理由が、この図1枚で説明できます — 0.5秒以内に見なければならないP0を、よりによっていちばん見えないゲーム中央に置いたからです。訂正案で上部ステータスバンド右側へ移したのは、まさにこの図の緑へ戻したということです。
本章のlintは、「要素別の座標・色」がきれいに入ってくるという前提の上に成り立ちます。ところが、その座標をどこからどう取り出すかが、実際にはもっとも現実的な分かれ道です。書籍がよくごまかす場所なので、三つの経路を正直に比較します。正解は一つではなく、チームの状況によって分かれます。
| 経路 | 何をするか | 強み | 弱み / 現実 |
|---|---|---|---|
| ① ゲーム内telemetryログ | UIフレームワークが描画するウィジェットの座標・サイズ・色をビルドが直接ダンプ | 座標が正確(推定ではない)、当たり判定・アンカーまで出る | UIコードにダンプ用フックを仕込む必要あり。プログラマーとの協業が必要。一度敷けばもっとも信頼できる |
| ② 既製のvision API | スクリーンショットをOCR・物体検出APIに入れてテキスト・ボックスの座標を抽出 | ビルド修正が不要、外部のスクリーンショットでも可能 | 座標が近似値、ゲージ・アイコンのような非テキストは分類が弱い。外部送信=未公開ビルドの流出リスク |
| ③ 自前実装(ピクセル分析) | スクリーンショットを直接読んで色の境界・ボックスをヒューリスティックに抽出 | 依存関係が最小、色コントラストの計算には十分 | 要素の意味(これがP0かどうか)が分からない。マニフェストと照合してこそ役に立つ。保守の負担 |
三つの経路の関係が、本章のワークド・トランスクリプトをそのまま説明します。§9.1.2でコントラスト検査が正確だったのは、色値(fg/bg)が①・③で正確に入ってきたからであり、タッチFAILが人の手でひっくり返ったのは、当たり判定がマニフェストから抜けていたからです(②・③は当たり判定を見られません。①だけが見られます)。つまりコントラストはピクセルだけでも捕まえられますが、タッチの当たり判定は①のtelemetryなしには捕まえられません。この限界を知って始めてこそ、AIレビューの結果をどこまで信じるかの線が引けます。
著者プロジェクトの選択は、①telemetryを正本とし、AIはスクリーンショット+telemetryマニフェストを照合するレビュアーとして使う構造です。画面にだけ見えてマニフェストにないもの(§9.1.2の未登録の点滅アイコン)をAIが捕まえ、マニフェストにはあるのに画面ではずれているものを人が捕まえます。どちらか一方だけでは、両側に死角が残ります。
flowchart LR
A["QAビルド
HUDスクリーンショット"] --> C
B["telemetryダンプ
座標・色・当たり判定
(経路①)"] --> C["マニフェスト + スクリーンショット"]
C --> D["AIレビュー
コントラスト・配置・タッチ
+ 未登録要素の報告"]
D --> E{"WCAG/HIG
標準判定"}
E -->|FAIL/WARN| F["人によるレビュー
例外・あいまいのみ"]
F -->|再依頼| D
F -->|通過| G["ビルドゲート通過"]
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class A,B,C data;
class D ai;
class E code;
class F human;
class G pass;
人の手が触れる場所は2か所だけです。telemetryダンプをきれいに入れる場所(先頭)と、コード・標準が捕まえられない例外(当たり判定)・あいまいなもの(未登録要素)を判定する場所(最後)です。その間の退屈なコントラスト計算と配置照合は、AIと標準が回します。
AIレビューが毎回計算をやり直すと、トークンと時間がかかります。コントラスト・タッチ・コーナー到達のように、決定論的に答えが出る項目はコードが先にさばきます。AIはコードが捕まえられないもの(画面の意味解釈、未登録要素)にだけ入ります。両者は競争ではなく分担です。
# hud_lint.py — HUDマニフェスト標準検証(骨格)
# 入力: telemetryマニフェスト(要素別 rect/hit_rect/fg/bg/class/interactive)
# 出力: WCAG/HIG + 両手到達の違反リスト
def _luminance(hex_color): # WCAG相対輝度
r, g, b = (int(hex_color[i:i+2], 16) / 255 for i in (1, 3, 5))
f = lambda c: c/12.92 if c <= 0.03928 else ((c+0.055)/1.055) ** 2.4
R, G, B = f(r), f(g), f(b)
return 0.2126*R + 0.7152*G + 0.0722*B
def contrast_ratio(fg, bg): # WCAGコントラスト比
L1, L2 = sorted((_luminance(fg), _luminance(bg)), reverse=True)
return (L1 + 0.05) / (L2 + 0.05)
def in_thumb_corner(e, w, h):
"""横持ち両手の親指が届く左・右下コーナーか。"""
x, y = e["hit_rect"][0] / w, e["hit_rect"][1] / h
bottom = y > 0.55
left_corner = bottom and x < 0.30 # 左手親指 = 移動
right_corner = bottom and x > 0.70 # 右手親指 = スキル
return left_corner or right_corner
def lint(elements, screen_w, screen_h):
issues = []
for e in elements:
# 規則A: コントラスト比(テキスト4.5:1 / 非テキスト・大きい文字3:1)
need = 4.5 if e["kind"] == "text" else 3.0
cr = contrast_ratio(e["fg"], e["bg"])
if cr < need:
issues.append(f"[A] {e['id']}: 대비 {cr:.1f}:1 < {need}:1 (WCAG SC 1.4.3)")
# 規則B: タッチターゲット — ヒットボックス基準(見た目のサイズではない)
if e.get("interactive"):
tap = min(e["hit_rect"][2], e["hit_rect"][3]) # ← rectではなくhit_rect
if tap < 44:
issues.append(f"[B] {e['id']}: 탭 {tap}pt < 44pt (HIG)")
# 規則C: 操作要素は両手親指コーナー(左・右下)になければならない
if not in_thumb_corner(e, screen_w, screen_h):
issues.append(f"[C] {e['id']}: 조작 요소가 양손 엄지 코너 밖에 배치됨 "
f"(x={e['hit_rect'][0]}, y={e['hit_rect'][1]})")
return issues
このコードが、会議での「この文字、ちょっと見えにくくないですか?」という水掛け論を終わらせます。[A] debuff_alert: 대비 2.0:1 < 4.5:1 (WCAG SC 1.4.3)とコードが出力すれば、議論することはありません。直せばいいのです。注目すべき2行は、ルールBがrectではなくhit_rectを見るという点、そしてルールCが操作要素を左下・右下の二つのコーナーだけ通すという点です — §9.1.2で人がAIをひっくり返したあの教訓(当たり判定)と、横持ち両手グリップの到達限界が、一緒にコードへ入りました。単一の「親指の弧」しきい値一つではなく、左親指(移動)・右親指(スキル)の二つのコーナーを別々に見るという点が、横持ち判定の核心です。一度人が捕まえた例外は、次からはコードが捕まえます。そこでAIには「コードがPASSにしたものではなく、画面でしか見えない異常(未登録要素・視覚的な重なり・見切れ)を報告せよ」という狭い役割だけを残します。決定論で捕まえられるものはコードが、画面の意味解釈が必要なものはAIが、ビルドの事情を知る必要のある例外は人が — この分担が核心です。
本章に出てきた数値の出典は三つだけです。コントラスト4.5:1・タッチ44pt・48dpはWCAG SC 1.4.3・HIG・Materialの公式値であり、#888の背景に#D4C84Aの文字が約2.0:1であることも、その公式に色値を入れた計算値です(§9.1.1・§9.1.5)。「レビュー1画面が手作業で十数分、ループで数分」・「横持ちの常時情報12〜16種」は未検証の著者推定なので、本文にそう明記しました。残り(ビルド別のコントラストFAIL件数、タッチ当たり判定の未達数、親指コーナーからの逸脱件数、telemetryの誤タップ率)は、ビルドログで直接数えられる値です。ユーザーの不満件数のように、HUD一つでは因果を断定できない結果指標はKPIに上げていません。
| パターン | なぜ失敗するか | 処方 |
|---|---|---|
| デザイナーのモニターで目視レビュー | 6インチ・戦闘エフェクトの条件が抜けてコントラスト事故が繰り返される | スクリーンショットlintをビルドゲートに(§9.1.2) |
| スクリーンショットだけAIに投げて「レビューして」 | 座標を推測して近似判定になり、信頼できない | telemetryマニフェストを同梱(§9.1.4) |
| 見た目のサイズでタッチターゲットを判定 | 当たり判定の拡張を見落とし、問題ないボタンをFAILにする | hit_rect基準の検査(§9.1.5) |
| P0通知を画面中央に配置 | 戦闘エフェクトに埋もれて「見えなくて死んだ」 | 上部ステータスバンド・両コーナーへ(§9.1.3) |
| 操作ボタンを画面左の中央・上部に配置 | 横持ち両手グリップでは親指が届かない | 左下・右下コーナーへ(§9.1.5ルールC) |
| 縦持ち片手グリップを前提に設計 | MMORPGは横持ち両手が標準で、情報・操作が合わない | 横持ち両手へ転換(§9.1.1) |
| コントラストを「見える/見えない」で議論 | 結論が人によって違う | WCAG 4.5:1の計算値で(§9.1.1) |
四つ目がもっとも頻繁に繰り返されます。新しい通知を急いで載せるとき、空いているスペースが画面中央しかないのでそこに置きます — そしてその中央こそが、まさにゲームが起きている場所なのです。
一人ならこれだけ:telemetryもマニフェストもなくて構いません。自分のゲーム(または好きなゲーム)の横持ちHUDスクリーンショットを1枚撮り、いちばん小さい文字・アイコン2〜3個の前景/背景の色をスポイトで抽出して手で書き留めたうえで、§9.1.2のプロンプトを貼って一度回してみましょう。AIが計算したコントラスト数値を一つ選び、オンラインのWCAGコントラスト計算ツールで自分で検算してみると、「見える/見えない」がどう数字になるのかが体で分かります。AIが画面中央に置いたP0があれば、「なぜ中央が危険なのか、もう一度見直して」と反論してみましょう。
チームなら、次の一歩から始めましょう。UIフレームワークからウィジェットの座標・色・当たり判定をダンプするtelemetryフック(経路①)をまずプログラマーと合意し、§9.1.5のcontrast_ratioという一つの関数からビルドに入れます。コントラスト計算は標準の公式なので異論がなく、関数一つあるだけでも、ビルドごとのコントラストFAILが数字として出てきます。その次にin_thumb_cornerを載せれば、横持ち両手の操作要素のコーナー逸脱までコードが捕まえます。配置・未登録要素のような解釈は、その上にAIとして載せればよいのです。
一次読者:モバイルファーストのアクション・MMORPGのUX・戦闘プランナー(中規模チーム) 一人・趣味開発の読者向け縮小版:§9.2.7「一人ならここまで」
新クラスのスキル6個を、モバイル画面のどこにどう並べるか。この問いが会議に上がると、最初の30分はいつも同じように流れていました。誰かがホワイトボードに丸を6つ描き、別の誰かが「それだと親指が届かない」と言い、また別の誰かが「では上に上げるとミニマップが隠れる」と返します。3人とも正しいことを言っているのに、結論は出ませんでした。次の会議では、同じホワイトボードがまた描かれました。
問題は、配置案を描く仕事と、その案がルールを守っているかを検査する仕事が、一人の頭の中で混ざっている点です。描いた本人は、自分が描いたものをなかなか落とせません。本章はその2つを切り離します。配置案を複数作る退屈な仕事はAIに任せ、その案が重なり・親指コーナー・タッチサイズのルールに違反していないかはコードが落とします。 人間は、コードが通した案の中から「ゲームの手触り」で1つを選ぶ場所にだけ立ちます。9.1がHUD全体のルールブックを立てたとすれば、本章はそのルールブックを、スキルボタンという最も手が触れる一塊に最後まで適用する1サイクルです。
HUD上のほとんどの要素は、読むだけのものです。HPバーを押す人はいません。だからこそ§9.1の親指コーナーの図では、HP・MP・ターゲットの体力を、手が届かない上部の読み取り領域に置いてよかったのです。スキルボタンは正反対です。0.1秒単位で正確に押す必要があり、戦闘中は視線が敵に向いているため、指は位置を記憶で探します。位置がわずかにずれただけで、その場で誤タップが起きます。
MMORPGモバイルは横持ち両手グリップが標準で、押す要素は左右下のコーナーに、消費・スロットは中央下部に置きます(なぜ横持ちが標準なのか、3つの領域が何なのかは§9.1で扱います)。その標準では、スキルはほぼすべて右手の親指が届く右下コーナーのクラスターに並びます(左手の親指は左下の移動に縛られています)。1つ区別を設けます — 0.1秒単位で押すアクティブスキルはこの右下クラスターが定位置ですが、消費・自動アイテム・クイックスロットは両親指の間、中央下部のスロット帯に別に置きます。本章はアクティブスキルボタンだけを扱い、すべての座標判定は横持ち両手グリップを前提とします。
そのため、スキルボタン配置は3つの決定論的ルールに同時に縛られます — 最小タッチターゲット(HIG 44pt)、隣接ボタン間隔(Material 8dp)、親指の到達(スキルは右親指の右下コーナー)。3つとも§9.1.1で立てたルールブックにすでに入っている、座標とサイズで判定できる項目なので、公開標準の数値はそのルールブックに従います(タッチ44pt・間隔8dpは公認数値、右親指コーナーだけは業界通用モデル)。この3つが、本章でAIの配置案を落とすlintの一次入力になります。「このボタン、少し小さくありませんか?」ではなく「skill_3は40ptでHIG 44pt未達」とコードが言えば、ホワイトボードの前の30分が消えます。
プラットフォーム基準をPCと並べてみると、配置の出発点がはっきりします。PCは精密・大量、モバイル横持ちは両手コーナー限定です(全体の比較表は§9.1のルールブックを参照)。スキル入力だけを切り出すと違いは明確です — PCはショートカットキーがあるので、スキルを画面のどこに置いても指はキーボードにあり、到達は問題にならず、スロットも多数置けます。モバイル横持ちはホバーもショートカットキーもないため、スキルを右親指が届く右下コーナーに頻度順に並べ(同時露出6〜8個が限界)、最もよく使うスキルをコーナーの内側(最も届きやすい位置)に置く必要があります。だからモバイルのスキル配置の本質は「きれいな配列」ではなく、「右親指コーナー内での頻度順の優先配置+ルールブックによるチェック」です。そして案を複数描く仕事は、人が手でやると退屈で、やるたびに基準が揺れます。退屈で気まぐれな反復作業 — AIが人より疲れずにこなせる場所です。
新クラス「呪術師」のアクティブスキル6個をモバイルに配置する1サイクルを、入力から廃棄まで通しで見せます。以下は著者のプロジェクト(モバイルファーストMMORPG、以下「プロジェクトA」)の新規スキルUI作業セッションを忠実に再現したものです。入力とプロンプトはそのままコピーして使うことができ、出力は実際のセッションを再構成したものです。
スキル6個の使用頻度と基本的な性格をyamlにまとめます。使用頻度はデータシートの戦闘ログから抽出した値なので、新たにでっち上げるものではありません。
# skill_set_shaman.yaml — 新クラス「呪術師」アクティブスキル6種
screen: { w: 2400, h: 1080, dpr: 3 } # 6.xインチ横持ち基準、pt = px / dpr
skills:
- id: s1_quickbolt # 基本攻撃、最も頻繁
use_rate: 0.41 # 戦闘中の使用比率(ログ抽出)
role: spam # 連打
- id: s2_hex # デバフ、頻繁
use_rate: 0.22
role: core
- id: s3_totem # 設置型、普通
use_rate: 0.14
role: core
- id: s4_heal # 回復、たまにだが緊急
use_rate: 0.11
role: panic # 危急時に即座
- id: s5_curse # 範囲デバフ、たまに
use_rate: 0.08
role: situational
- id: s6_ultimate # アルティメット、まれに
use_rate: 0.04
role: burst
核心となるスロットはuse_rateとroleです。最も頻繁に押すs1_quickbolt(41%)と、緊急時に0.2秒以内に見つけなければならないs4_heal(panic)は、右親指が最も届きやすい位置(右下コーナーの内側)に置く必要があります。まれにしか使わないs6_ultimate(4%)はコーナーの縁で、少し遠くても構いません。この優先順位が、次のステップのAI配置への入力のすべてです。
添付のyamlは新クラスのアクティブスキル6種だ。横持ち両手グリップの画面にスキルボタンの配置案を3つ作って。スキルは右手の親指が届く右下コーナーに敷いて(左手は左下の移動に縛られている)。
3案は互いに異なる哲学で — 例えばコーナー扇形、2行グリッド、頻度加重のように。ボタンごとにx,y,w,hをpxでくれ(画面2400x1080、dpr 3、pt=px/3)。
よく使うかrole=panicのスキルは右親指がよく届くコーナー内側に、タッチターゲットは132px(44pt)以上、隣接間隔24px(8dp)以上で。
案ごとに設計意図1行と君が見る弱点1つも必ず一緒に書いて — 弱点のない案はだめ。案別に(配列哲学|座標表|設計意図|自己申告弱点)だけ、説明抜きで。
このプロンプトで、配置の指示よりも力を入れたのは、出力形式と弱点申告を強制するくだりです。座標を数字で受け取らなければlintを回せないため、px出力を動かないように固定し、「この案の弱点1つ」を一緒に出させて、AIが自分の限界を先に語るようにしました。自由に「きれいなUIを作って」と頼むと、一般的なモバイルRPGの平均的な配置が、図もなく言葉だけで返ってきます。
== 案A: 右下コーナー扇形 ==
配列哲学: 右親指の回転軸を中心に6個を扇形に。頻度順に内側→外側。
| id | x | y | w | h | pt |
|-------------|------|-----|-----|-----|------|
| s1_quickbolt| 2160 | 912 | 168 | 168 | 56pt |
| s2_hex | 2016 | 852 | 132 | 132 | 44pt |
| s3_totem | 2052 | 996 | 132 | 132 | 44pt |
| s4_heal | 1884 | 924 | 132 | 132 | 44pt |
| s5_curse | 1920 | 780 | 120 | 120 | 40pt |
| s6_ultimate | 1788 | 840 | 120 | 120 | 40pt |
設計意図: よく使うs1を最も大きく、届きやすい右下コーナー内側に。
自己申告弱点: s5,s6が40ptでHIG未達のおそれ。扇形の外側は右親指到達が曖昧。
== 案B: 2行グリッド(2x3) ==
配列哲学: 右下2行x3列の格子。視覚的整列を優先。
| id | x | y | w | h |
|-------------|------|-----|-----|-----|
| s1_quickbolt| 2208 | 912 | 132 | 132 |
| s2_hex | 2052 | 912 | 132 | 132 |
| s3_totem | 1896 | 912 | 132 | 132 |
| s4_heal | 2208 | 756 | 132 | 132 |
| s5_curse | 2052 | 756 | 132 | 132 |
| s6_ultimate | 1896 | 756 | 132 | 132 |
設計意図: 全ボタン同一サイズ、整列がすっきり。
自己申告弱点: 上の行(s4,s5,s6, y=756)が右親指コーナー上側の境界で到達が曖昧。
緊急回復s4が上段なので危急時に探すのが遅いおそれ。
== 案C: 頻度加重扇形 ==
配列哲学: s1を中心の大きい円に、残りを頻度順に右親指の回転軸へ巻く。
| id | x | y | w | h |
|-------------|------|-----|-----|-----|
| s1_quickbolt| 2184 | 900 | 180 | 180 |
| s2_hex | 2028 | 852 | 144 | 144 |
| s3_totem | 2052 | 996 | 132 | 132 |
| s4_heal | 1908 | 912 | 144 | 144 |
| s5_curse | 1932 | 792 | 120 | 120 |
| s6_ultimate | 1812 | 972 | 120 | 120 |
設計意図: 手がs1へ自然に行き、緊急s4を大きくしてコーナー内側に。
自己申告弱点: 扇形なのでボタン間隔が不均等。s2-s5, s4-s6の近接衝突のおそれ。
3案すべてが自己申告の弱点を報告した点が、この出力の核心です。Aは「40pt未達のおそれ」、Bは「緊急回復が上の行」、Cは「近接衝突のおそれ」。AIが、自分の描いた絵の弱いところを先に指さしました。しかしこれは自己申告にすぎず、本当の判定はコードが行います。
3案を目で比較すると、また「Bのほうがすっきりして見えるけれど」という好みの争いが始まります。そこで§9.2.3のskill_layout_lint.pyに3案をそのまま食わせます。結果はこうでした。
[案A] 右下コーナー扇形
[FAIL] B-size : s5_curse 40pt < 44pt (HIG未達)
[FAIL] B-size : s6_ultimate 40pt < 44pt (HIG未達)
[WARN] C-corner: s6_ultimate x=1788 — コーナー左境界、右親指到達「普通」
→ 通過 4/6, 致命違反 2
[案B] 2行グリッド(2x3)
[FAIL] C-corner: s4_heal y=756 (0.70h) < 0.55h 下でない → 右親指コーナー上側
[FAIL] C-corner: s5_curse y=756 (0.70h) < 0.55h 下でない → 右親指コーナー上側
[WARN] role : s4_heal(panic) y=756 — 緊急スキルが上段
→ 通過 4/6, 致命違反 2
[案C] 頻度加重扇形
[FAIL] A-overlap: s2_hex ∩ s5_curse 間隔 18px < 24px (8dp未達)
[FAIL] A-overlap: s4_heal ∩ s6_ultimate 間隔 12px < 24px (8dp未達)
→ 通過 4/6, 致命違反 2
3案がすべて落ちました。自己申告とlintの判定がほぼ重なった点が興味深いところです。AIが「弱点」と言った場所で、実際に違反が出ました。ただし自己申告は「おそれ」であり、lintは「s2_hexとs5_curseの間隔18px」という数字です。会議で議論する余地がありません。
この段階で重要な決定が1つあります。3案すべてが落ちたからといって、最初には戻りません。lintレポートをそのまま次のプロンプトに貼り付けて再依頼します。
3案のlint結果をそのまま貼る(上のレポート)。この中で違反が最も直しやすい案を1つだけ選んで、FAILを全部0にした修正案を1つだけくれ。
ボタンは全部132px(44pt)以上、間隔24px(8dp)以上、s4_heal(panic)は右親指コーナー内でs1の次によく届く位置に。
座標表だけくれて、どのFAILがどう解消されたか1行ずつ書いて。
// (意図: コーナー内 = x>横70%(1680), y>縦55%(594))
AIは案A(コーナー扇形)を選びました。違反が「ボタン2つのサイズ不足」だけで、最も少ない手直しで済むからです。小さくて落ちたs5・s6を132pxに拡大し、大きくなった分だけ扇形の曲線をわずかにコーナーの内側へ寄せて間隔を確保した修正案を出してきました。
== 案A' (修正) ==
| id | x | y | w | h | pt |
|-------------|------|-----|-----|-----|------|
| s1_quickbolt| 2160 | 900 | 168 | 168 | 56pt |
| s2_hex | 2010 | 846 | 132 | 132 | 44pt |
| s3_totem | 2046 | 990 | 132 | 132 | 44pt |
| s4_heal | 1872 | 918 | 144 | 144 | 48pt |
| s5_curse | 1908 | 774 | 132 | 132 | 44pt |
| s6_ultimate | 1740 | 858 | 132 | 132 | 44pt |
解消: B-size s5 40→44pt / B-size s6 40→44pt /
C-corner s6 x=1740(0.725w)·y=858(0.79h)でコーナー内側を維持 →
role: s4_heal 144pxに拡大して緊急識別を強化。
skill_layout_lint.pyに案A'を再び食わせました。
[案A'] 右下コーナー扇形(修正)
[PASS] B-size : 全ボタン ≥ 44pt
[PASS] A-overlap: 最小間隔 30px ≥ 24px
[PASS] C-corner : 全操作ボタンが右親指コーナー内 (x≥1680, y≥594)
[WARN] C-corner : s6_ultimate x=1740 — コーナー左端、到達「普通」
→ 通過 6/6, 致命違反 0, WARN 1
FAILが0になりました。残ったWARN 1件(s6_ultimateがコーナーの左端にあり、右親指の到達が「容易」ではなく「普通」)は、コードが自動では落としません。人間に上げます。そしてこのWARNは、実は意図された設計です。s6は使用頻度4%で最もまれにしか使わないアルティメットスキルなので、コーナーで最も内側の位置はよく使うs1に譲り、縁に置くのが正しいのです。人間が「このWARNは意図だ」と判定して通しました。入力→3案生成→lint→全滅→再依頼→通過という1サイクルが、ここで閉じます。
この一巡が、本章の「Show」の基準です。AIが何を描き、lintが何を落とし、人間がどのWARNを生かすのかを最後まで見なければ、「AIでUI案を作った」という文は空虚です。
上のサイクルの心臓部は、3つのルールで案を落とす30行あまりのコードです。§9.2.1の表の3項目が、そのまま3つの関数になります。
# skill_layout_lint.py — スキルボタン配列検証(骨格)
# 入力: AIが出したボタン座標リスト [{id, x, y, w, h, role, use_rate}]
# 出力: A-overlap / B-size / C-corner 違反リスト
# 前提: 横持ち両手グリップ。スキルは右手の親指が届く右下コーナーに敷く。
MIN_TAP_PX = 132 # HIG 44pt * dpr 3 = 132px
MIN_GAP_PX = 24 # Material 8dp * dpr 3 = 24px
RIGHT_CORNER_X = 0.70 # 画面横0.70より右 = 右親指コーナー
BOTTOM_Y = 0.55 # 画面縦0.55より下 = 下部コーナー
def in_right_thumb_corner(b, w, h):
"""横持ちグリップで右手の親指が届く右下コーナーか。
(左親指=左下の移動、右親指=右下のスキル)"""
rx, ry = b["x"] / w, b["y"] / h
return rx > RIGHT_CORNER_X and ry > BOTTOM_Y
def lint(buttons, screen_w, screen_h):
issues = []
# ルールB: タッチターゲット最小サイズ (HIG 44pt)
for b in buttons:
side = min(b["w"], b["h"])
if side < MIN_TAP_PX:
issues.append(f"[FAIL] B-size : {b['id']} {side//3}pt "
f"< 44pt (HIG未達)")
# ルールA: 隣接ボタンの重なり/間隔 (最も近い2辺の距離)
for i, a in enumerate(buttons):
for c in buttons[i+1:]:
gap = edge_gap(a, c) # 2つの矩形の最短間隔(px)
if gap < MIN_GAP_PX:
issues.append(f"[FAIL] A-overlap: {a['id']} ∩ {c['id']} "
f"間隔 {gap}px < {MIN_GAP_PX}px (8dp未達)")
# ルールC: 操作要素は右親指コーナー内。panicはコーナー内側ほど良い。
for b in buttons:
rx, ry = b["x"] / screen_w, b["y"] / screen_h
if not in_right_thumb_corner(b, screen_w, screen_h):
issues.append(f"[FAIL] C-corner: {b['id']} "
f"x={b['x']}({rx:.2f}w) y={b['y']}({ry:.2f}h) "
f"→ 右親指コーナー外")
elif b.get("role") == "panic" and rx < 0.78:
issues.append(f"[WARN] role : {b['id']}(panic) "
f"緊急スキルがコーナー内側境界の近く")
return issues
このコードが、会議での「B案のほうがきれいですけど」という好みの発言を無力化します。きれいさは、lintが通した後に語るものです。lintが[FAIL]を吐く案は、きれいであろうとなかろうとビルドに入れません。§9.1.1で立てたHUDのlintゲートを、スキルボタンという最も厄介な一塊に最後まで適用したものです — 座標・サイズで判定できるものはコードが、「このWARNは意図か」という判断は人間が受け持つという分担が、ここでもそのまま成立します。
全体のサイクルをひと目で見ると、こうなります。
flowchart LR
A["スキル仕様yaml
(use_rate·role)"] --> B["AI: 配置案3つ
座標+自己申告の弱点"]
B --> C{"skill_layout_lint.py
重なり・サイズ・右親指コーナー"}
C -->|FAILあり| D["lintレポートを
そのまま再依頼"]
D --> B
C -->|FAIL 0, WARNのみ| E["人間: WARNが
意図か判定"]
E --> F["配置確定
+ ArtGuide 06_UI sync"]
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class A data;
class B ai;
class C code;
class E human;
class F pass;
人の手が触れる場所は2か所だけです。入力仕様をきれいに入れる最初の部分と、lintには落とせないWARNを判定する最後の部分。その間の退屈な3案生成と座標チェックは、AIとlintが回します。
配置案を一度出して終わりにすると、このツールがうまく機能しているのか分かりません。そこでlintの結果を毎回ログに残します。記録する値は単純です — AIが出した案が最初のlintをいくつ通過したか(初回通過率)、そして再依頼何回でFAIL 0に到達したか(往復回数)。
以下の数値は、新クラス3種(呪術師ほか2種)のスキルUIをこのサイクルで作りながら直接カウントした実測値です。標本がクラス3種(配置セッション9回)と小さいため、精密な母数ではなく方向性を示す値として読むのが正しい姿勢です。加工した数字はありません。
| 項目 | 実測 | 備考 |
|---|---|---|
| AIの最初の配置案のうちlintを一発通過 | 9回中1回 | 残りの8回は1件以上のFAIL |
| 初回判定時の平均FAIL数 | 1案あたり1.8件 | 大半はサイズ不足または右親指コーナー外 |
| FAIL 0到達までの平均往復 | 1.4回 | lintレポート再投入方式 |
| 最も多いFAILタイプ | B-size(サイズ不足) | 次がC-corner(右親指コーナー) |
最も重要なのは1行目です。AIが最初に出した案は、9回中8回lintを通過できませんでした。 これはこのツールの失敗ではなく、正常動作のシグナルです。AIに座標を自由に出させると、HIG 44ptを頻繁に破ります。lintがそれを毎回捕まえ、レポートをフィードバックすれば、1〜2回の往復で0になります。もし初回通過率が100%だったなら、それはlintが緩すぎるという意味であって、AIが完璧だという意味ではありません。
この通過率ログは、lintルールを締めるか緩めるかを決める根拠にもなります。あるFAILタイプが毎回「実は意図だった」と人の手で解放されるなら、そのルールは厳しすぎます。逆に、リリース後に誤タップの不満が届いているのにlintは通していたなら、ルールが緩いのです。
§9.2.2でlintを通過した案A'を座標どおりに描くと、以下のようになります。表の数字が実際の画面でどんな形になるのかは、図で見て初めて手に取るように分かります。横持ちのスマートフォンを両手で握った姿勢で、左手の親指は左下(移動)、右手の親指は右下(スキルクラスター)に届きます。円の大きさはタッチターゲット(pt)に比例し、色は親指到達の難易度(緑は容易、黄は普通)です。
図で見ると、lintレポートの最後のWARNがひと目で理解できます。s6_ultimate(黄)だけが右下コーナーの左端、右親指到達「普通」の位置です。しかしs6は使用頻度4%のアルティメットスキルなので、コーナーの縁に置くのが正しいのです。最もよく使うs1(緑、56ptで最大)は右親指が最も届きやすいコーナー内側の右下に、緊急回復のs4(黄色の枠)はサイズを大きくして、緊急時に手が素早く見つけられるようにしました。左手の親指は左下の「移動」に縛られているため、スキルはすべて右コーナーに集まります。座標表1枚が図1枚と正確に一致すること — それが座標を数字で受け取った理由です。
| パターン | なぜ失敗するか | 処方 |
|---|---|---|
| ホワイトボードに丸だけ描いて会議 | 座標がなくlint不可、好みの争いの繰り返し | 座標をpxで受け取りlintに食わせる(§9.2.2) |
| 「AIさん、きれいなスキルUIを作って」と丸投げ | ルールブックなしでは一般的なRPGの平均配置になる | 3案+座標+自己申告の弱点を強制するプロンプト |
| 縦持ち片手グリップ前提で配置 | MMORPGは横持ち両手が標準、スキルは右親指コーナー | 横2400x1080、右下コーナー基準でlint |
| 配置案を目だけで比較 | HIG未達・重なりを毎回見落とす | skill_layout_lint.pyで自動判定 |
| 最初の案がlint通過→ツールはうまくいったと安心 | lintが緩いシグナルの可能性 | 通過率ログでルールの締まり具合を点検(§9.2.4) |
| WARNまでコードが自動遮断 | 意図された配置(まれなアルティメット)まで落とす | WARNは人間の判定で(§9.2.3) |
5番目が最も見落とされがちです。AIの最初の案が毎回通過すれば気分は良いですが、それはたいていlintルールが緩いという意味です。9回中8回落ちるのが健全な状態です。
一人ならここまで:lintコードがなくても大丈夫です。自分のゲーム(または好きなゲーム)のスキル4〜6個を選んで§9.2.1の形式の仕様を手で書き(use_rateはざっくり頻度の順位だけで構いません)、§9.2.2のプロンプトをそのまま貼り付けて3案を受け取ってみましょう。次に、定規の代わりに「44pt = 132px」だけを頭に入れて、AIが出した座標表から132px未満のボタンを手で探して丸を付けてみましょう。さらに横画面だと想定して、右下コーナー(横70%より右+縦55%より下)の外に落ちたスキルがないかも確かめてみましょう。その1回が、lintが何をしているのかを体で教えてくれます。
チームなら、次の一歩から始めましょう。§9.2.3のskill_layout_lint.pyの3つの関数(サイズ・間隔・右親指コーナー)から先にコードとして固定しておきます。3つの関数で十分です。ルールブックがあれば、AIの配置案でもデザイナーの案でも同じ物差しで測ることができ、lintを通過した案だけがアートチームの96_ArtGuide/06_UI/へ渡り、_convert_md_to_html.py→_SyncToArtRepo.batの経路で自動syncされます。確定座標がアートチームに届くまでの、人間の最後の仕事は、WARN 1件を「意図だ」と判定することだけです。
主な読者:非企画職(アート)と毎日連携するUX・UIプランナー(中規模チーム) 一人/趣味の読者向けミニ版:§9.3.8「一人ならこれだけ」
プランナーがMarkdown(マークダウン)でUIの決定事項を整理しておくと、仕事がすっきりします。バージョン管理ができ、diffが見え、AIにそのまま渡せます。問題は、アートチームがMarkdownを読まないことです。より正確に言えば、読む理由がないのです。アートデザイナーに「아트_결정사항.mdをSVNから取得して見てください」と言うと、半分はSVNクライアントをインストールしておらず、残りの半分は##ヘッダーや表の記法が崩れたままメモ帳で開いた画面を見ながら「これ、どうやって見るんですか」と聞いてきます。
ここで間違った処方は「アートチームにMarkdownを教えよう」です。アートデザイナーの時間はピクセルを磨くことに使うべきです。Markdownの記法、SVNのチェックアウト、diffの見方を学ぶのに費やす時間は、すべて損失です。正しい処方は、プランナー側で変換と受け渡しを自動化し、アートチームの学習負担を0にすることです。プランナーはmdで書き、スクリプトがhtmlに変換し、別のスクリプトがアートのリポジトリへ押し込み、アートチームはブラウザーでhtmlだけを見ます。本章では、そのパイプラインを実際に一度最後まで回します — 決定事項のドラフトをAIで作るところから、変換・受け渡しの自動化、そして人が何を拒否するかまで。
企画とアートの連携が壊れる理由を「決定権の曖昧さ」とまとめる本は多いです。誰が色を決め、誰が機能を決めるのか。その分担も重要ですが、分担表をどれほどうまく描いても、その分担表をアートチームが読めなければ何も起こりません。実務でより頻繁に事故が起きる場所は、決定権ではなく受け渡しのフォーマットです。
著者のプロジェクト(モバイル優先のMMORPG、以下「プロジェクトA」)で実際に繰り返された事故は次のとおりです。
| 事故 | 表面的な原因 | 本当の原因 |
|---|---|---|
| アートが旧バージョンの決定事項で作業 | 「最新版を受け取っていませんでした」 | 受け渡しが手動(メール添付)のため漏れる |
| 決定事項の表が崩れて見える | 「これ、どうなってるんですか」 | mdをメモ帳で開いたため |
| 「その決定、どこに書いてありますか?」 | 口頭での伝達 | 正本(カノニカル)がチャットに散らばっている |
3つの事故はいずれも決定権の問題ではありません。正本のドキュメントが、アートチームが読むフォーマットで、自動的に、常に最新の状態で届かないから起きるのです。だから本章の道具は、分担表ではなく受け渡しのパイプラインです。分担は一度合意すれば終わりですが、受け渡しは決定が変わるたびに毎回発生するからです。
まず実際のフォルダー構造から見ます。プロジェクトAのアートガイドはworkspace/96_ArtGuide/の下で7つのドメインに分かれています。
96_ArtGuide/
├── 00_Common/ # 共通 (スタイル・カラーパレット・ライティング基準)
├── 01_Character/
├── 02_Animation/
├── 03_Monster/
├── 04_NPC/
├── 05_VFX/
├── 06_UI/ # ← この章が扱う領域
└── 07_Env/
そして、このフォルダーには運用ファイルが2つ一緒に住んでいます。_convert_md_to_html.pyと_SyncToArtRepo.batです。この2つのファイルが本章の背骨です。
全体の流れは4つの段階です。核心は、人(プランナー)は1段目のmdだけを触り、残りの3段はすべてスクリプトが回すという点です。アートチームは4段目のhtmlだけを見ます。mdの存在自体を知らなくてもかまいません。
flowchart LR
A["1段目 · 企画チームSVN
아트_결정사항.md
(プランナーが作成/AIドラフト)"]
A --> B["2段目 · _convert_md_to_html.py
md → html 変換
(表・ヘッダー・画像埋め込み)"]
B --> C["3段目 · _SyncToArtRepo.bat
アートSVNへ自動push
(別リポジトリ)"]
C --> D["4段目 · アートチームのブラウザー
htmlのみ閲覧
(md記法の学習負担0)"]
D -.フィードバック/修正依頼.-> A
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
class A,D human;
class B,C code;
各段階が正確に何をするのかを押さえます。
1段目(プランナー、人間) — 06_UI/아트_결정사항.mdに決定事項をMarkdownで書きます。ここにAIを組み込む方法が§9.3.4の背骨です。決定事項は「ボタンのprimaryカラーは#3A7BD5」「タッチターゲットは最小44pt」のような項目です。
2段目(_convert_md_to_html.py、自動) — mdをhtmlに変換します。単純な変換ではなく、アートチームが見やすいように表をレンダリングし、の画像参照をインラインで埋め込み、目次を付けます。アートデザイナーがブラウザーでダブルクリック1回で開ける、自己完結のhtmlができあがります。
3段目(_SyncToArtRepo.bat、自動) — 変換されたhtmlをアートチームの別のSVNリポジトリへpushします。企画リポジトリとアートリポジトリが分離されているのが核心です。アートチームは自分のリポジトリだけを見ればよく、企画リポジトリの権限や構造を知る必要はありません。
4段目(アートチーム、人間) — アートデザイナーは、自分のリポジトリに同期されたhtmlをブラウザーで開きます。Markdownの記法も、SVNコマンドも、diffの見方も学ぶ必要はありません。md記法の学習負担が0であることが、このパイプラインの設計目標であり成功基準です。
フィードバックは4段目から1段目へ戻ってきます。アートが「この決定、おかしいです」と言えば、プランナーがmdを直し、2〜3段目が再び自動で回ります。アートは更新されたhtmlを開き直すだけで済みます。
ここで一度立ち止まり、設計意図を明示します。mdをhtmlに変換すること自体は、ささいな作業です。本当の設計は、誰の学習負担を誰が引き受けるかを決めたところにあります。
選択肢は2つありました。
核心は非対称性です。案Aは、学習コストがアートの人数分だけ掛け算され、そのコストが新しいメンバーが入るたびに再発します。案Bは、プランナーが一度スクリプトを書けば終わりで、アート側の限界コストは0です。負担を、人数が多い側ではなく自動化が可能な側に寄せること — これが非企画職との連携ツールの第1原則です。この原則が崩れると、つまり連携ツールが相手の職種に新しい学習を強要すると、そのツールは1〜2四半期のうちに「使われなく」なります。
1段目でプランナーがmdを書くと述べましたが、そのmdドラフトをAIで作る場面を、1サイクル最後までお見せします。決定会議が終わると、散発的なメモ(チャット・ホワイトボードの写真・口頭での合意)が残ります。これを正本の決定事項mdに整理する作業は退屈で、毎回形式がぶれます。AIにぴったりの仕事です。ただし、決定そのものは人間が行い、AIは決定を決められたフォーマットに整理するだけという境界が核心です。
[UI決定会議メモ — 06_UI スキルスロット関連、生データ]
- スキルスロットのボタンサイズを大きくすることになった。モバイルで小さすぎるので。
- 色はアートが決めることに。ただしprimaryトーンは青系を維持。
- スロット非アクティブ(クールダウン)状態の表現はグレー+数字オーバーレイで合意
- 多言語… スキル名が長くなるのはどうする? いったん保留
- あ、それとロングプレスでスキル説明が出るように(これは機能、企画確定)
添付はUI決定会議の生メモだよ。アートチームに渡す決定事項マークダウンに整理して。
各項目を[確定]/[保留]/[アート決定]に分類して(確定=企画が決めアートが従う、保留=未決+理由、アート決定=視覚表現なのでアートが決める・企画は制約のみ)、根拠/制約を一行ずつ付けて — メモに根拠がなければ「根拠未記載」と正直に書き、推測で埋めないで。
タッチサイズのように公開標準で検証できるものは標準を明記して、メモにない項目は絶対に新しくでっち上げないで。
表は|項目|分類|決定/制約|根拠|検証可能かどうか|、frontmatter(title, layer: L1)を含めて全文でちょうだい。
このプロンプトの重心は、整理作業ではなく、分類の強制とでっち上げの禁止に置かれています。AIに決定事項の整理をさせると、メモになかった「ベストプラクティス」を親切に追加してしまいます。その追加分は、アートチームには「企画が確定した決定」として読まれます。だから、[確定]/[保留]/[アート決定]の分類、根拠の義務化、そして「メモになければでっち上げるな」という釘を打ち込みます。
---
title: 06_UI 決定事項 — スキルスロット
layer: L1
---
# 06_UI 決定事項 — スキルスロット
| 項目 | 分類 | 決定/制約 | 根拠 | 検証可能 |
|---|---|---|---|---|
| スキルスロットのボタンサイズ | [確定] | 最小 44×44 pt 以上 | モバイルのタッチターゲット、Apple HIG 44pt推奨 | ✅ 座標・サイズ |
| primaryトーン | [アート決定] | 青系を維持(制約)、正確な値はアート | メモ: 「primaryトーンは青系を維持」 | △ 色の値 |
| クールダウン状態の表現 | [確定] | グレーオーバーレイ + 残り数字 | 会議で合意 | ◯ 視覚検収 |
| スキル名の多言語の長さ | [保留] | 後日の会議 | メモ: 「いったん保留」 — 長い言語(独・露)の検証未実施 | — |
| ロングプレスのスキル説明 | [確定] | ロングプレス → ツールチップ表示 | 機能確定(企画領域) | ◯ 動作検収 |
| スロット間の間隔 | [確定] | 最小 8 dp 推奨 | 根拠未記載 — メモになし、Material推奨値を参考 | ✅ |
この出力をそのまま正本として上げてはいけません。1行、引っかかります。
最後の行、슬롯 간 간격 | 최소 8 dp(スロット間の間隔 | 最小8dp)です。メモを見直すと、会議で間隔の話は一度も出ていません。AIが「モバイルUIだから間隔の基準もあるべきだろう」と判断し、Material Designの推奨値を親切に追加したのです。ルール5(「メモにない項目のでっち上げ禁止」)違反です。AIは근거 미기재(根拠未記載)と正直に書いてはいますが、項目自体を作るべきではありませんでした。この1行がアートチームに渡れば、「企画が8dpの間隔を確定した」と読まれます。
そこで再依頼します。
「スロット間の間隔」は会議メモになく、君が追加したものだよ。表から外して。
メモにはないけど決定が必要そうなものは、表ではなくいちばん下の「## 未決 — 次回会議の議題」に候補としてだけ載せて、決定事項の表にはメモに実際にあった項目だけ残して。
AIは間隔の項目を表から外し、いちばん下に「次回会議の議題:スロット間の間隔基準(現在未定)、多言語スキル名の長さの処理」を候補として分離しました。これで決定事項の表には会議で実際に決めたことだけが残り、AIが思いついた合理的な候補は「確定」ではなく「議題」へ格下げされました。この分離が重要な理由は、アートチームが受け取るドキュメントの中で何が確定で何がまだ議論中なのかが混ざると、アートが未定事項を確定と思い込んで作業を始めてしまうからです。
この1往復で1段目(md)が完成しました。ここからは人の手を離れ、2〜3段目の自動化へ進みます。
完成したmdは、ここからスクリプトが処理します。変換スクリプトの骨格は単純です。
# _convert_md_to_html.py (骨格)
# 入力: 06_UI/*.md (プランナーが書いた決定事項)
# 出力: 同名の .html (アートチームがブラウザーで開く自己完結ファイル)
def convert(md_path):
md_text = read(md_path)
front, body = split_frontmatter(md_text) # title・layer 抽出
html_body = markdown_to_html(body, extensions=[
"tables", # 表レンダリング (アートがメモ帳で見ていた崩れた表を解決)
"fenced_code",
])
html_body = embed_images_inline(html_body, base_dir=md_path.parent)
# ↑  のような参照をインライン埋め込み →
# アートが画像ファイルを別途受け取らなくて済む
toc = build_toc(html_body) # 目次の自動生成
return render_template(title=front["title"], toc=toc, body=html_body)
ここで、変換が単純なmd→htmlではないという点が核心です。3つのことを追加で行います。表を正しくレンダリングし(アートがメモ帳で見ていた崩れた|---|が消えます)、画像をインラインで埋め込み(アートが画像ファイルを別途受け取る必要がなくなります)、目次を自動で付けます(決定事項が長くなっても、アートは目的の項目へジャンプできます)。この3つが「htmlだけ見ればいい」を実際に成立させます。
受け渡しスクリプトは、こうまとめられます。
REM _SyncToArtRepo.bat (骨格)
REM 1) 06_UI のすべての md を html に変換
python _convert_md_to_html.py 06_UI\*.md
REM 2) 変換された html をアート SVN の作業コピーへコピー
xcopy 06_UI\*.html %ART_REPO%\UI\ /Y
REM 3) アート SVN に自動コミット・push (別リポジトリ)
svn add %ART_REPO%\UI\*.html --force
svn commit %ART_REPO%\UI -m "[auto] 06_UI 決定事項更新"
プランナーがやることは、_SyncToArtRepo.batのダブルクリック1回です(あるいは決定事項のコミット時に自動実行されるよう、フックを掛けます)。これで変換・コピー・アートリポジトリへのpushが一度に回ります。アートチームは、自分のリポジトリを更新すれば最新のhtmlが届いています。
AIはどこまで入るのか — この2〜3段目の自動化コードは、AIに書かせてもかまいません。「mdフォルダーを受け取り、表・画像込みのhtmlに変換して別のSVNへpushするスクリプトを書いて」は、AIが得意とする領域です。しかし、どの決定を確定とするか、何をアート決定として渡すか(§9.3.4)はAIに委ねません。コードはAI、決定は人間 — 本書全体で繰り返される分担が、ここでもそのまま当てはまります。
アートとの連携でAIが誤って使われる代表例が、画像プロンプトです。プランナーがアートチームにリファレンスを渡すとき、あるいはコンセプトを素早く可視化するときに、画像生成AIを使います。このときよくある間違いは、結果の描写(「青い丸ボタン、グロー効果、4K」)から書くことです。
著者の連携原則の1つがimage_prompt_design_intent_first — 画像プロンプトも、結果の描写ではなく設計意図を先に書くというものです。
| 方式 | プロンプト | 問題/効果 |
|---|---|---|
| 結果優先(悪い) | 「青い丸ボタン、グロー、4K、ゲームUI」 | アートが「なぜ青?」を問えない。意図が蒸発する |
| 意図優先(良い) | 「クールタイム(クールダウン)中かどうかを直感的に伝えるスキルボタン。アクティブ=すぐ押したくなる視覚的な引力、クールタイム中=抑制。トーンはprimaryの青系」 | アートが意図を見て、より良いビジュアル案を逆提案できる |
違いは、アートチームがプロンプトを受け取ったときに何ができるかです。結果の描写だけを受け取った場合、アートはそのまま描くか無視するかの2択です。設計意図を受け取れば、アートはその意図をより良く解く、自分のビジュアル案を提案できます。 これが、プランナーがアートの決定領域(§9.3.4の[アート決定])を侵さずに方向を示す方法です。プランナーは「何のために」を渡し、アートは「どう見せるか」を決めます。
だから§9.3.4の決定事項mdに画像リファレンスを入れるときも、キャプションを「青いボタン」ではなく「クールタイム状態の区別が目的のスロット — 正確な表現はアート決定」と書きます。変換スクリプトがこのキャプションごとhtmlに埋め込むので、アートは画像と意図を一緒に受け取ります。
このパイプラインの効果を、「連携事故が70%減った」のような数字で書きたい誘惑があります。そうした数値は、検証されていなければ本の信頼を削ります。正直に区分します。
公開標準で検証可能なもの — 決定事項に載るタッチ44pt・間隔8dp・コントラスト4.5:1のような公開標準は、§9.1のルールブックに従います。でっち上げた数値ではなく、そのまま引用してlintで自動検証できる値です。
測定可能な運用指標 — このパイプラインで実際に数えられるのは、次のようなものです。アートが旧バージョンで作業した事故の件数(受け渡しが自動なら0に収束します)、アートチームの新メンバーが決定事項を初めて開いて見るまでにかかる時間(htmlのダブルクリックなら分単位です)、決定の変更がアートリポジトリに反映されるまでの遅延(スクリプトの実行時間です)。この3つは「感覚」ではなく、ログと観測で数えられます。
著者の推定(未検証の仮説) — 「手動のメール受け渡しのときより漏れが減った」という方向は明らかですが、正確な減少率はサンプルを別途記録していないため、断定しません。絶対値ではなく方向で読んでください。受け渡しが人の手に懸かっていれば忙しい週には必ず漏れが出ますし、受け渡しがスクリプトなら漏れは構造的に消えます。
一人ならこれだけ:アートチームもSVNもなくてかまいません。自分が依頼する外注アート、あるいは一緒に作っている友人にUIの決定を伝える場面を想像してみましょう。§9.3.4のプロンプトをそのまま使って、頭の中の散発的なUI決定を[確定]/[保留]/[アート決定]に分類したmdを1枚、AIで作ってみましょう。その中からAIが「親切に追加した」項目(メモになかったもの)を1つ見つけて、「これは私が決めた覚えがない、外して」と反論してみると、決定の整理における人間とAIの境界がどこにあるのか、体に入ってきます。変換は
markdownパッケージでpython -m markdown decision.md > decision.htmlの1行で十分です。
チームなら、次の一歩から始めましょう。大げさな双方向同期から作り始めるのではなく、変換1行+受け渡し1行から入れます。決定事項mdをhtmlに変換するスクリプト(§9.3.5の表レンダリング・画像埋め込みだけ)と、そのhtmlをアートが見る場所(共有ドライブでも別リポジトリでも)へコピーする1行です。この2行があるだけで、「アートがmdをメモ帳で開いて崩れた表に出会う」という最もよくある事故が消えます。分担表や決定権の整理は、その次です。
setup → prompt → verifyに要約すると、次のとおりです。
| ステップ | やること |
|---|---|
| setup | _convert_md_to_html.py(変換)+受け渡し1行(コピー/push)を先に入れます |
| prompt | §9.3.4のプロンプトで会議メモを[確定]/[保留]/[アート決定]のmdに整理します |
| verify | AIがでっち上げた項目(メモにないもの)を拒否 → 変換・受け渡しを自動実行 → アートはhtmlだけを確認します |
金曜の夕方6時40分。翌週月曜の社内ビルドに、クエスト12種を新しく入れると決めていた日でした。私はquest_tableに新しい行を追加し、報酬シートに対応する行を埋め、ダイアログシートにNPCのセリフを紐付けました。3枚のシート、約50行。目で2回見直して、問題なさそうに見えました。
月曜の朝、ビルドが壊れました。新しいクエストのうち1件が参照するreward_idが、報酬シートに存在しなかったのです。金曜の夕方に報酬行を1つ削除して追加し直したとき、idを1文字打ち間違えていました。rwd_q318をrwd_q381と。人間の目では絶対に捕まえられない類のタイポです。2つのシートは別のフォルダーにあり、別の人が、別の時間に触ります。行が50個のうちは目で捕まえられます。しかし30を超えるシートが互いを外部キー(FK)で参照し始めると、人間の目はもはや検査ツールではありません。
本章では、そのタイポをビルドが壊れる前に捕まえるチェックatomの一種 — integrity_check_fk — が、30を超えるシートのFK整合をチェックし、破損したときにコラボレーションツール(タスクや日程を管理するSaaS — 本プロジェクトではClickUpを使っており、JIRAやRedmineも同じ位置づけです)で担当者へ通知する流れを、私が実際に回した1つのセッションをたどりながらお見せします。
他人が作ったものの中からずれた1行を見つけ出す仕事で、私はこの業界に入りました。シングルプレイゲームのQA・チェックが最初の仕事で、当時は手と目が唯一の検査ツールでした。20年余りが過ぎた今は、同じ仕事をコードに任せています — 人間の目が検査ツールであることをやめた場所で。
まず、何をチェックするのかを図で見ます。ゲームのマスターデータ(データシート群)はリレーショナルデータベースと同じです。あるシートの列が、別のシートの主キーを指します。この矢印が切れると、ランタイムでゲームが落ちるか、さらに悪い場合は黙って空の値を表示します。
緑の実線は生きている参照です。quest_table.reward_idが指す値が、reward_table.reward_idに実際に存在します。赤の点線は死んだ参照です — あるクエストのnpc_idが、npc_tableに存在しないidを指しています。integrity_check_fkが捕まえるのが、この赤い点線です。
プロジェクトAのマスターデータには、こうした参照が1つや2つではありません。30を超えるシートが互いを指し合い、FKの矢印をすべて描くと1ページに収まりません。人が毎回手でたどるのは不可能です。
ここからは、私が実際に回したセッションです。要約せず、プロンプト全文と生の出力、そして私が拒否して再依頼した部分まで、そのまま記します。
まずチェッカーに、「どの列がどのシートを指すのか」というFKマップから作らせました。30シートのFKを人が1つずつ登録するのは非現実的なので、仕様書シートの列命名規則(*_idは同名のシートを指す)から候補を自動推論させました。私が入力したプロンプト全文は次のとおりです。
添付した仕様書シート(
$스키마タブ)を読み、すべてのデータシートから外部キー(FK)と思われる列を推論してください。ルールは次のとおり。(1)列名がxxx_idの形式で、(2)xxx_tableまたはxxx_listシートに同名のPK(Primary Key、主キー)列が存在すれば、FK候補とみなします。推論結果を{source_sheet, source_column, target_sheet, target_column, confidence}のリストとしてJSON出力。ルールで断定できないものはconfidenceをlowにして、理由を書いてください。
Claudeの生の出力の一部です(曖昧な項目も含めてそのまま転記します)。
[
{"source_sheet":"quest_table","source_column":"reward_id",
"target_sheet":"reward_table","target_column":"reward_id","confidence":"high"},
{"source_sheet":"quest_table","source_column":"npc_id",
"target_sheet":"npc_table","target_column":"npc_id","confidence":"high"},
{"source_sheet":"quest_table","source_column":"prev_quest_id",
"target_sheet":"quest_table","target_column":"quest_id","confidence":"medium",
"note":"名前がquest_idではなくprev_quest_idのため規則に掛からない。同一シートの自己参照と推定。人の確認が必要"},
{"source_sheet":"reward_table","source_column":"item_id",
"target_sheet":"item_table","target_column":"item_id","confidence":"high"},
{"source_sheet":"dialogue_table","source_column":"speaker_id",
"target_sheet":"npc_table","target_column":"npc_id","confidence":"low",
"note":"speaker_idがnpc_idを指すのかplayerを指すのか不明。speakerが'player'の行もあるように見える。断定不可"}
]
ここで、2つの項目をそのまま通しませんでした。prev_quest_id(自己参照)はチェック対象として採用しつつ、「空の値(最初のクエスト)は違反ではない」という例外を私が直接付けました。dialogue_table.speaker_idは拒否しました — speakerが'player'の行が実際にあり、これをFKとしてチェックすると、プレイヤーのセリフがすべて誤検出(偽の違反)として挙がってしまうためです。
この拒否が重要です。自動推論したFKマップを人のレビューなしにそのまま使うと、初回実行で数百件の誤検出があふれます。人が一度ふるいにかけてこそ、チェックは信頼を得ます。
ふるいにかけたFKマップを、integrity_check_fk atomの入力として固定しました。atomの様式は次のとおりです。これはプロジェクトAで実際に使っているチェックatom 1個の全文です。
---
name: integrity_check_fk
description: 登録されたFKマップに従い、すべてのsourceカラム値がtargetシートのPKに存在することを検証
type: integrity_check
category: data
priority: P0 # 破損FKはビルドブロック
execution_time:
- on_save # シート保存時は該当シートのみ
- on_build # ビルド時は全FK
- nightly # 毎日0時に全体 + レポート
input:
fk_map: fk_map.reviewed.json # ステップ1~2で人がレビューしたマップ
output_format: violation_list
on_violation:
- notify: clickup # 失敗時はClickUpに通知
related_atoms:
- integrity_check_clickup_notify
- integrity_check_id_uniqueness
---
チェックのロジック自体は長くありません。sourceシートの各値が、targetシートのPK集合に存在するかを確認する集合メンバーシップ検査です。
def check_fk(fk_map, sheets):
violations = []
for fk in fk_map:
pk_set = {r[fk["target_column"]] for r in sheets[fk["target_sheet"]]}
for i, row in enumerate(sheets[fk["source_sheet"]]):
val = row[fk["source_column"]]
if val in ("", None): # 空のFKは例外 (ステップ1で決めた規則)
continue
if val not in pk_set:
violations.append({
"fk": f'{fk["source_sheet"]}.{fk["source_column"]}',
"row": i + 2, # ヘッダー1行 + 1-index
"value": val,
"target": fk["target_sheet"],
"severity": fk.get("severity", "P0"),
})
return violations
レビュー済みのマップで、30シート全体にチェックを回しました。出力は標準のviolation_listです。次が、その日に実際に出た結果です(id・シート名は匿名化していますが、違反件数と構造は実物です)。
{
"check": "integrity_check_fk",
"executed_at": "2026-05-18 09:14:02",
"input_files": 31,
"violations": [
{"fk": "quest_table.reward_id", "row": 318, "value": "rwd_q381",
"target": "reward_table", "severity": "P0",
"message": "reward_id 'rwd_q381'がreward_tableに存在しない。'rwd_q318'のタイポと推定"},
{"fk": "quest_table.prev_quest_id", "row": 502, "value": "q_0500",
"target": "quest_table", "severity": "P0",
"message": "prev_quest_id 'q_0500'がquest_tableに存在しない。'q_500'の表記不一致(0パディング)と推定"}
],
"summary": {"fk_checked": 23, "rows_scanned": 4117, "violations": 2, "passed": 4115}
}
金曜の夕方のあのタイポ(rwd_q381)が、最初の行で捕まりました。2件目は、私が知らなかった別の問題でした。あるクエストのprev_quest_idがq_0500なのに、実際のクエストidはq_500だったのです。0パディングが入った表記の不一致。人間の目には同じに見えますが、文字列としては別の値で、ゲームは先行クエストを見つけられず、そのクエストをロック状態のままにします。リリースされていたら、プレイヤーからの問い合わせが届いていた類の欠陥です。
messageフィールドの「오타로 추정(タイポと推定)」「0 패딩 추정(0パディングと推定)」は、チェッカーに、単純なメンバーシップ失敗にとどまらず最も近いPK値(編集距離基準)を併せて提示させた部分です。人が「なぜ壊れたのか」を追跡する時間を減らします。ただし、この推定はあくまでヒントで、実際の修正値は人が決めます。
ここまでが、チェック1個の動作です。しかし、チェックが違反を捕まえても、誰も見なければ意味がありません。核心は、違反がまっすぐ担当者へ届く流れです。プロジェクトAでは、この流れをintegrity_check_clickup_notifyという別のatomが担当します(JITメタデータ上のインパクトスコアは294.93で、検証atom群の中で最も高く評価されたatomの1つです — 整合性の失敗を人に届けることが、チェックそのものと同じくらい重要だという意味です)。
全体のcascadeは次のとおりです。チェックatomが順に実行され、どこかの段階でP0違反が出ると、通知atomへ流れます。
flowchart TD
A[シート保存 / ビルドトリガー] --> B[integrity_check_id_uniqueness
PK重複チェック]
B -->|重複ありP0| F[ビルドブロック]
B -->|通過| C[integrity_check_fk
FKマップに基づく整合チェック]
C -->|破損FK 0件| D[integrity_check_range
報酬・数値範囲チェック]
C -->|破損FKありP0| E[integrity_check_clickup_notify]
D -->|範囲違反P1| E
D -->|通過| G[チェックPASS · ビルド続行]
E --> H{severity?}
H -->|P0| I[コラボレーションツールにタスク作成
+ 担当者メンション + ビルドブロック]
H -->|P1| J[コラボレーションツールにコメント + alert
ビルドは続行]
I --> F
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class A data;
class B,C,D,E,H,I,J code;
class G pass;
class F fail;
このcascadeには、2つの設計上の決定が入っています。
第一に、PK重複チェックがFKチェックより先です。FKチェックは、targetシートのPKがユニークであることを前提とします。PKが重複していると、「この値はPK集合にあるか」という問い自体が無意味になります。そこでintegrity_check_fkのatomにrelated_atoms: integrity_check_id_uniquenessを明記し、cascade内の順序を固定しました。依存するチェックが失敗したら、FKチェックはスキップします — 回しても偽の結果しか出ないためです。
第二に、通知の強度はseverityで分かれます。P0(壊れたFK)はコラボレーションツールにタスクを作り、FKマップに登録された担当者(reward_tableなら報酬担当)をメンションし、ビルドをブロックします。P1(報酬数値が推奨範囲を外れている — 間違いというより検討が必要なケース)はコメントとalertだけを残し、ビルドは通します。すべての違反をビルドブロックにすると、人々はやがて、ビルドブロックを無視する方法を学びます。ブロックは、本当に止めるべきものにだけ使います。
コラボレーションツールに実際に作成されるタスクの本文は、violation_listの1項目がそのまま変換された形です。
[P0] integrity_check_fk 違反 — ビルドブロック済み
シート: quest_table | カラム: reward_id | 行: 318
値 'rwd_q381'がreward_tableに存在しません。
最も近い候補: 'rwd_q318' (編集距離 1)
担当: @報酬_担当 | 検出: 2026-05-18 09:14 | ビルド: nightly-0042
チェック結果が人の受信トレイに届くまで、手は一度も入りません。チェック → 分類 → タスク作成 → メンションが、1本のパイプラインです。これが可能なのは、violation_listが標準の出力様式だからです。どのチェックatomが捕まえたものでも出力構造が同じなので、通知atom 1つがすべてのチェック結果を受けて処理します。
チェックを最初に有効化すると、誤検出が必ず出ます。ステップ1のspeaker_idがその例です。これを放置すると、人々は違反レポートを「どうせ大半が誤検出だから見なくていい」と学習します — チェッカーの信頼が崩れる、最もよくある経路です。
プロジェクトAでは、human_review_attestation_evidence_mandatoryという原則でこれを防ぎます。誤検出と判定して例外処理するとき、誰が・いつ・なぜそう判断したのかを証跡として残さなければなりません。FKマップファイル(fk_map.reviewed.json)の例外項目ごとに、次が付きます。
{
"source_sheet": "dialogue_table", "source_column": "speaker_id",
"excluded": true,
"review": {
"by": "이민수", "at": "2026-05-18",
"reason": "speaker_idはnpc_idまたは'player'リテラルを持つ。FK単一検査には不適合。",
"follow_up": "speaker_typeカラム追加後、分岐検査として再導入を検討"
}
}
この証跡がないと、しばらく経って「この列はなぜチェックしていないのか」という疑問が再び浮かんだとき、答える根拠がありません。すると再びチェックに入れて、再び数百件の誤検出を見ることになります。レビューの証跡は、同じ議論を繰り返させません。
自分のデータシートにFKチェックを導入したい読者のための、最小限の手順です。
setup. データシートのフォルダーと、列の仕様(どの列がPKで、どれがFKか)を1か所に集めます。仕様がなければ、列名の規則(*_id)だけでも始められます。
prompt. 次をチェッカーに入力してみましょう。
このデータシート群からFK候補を推論してください。
xxx_id列がxxx_tableの同名PKを指すなら、FKとみなします。結果を{source_sheet, source_column, target_sheet, target_column, confidence}のJSONで出力し、ルールで断定できないものはconfidenceをlowにして、理由を書いてください。
verify. 出力されたFKマップは、必ず人が1行ずつレビューしてください。自己参照(prev_*)、リテラルの混在('player'のような)、多態参照(状況によって別のシートを指す列)は、自動推論がよく間違えます。ふるいにかけたマップでチェックを回し、初回実行で出た違反を1件ずつ「本当の破損 / 誤検出」に分類しましょう。誤検出は例外処理しつつ、理由をファイルに残します。
この3つのステップを経れば、金曜の夕方の1文字のタイポが月曜のビルドを壊すことはなくなります。チェックが土曜未明のnightlyでそのタイポを捕まえ、月曜の出勤前には、コラボレーションツールのタスクが1件、担当者を待っています。
一人ミニ版。 一人で作業していてコラボレーションツールがなくても、このチェックには意味があります。FKマップを手で10行ほど書き、上のPython関数を1つ回すだけでも、壊れた参照は捕まります。通知はコンソール出力やテキストファイルで十分です。核心は通知チャネルではなく、「人間の目では捕まえられない参照エラーを、機械が捕まえて人に届ける」という流れそのものです。
夜11時、nightlyジョブがコラボレーションツールにカードを1枚差し込みました。タイトルは[integrity] D17 부합 측정 미수행 7일 경과(D17の合致測定が未実施のまま7日経過)。適用から1週間経った決定が「本当に意図どおり動いたのか」を誰も確認しないままビルドに残っている、という通知でした。データは無事でした。シートの形式も、FKも、enumも通過していました。それでも、決定は検証されていなかったのです。
このギャップが本章の出発点です。データに欠陥がなくても決定は間違いうるのであり、その間違いを捕まえる場所は、データチェックとは別のところにあります。その場所を3つの層に分け、各層でAIがどこまで補助し、人がどこでハンコを押すのかを明示すること — それが決定検証3-layerセンサーです。
checkカスケードは4種類のチェックを一度に回します — doc-audit(文書の一貫性)、data-qa(データ品質)、integrity(完全性)、link(相互参照の切れ)です。この4つが通過すれば「データは無事」という意味です。しかし、無事なデータの上に間違った決定が載ることがあります。
報酬のマスターデータが形式上完璧でも、その数値がインフレを引き起こすなら。FKがユニークでも、2つのクエストが同じ時刻に同じNPCを占有するなら。voiceが一貫していても、2人のキャラクターの関係設定が矛盾するなら — データチェックはすべて通過し、決定はすべて間違っています。データチェックは「欄が埋まっているか」を見ますが、決定チェックは「その値がほかの決定・ほかのデータ・実際のユーザーと噛み合っているか」を見ます。会計にたとえるなら、前者は伝票の様式チェックで、後者は財務諸表の整合性監査です。
だから決定検証には、データ検証とは分離されたセンサーを置きます。1つのチェックに束ねると「通過/失敗」の1行に押しつぶされ、失敗したときにデータの問題なのか決定の問題なのか、解釈が曖昧になります。分離すれば責任が明確になります。
3-layerセンサーの核心は、検証の次元を3つに分けた点です。各層は、見る対象も、動作する時点も、AIと人の役割分担も異なります。
flowchart TD
D["決定D17
defense_factor 1000 → 1500"] --> L1
subgraph L1["Layer 1 · 決定 ↔ 決定"]
L1a["AI: scopeの重なり検出 + 矛盾の一次判定"]
L1b["人: 「矛盾」判定のみレビュー + ハンコ"]
L1a --> L1b
end
subgraph L2["Layer 2 · 決定 ↔ データ"]
L2a["AI: 影響データ抽出 + シミュレーション + ±%ルール"]
L2b["人: 意図と測定の乖離を解釈"]
L2a --> L2b
end
subgraph L3["Layer 3 · 決定 ↔ ユーザー"]
L3a["AI: フィードバック分類 + 感情分析"]
L3b["人: サンプル100件の照合 + 最終合致宣言"]
L3a --> L3b
end
L1 --> L2 --> L3 --> CARD
CARD["決定カードD17
3-layer検証結果 + レビュー証拠"]
CARD --> ATT["human_review_attestation
レビュー担当者・時刻・証拠の添付を強制"]
style L1 fill:#e8f0ff,stroke:#4a72c0
style L2 fill:#e8f7ed,stroke:#3a9a5a
style L3 fill:#fff3e0,stroke:#d08a2a
style ATT fill:#fde8e8,stroke:#c04a4a
3つの層すべてで、最後のハンコは人が押します — そのハンコこそが、human_review_attestation_evidence_mandatory atomが強制する証拠です。各層が何を見て、誰がどこでハンコを押すのかは、以下で順に見ていきます。
新しい決定が既存の決定とぶつからないかをチェックする層です。決定ペアは決定数の2乗で増えるため、決定が200個なら約2万ペアになります。人が手作業で全部を見ることはできません。そこでAIが一次フィルターを回します。
# decision_conflict_check.py — Layer 1 センサー
def check_new_decision(new_decision, existing_decisions):
conflicts = []
for existing in existing_decisions:
if has_overlap(new_decision.scope, existing.scope): # 機械的1次: scopeの積集合
verdict = llm_judge(new_decision, existing) # AI 2次: 矛盾/補完/無関係
if verdict.label == "矛盾":
conflicts.append({
"with": existing.id,
"label": verdict.label,
"reason": verdict.reason,
"needs_human_review": True, # 人によるレビューのフラグ
})
return conflicts
has_overlapは機械的な一次フィルターです。scopeはatom領域(combat・narrative・economyなど)なので、積集合の計算は正確です。重ならないペアは即座に捨てます。2万ペアはたいてい数十ペアまで減ります。その数十ペアだけをAIが見ます。
ここでは、実際に何がやり取りされるのかを最後まで見せます。要約せず、曖昧な出力もそのまま残します。
① 人が送ったプロンプト(全文)
次の2つの決定が矛盾するか判定してください。
[決定A] (2026-03-XX) 戦闘のグローバルクールダウンを0.5秒に全スキル統一
[決定B] (2026-04-XX) 回復系スキルはグローバルクールダウンの適用外とする
判定形式(この形式のみ、他の言葉は禁止):
- label: 矛盾 | 補完 | 無関係
- reason: 1行
- confidence: 0.0~1.0
推測しないでください。根拠が弱ければconfidenceを下げてください。
(プロンプトの内容: 「戦闘のGCDを0.5秒に全スキル統一」という決定Aと、「回復系スキルはGCDの適用外」という決定Bが矛盾するかを、label(矛盾|補完|無関係)・reason 1行・confidence 0.0〜1.0の固定形式だけで判定させ、根拠が弱ければconfidenceを下げるよう指示しています。)
② Claudeの生の出力(加工なし)
- label: 補完
- reason: 決定Bは、決定Aが定めた0.5秒統一ルールに回復系という明示的な例外を追加する構造であり、ルールを覆さずに狭めている。
- confidence: 0.74
③ 人による検証/拒否
判定は「補完」、confidenceは0.74です。ルール上「補完・無関係」は自動通過ですが、confidenceが0.8未満なら人が1行でも確認することになっています。レビュー担当のチームメンバーAが決定Bの原文を読み直し、ひとつの事実を発見しました — 決定Bには「ただし、回復スキルも固有のクールタイム(クールダウン)1.2秒を持つ」というただし書きが付いていたのです。AIは要約された1行だけを受け取っており、このただし書きを入力として受け取っていませんでした。チームメンバーAはAIの判定を受け入れつつ補強として処理し、「AIが見た入力は不完全だった」と記録に残しました。
④ 再依頼(入力補強後)
[追加の手がかり] 決定Bには「回復スキルはグローバルクールダウンは免除するが、固有クールダウン1.2秒を
持つ」という条件が付いています。この手がかりを含めて再判定してください。
- label: 補完
- reason: 固有クールダウン1.2秒がグローバルの0.5秒より長く、回復スキルは免除されても
むしろより長い間隔を持つ。Aの意図(連打防止)と衝突しないため補完が強化される。
- confidence: 0.91
判定は「補完」のままですが、根拠が固くなり(固有クールタイム1.2秒はグローバルの0.5秒より長いため、回復スキルは免除されてもむしろ間隔が長くなり、決定Aの意図である連打防止と衝突しない)、confidenceは0.74→0.91に上がりました。チームメンバーAはここでハンコを押しました。核心は結果ではなく過程の記録です — AIの一次判定、人が発見した入力の欠落、補強しての再依頼、最終レビュー。この4つの段階が、そのまま決定カードのLayer 1証拠欄に入力されます。
このトランスクリプトの原則はひとつです。AIの「補完・無関係」判定も無条件には通過させない。 AIが間違っていたのではなく、AIが受け取った情報が不完全だったのであり、それを発見できるのは決定の原文を知っている人です。
チェックの時点は3か所です。新規決定の追加時に即時+alert、pending atomの昇格時にチェックしてから昇格、nightlyで全ペアを再チェックします。
決定がデータにどう反映され、意図と合致しているかを測定する層です。もっとも自動化しやすく、もっとも正確です。シミュレーターとマスターデータがすでにあるなら、検証ルールを載せるだけで済みます。
決定D17(defense_factor 1000→1500)を例にとると、センサーが自動でCombatBalanceシートと自動シミュレーションの結果、影響を受けたキャラクターデータを引き出し、意図(タンクの生存+49%)と測定(シミュレーション+52%)を比較します。合致判定のルールは定量的です。
| 意図に対する測定の差 | 処理 | 誰が |
|---|---|---|
| ±10%以内 | 合致(自動通過) | AI |
| ±10〜25% | alert・再検討 | 人が解釈 |
| ±25%超 | 違反・決定の再検討義務 | 人が決定 |
ここでの人の役割は「AIが合致と言ったから通過」ではありません。alert区間と違反区間を解釈することが人の仕事です。D17のシミュレーションは+52%で±10%以内に収まり自動合致でしたが、同じシミュレーションが副次効果をひとつ吐き出しました — ハイブリッドキャラクターK_021が意図の外で+28%強くなったのです。D17の直接の意図ではないため、合致ルールには引っかかりません。ルール上は通過なのに人の目には事故 — この区間を捕まえることが、Layer 2に人が存在する理由です。
この層の自動化率は約95%でもっとも高い水準です。それでも5%が残るのは、まさにこの解釈のためです。数字がルールを通過することと、その数字がゲームにとって正しいことは、別の問いです。
3つの層のうち、もっとも難しい層です。決定が実際のユーザーに意図どおり作用したかを見ます。ビルドリリースの1〜2週間後の実測指標(タンクの平均生存時間、タンクを含む5:5 PvPの勝率)と、自然言語のフィードバック(フォーラム・SNS)を入力として受け取ります。
自然言語のフィードバックが検証の入力になる — これがこの層の特徴です。フォーラム約200件、SNS約1,500件をAIがカテゴリーに分類し、感情を付けます。
[AIフィードバック分類 — タンク関連の1週間収集分]
肯定 62% 否定 23% (「タンクが強くなりすぎ」が多数) 無関係 15%
(分類結果の内訳: タンク関連の1週間収集分で、肯定62%・否定23%(「タンクが強くなりすぎ」が多数)・無関係15%。)
ここで止まると罠にはまります。AIの感情分類は、韓国語と英語が混ざると精度が落ちます(「탱커 강해졌다 ㅋㅋ(タンク強くなったww)」が肯定なのか皮肉なのか、判定が揺れます)。そこで運用ルールとして、四半期ごとに人がサンプル100件を直接分類し、AIの結果と照合します。 照合で誤差がしきい値以上なら、その四半期の分類は信頼せず、人が全数を再分類します。
最終的な合致宣言は人が行います。D17の場合、実測+44%(シミュレーション予測+52%、誤差8% — 正常範囲)で、フィードバックは肯定優勢でした。AIは「肯定優勢+意図の範囲内」という入力を整理して上げましたが、合致だとハンコを押したのは人です。自動化率は約70%、人が30%です。この層だけは、完全自動が原理的に不可能です。ユーザーの言葉の意味を、機械は最後まで判定できないからです。
3つの層の最後のハンコを人が押すのなら、そのハンコが実際に押されたという証拠がなければ、システム全体が崩れます。レビューしたと口で言うだけで実際にはしていないケースを、どう防ぐのか。プロジェクトAでは、atom human_review_attestation_evidence_mandatoryがこれを強制します。
このatomのルールは単純で、妥協がありません。決定カードのどの層であれ「AI判定 → 人によるレビュー」が起きたなら、レビュー担当者の識別・レビュー時刻・レビュー証拠(補強メモ、拒否理由、サンプル照合結果のうち最低1つ)がカードに添付されなければならない。証拠が空なら、そのカードは「検証完了」に昇格できない。
証拠が空なら、integrity_check_clickup_notify atomが作動します。整合性の失敗 — ここでは「レビューのハンコはあるのに証拠がない」 — を検知すると、コラボレーションツールに即座にカードを作ります。本章冒頭の夜11時のカードが、まさにこのメカニズムです。
この2つのatomがペアになって「検証の検証」を作ります。3-layerセンサーが決定を検証し、attestation atomがその検証を人が実際に行ったかを検証し、notify atomが証拠の欠落を捕まえて通知します。AIの補助がどれほど広範囲でも、責任の最後の1マスは、証拠を残した人の名前で埋められます。
3つの層の結果とレビュー証拠が集まる単位が決定カードです。カード1枚が決定1件の完結単位であり、四半期の振り返りの入力へと流れていきます。以下はD17カードの構造です。
赤い行が核心です。各層の「증거:」(証拠)行が空なら、attestation atomがカードの昇格を止め、notify atomがコラボレーションツールに知らせます。6か月後に誰かが「なぜdefense_factorを1500にしたのか」と尋ねたら、このカード1枚が意図・測定・実測・レビュー担当者まで全部答えてくれます。決定カードは、第18部の意思決定追跡atomと同じメタデータの流れの上で動作します。
3つの層は自動化の度合いが異なります(それぞれ約80%・95%・70%、前の節で見たとおりです)。3つとも部分自動であり、最後のハンコは3つとも人が押しますが、人の作業量は全体として80%以上減ります。
導入はLayer 2からです。シミュレーションとマスターデータがすでにあるなら、検証ルールを追加するだけで済むため、1〜2か月で効果が出ます。次にLayer 1(インフラは少ないのに効果が大きい、追加1か月)、最後にLayer 3(インフラがもっとも大きく効果も大きい、追加2〜3か月)です。Layer 3を最初から付けようとして座礁するのが、よくある失敗です。
数値表記について: 上の自動化率と下の効果比率は、著者のプロジェクトの運用観察に基づく著者の推定(未検証)です。精密な測定値ではなく、方向とおおよその比率として読んでください。合致ルールの±10%/±25%のしきい値は実際の運用ルールであり、atom名(
integrity_check_clickup_notify、human_review_attestation_evidence_mandatory)は実在のatomです。
導入前後の変化を方向として整理するとこうなります。四半期あたりの決定矛盾事故は数件からほぼ0件に、決定後1週間の合致測定の実施率は一部から大半に、事故発生前の副作用発見率は半分未満から大半に上がりました。もっとも意味のある変化はトレーサビリティです — かなり時間が経ったあとでも決定の背景をたどれる比率が、少数からほぼすべてに変わりました。決定カードが、ゲームの決定の歴史を保存するからです。
| パターン | 処方 |
|---|---|
| Layer 1だけ運用する(矛盾チェックのみ) | Layer 2・3を追加して次元を埋める |
| Layer 3を最初から導入する | Layer 2から、インフラが小さい順に |
| AIの「補完・無関係」判定を無批判に受け入れる | confidenceのしきい値+人によるサンプルレビュー |
| レビューのハンコだけ押して証拠を添付しない | attestation atomが昇格を遮断する |
| 証拠欠落の通知を無視する | notify atomのコラボレーションツールカードを未完了として扱う |
| ユーザーフィードバックのAI分類を盲信する | 四半期ごとにサンプル100件を人が照合する |
setup. 決定ログを1つのファイルに集めてみましょう(決定id・scope・意図・適用日)。scopeはcombat・narrative・economyのようにenumで固定します。シミュレーションがなければ、Layer 2は「関連するマスターデータの手動比較」から始めても構いません。
prompt. 新しい決定が生まれるたびに、既存の決定と1ペアずつAIに尋ねてみましょう。形式は固定します(プロンプトの内容: 2つの決定が矛盾するかを、label(矛盾|補完|無関係)・reason 1行・confidence 0.0〜1.0だけで出力させ、推測を禁じ、根拠が弱ければconfidenceを下げさせる指示です)。
次の2つの決定が矛盾するか判定してください。
[決定A] ...
[決定B] ...
形式のみ出力: label(矛盾|補完|無関係) / reason 1行 / confidence 0.0~1.0
推測禁止。根拠が弱ければconfidenceを下げる。
verify. 「矛盾」判定とconfidence 0.8未満の判定は、人が決定の原文を読み直して確認しましょう。確認したら、決定カードにレビュー担当者の名前・時刻・メモ(補強/拒否/照合のいずれか)を必ず残します。証拠欄が空なら、そのカードを「検証完了」に上げてはいけません — この1行がattestation atomの一人バージョンです。一人運用でも、6か月後の自分のために証拠を残しましょう。
月曜の朝9時12分。アルファビルドが上がったばかりの週の、最初の検査カスケードが終わりました。checkが4種(doc-audit・data-qa・integrity・link、10.2)を一度に回して止まったとき、コンソールに表示された数字はこうでした。違反候補47件。そのうちP0が何件で、何から見るべきで、誰が手を入れるべきなのかは、その47行のどこにも書かれていませんでした。
チェッカーは「間違っている」という事実しか知りません。「これはリリースを止めるのか、来週見ても良いのか」は判断できません。アルファ終盤の本当のボトルネックは、チェッカーが足りないことではなく、チェッカーが吐き出した47行を人が分類しているうちに午前が丸ごと消えてしまうことにありました。本章はその47行をLLMが自然言語で分類し、人がその分類を受けて優先順位を付ける一回のワークド・サイクルを、丸ごと書き写します。
10.1で検証atomを30種余り作り、10.2で決定を3-layerセンサーでふるいにかける構造を立てました。この2つの章が作り出したのはログです。ログは決定ではありません。ログと決定の間には、人が手作業で埋めていたギャップがあります。
アルファ終盤にこのギャップが高くつく理由は単純です。チェッカーは1時間に数十回回りますが、人が47行を読んで「q_142は行き止まりだからリリースブロック、voice_lint 412はライターの判定待ち」と分類する作業は、毎回ゼロからやり直しになります。その分類労働を自然言語モデルに渡すことが、Gap Reportの出発点です。
以下はその月曜の朝、検査カスケードの生ログをClaudeにそのまま貼り付けて分類を依頼した、実際のセッションです。要約せずに書き写します。モデルが読み違えた箇所と、人が拒否した箇所もそのまま残します。これが本章の背骨です。
以下はアルファビルドの週次検査cascade(doc-audit/data-qa/integrity/link)が合わせて吐いた違反候補だ。週次会議で使えるように分類してくれ。
各項目をP0(リリースブロック)/P1(検討)/P2(観察)に分類して根拠を一行ずつ — 推測なら「推定」と書いて。severityは君が断定せず『提案』だけにして、確定は私がする。
同じ根から出たものはまとめて、担当分野(レベル/ナラティブ/バランス/UI/データ)も推薦してくれ。判定できないものは正直に「分類不可、人の確認が必要」として外してくれ。
[生ログ]
INTEGRITY q_142 quest_graph: node 'end_021' has no outgoing edge (dead-end), zone=dungeon_021
INTEGRITY q_158 quest_graph: reward node references item_id 'it_9920' not in item table
DATA-QA reward_curve dungeon_017 reward_exp delta +35.2% vs sibling median (threshold 25%)
DATA-QA reward_curve dungeon_017 reward_gold delta +31.0% vs sibling median
DOC-AUDIT voice_lint dialogue_id_412 contains banned term (refgame slang leak)
DOC-AUDIT voice_lint dialogue_id_511 tone drift: formality score 0.31 (npc 'elder' expects >0.7)
DOC-AUDIT voice_lint dialogue_id_512 tone drift: formality score 0.29
DOC-AUDIT voice_lint dialogue_id_513 tone drift: formality score 0.33
DATA-QA loc_overflow ui_btn_enhance EN string 24 chars > slot 18 (overflow 6)
DATA-QA loc_overflow ui_btn_enhance TH string overflow 11
LINK doc_link gdd_dungeon_021.md -> [reward_table] target moved (404)
INTEGRITY k_skill skill 'k_017_charge' cooldown 0.0 (suspicious, min expected 0.5)
... (計47行、以下同形パターン — 省略なしで添付済み)
生ログを分類しました。severityは私の「提案」であり、最終確定は人が行う必要があります。
## P0候補 (リリースブロックの可能性 — 人の確定が必要)
1. [q_142] quest_graph dead-end (dungeon_021)
根拠: 出口エッジのない終了ノード → プレイヤーがクエスト進行中に閉じ込められる。
行き止まりは進行不能のためP0候補。(確定根拠が明確)
担当推薦: レベル/ナラティブ
2. [q_158] reward nodeが存在しないitem_id 'it_9920'を参照
根拠: 報酬付与時にNULL参照 → クラッシュまたは報酬未付与の可能性。
P0候補。ただし、item_idがまだ未入力状態(作業中)の可能性もあり
「データ未完成」か「誤記」かは人の確認が必要。
担当推薦: データ
3. [voice_lint dialogue_id_412] 禁止用語(refgame用語の流出)
根拠: 他ゲームの固有用語がセリフに残っていると法的・ブランドリスク。
P0候補。(禁止用語は通常無条件でブロック)
担当推薦: ナラティブ
## P1候補 (検討後に決定)
4. [reward_curve dungeon_017] exp +35.2% / gold +31.0% (両方とも兄弟中央値を超過)
※ 2行を1件にまとめ — 同じダンジョンの報酬曲線の異常。
根拠: 閾値(25%)超過だが「意図された高難度ダンジョンボーナス」の可能性あり。
バランス上の意図かミスかはデータだけでは判定不可 → P1。
担当推薦: バランス
5. [voice_lint 511・512・513] tone drift 3件 (formality 0.29~0.33, elder NPCは>0.7を期待)
※ 3行を1件にまとめ — 同じNPC 'elder'の連続セリフのトーン逸脱。
根拠: 同じNPCが連続でタメ口調に落ちたパターン → 一人のライターの
1セッションの作業物である可能性。個別ではなくまとめて検討を推奨。
推定: 意図されたキャラクター変化(怒りの場面など)の可能性もありライター判定待ち。P1。
担当推薦: ナラティブ
6. [loc_overflow ui_btn_enhance] EN 6字 / TH 11字超過
※ 2行で1件。同じボタンの多言語オーバーフロー。
根拠: 切れはUX損傷だがクラッシュではない。スロット拡張or短縮翻訳が必要。P1。
担当推薦: UI
## P2候補 (観察)
7. [doc_link gdd_dungeon_021.md -> reward_table 404]
根拠: 文書内部リンク切れ。ゲームビルドには影響なし、文書整合性の問題。P2。
担当推薦: データ(文書所有者)
## 分類不可 — 人の確認が必要
8. [k_017_charge cooldown 0.0]
根拠: クールダウン0.0が「意図されたパッシブ性スキル」か「入力漏れ」か
スキル設計の意図が分からなければ判定不可。integrityチェッカーは0.5未満を
疑いましたが、私はこのスキルの設計意図を知りません。
→ バランス担当が直接確認する必要があります。
(残り39行も上の7パターンのいずれかに分類 — 本回答では代表のみ展開)
ここからが核心です。LLMの分類をそのまま採用したわけではありません。著者が会議の前に直接レビューし、赤ペンを入れた部分です。
it_9920は2週間前に削除されたアイテムでした。未完成ではなく、参照切れ。リリースブロック確定です。モデルが7つのグループに圧縮したこと自体は大きな成果でした。47行を人がゼロから分類していたら、午前が消えていたはずです。しかしP0候補3件のうち1件(412)を人が降格させ、P1候補1件(158)を人が昇格させました。分類の60%は正しく、高くつく30%は人が直しました。この比率がまさに「LLMは加工、決定は人」の境界線です。
よし。君の分類のうち2つを私が変えた。
- q_158: P0確定 (it_9920は削除されたアイテム、参照切れ)
- voice_lint_412: P1へ降格 (意図された古い言い回しの引用、禁止用語辞書に例外追加)
この2つを反映して週次会議用の1ページGap Reportをマークダウンでレンダリングしてくれ。要約→P0→P1→P2→推移の順で。
推移の数字は私が渡す — 先週P0 5件、P1 22件、偽陽性12%。
モデルはこの入力を受けて、下記§レポート様式の1ページをそのまま出力しました。人が直した2行が正確に反映され、推移の数字は人が渡した値をそのまま使いました(でっち上げませんでした)。この往復が、Gap Report 1枚ができ上がるすべてです。
上のトランスクリプトをフローに整理するとこうなります。重要な分岐点がすべて人の側にある、という点が核心です。
flowchart TD
A[検査カスケード check
doc-audit+data-qa+integrity+link] --> B[生の違反ログ47行]
B --> C{LLM一次分類}
C -->|severity提案| D[P0候補]
C -->|severity提案| E[P1候補]
C -->|severity提案| F[P2候補]
C -->|判定不可| G[分類不可
人の確認が必要]
C -->|根が同じ| H[重複グループ化]
D --> I{人による検証}
E --> I
F --> I
G --> I
I -->|採用| J[severity確定]
I -->|昇格/降格| K[人が修正]
K --> J
J --> L[Gap Report 1ページのレンダリング]
L --> M[週次会議への入力]
M --> N{整合性失敗?}
N -->|はい| O[integrity_check_clickup_notify
コラボレーションツールへ即時通知]
N -->|いいえ| P[担当・締め切りの割り当て]
style C fill:#e8f0fe
style I fill:#fef3e8
style O fill:#fde8e8
LLMが触るボックスは青色の1つだけです。オレンジ色(人による検証)ですべてのseverityが確定し、赤色では整合性の失敗がコラボレーションツールへ即時に飛びます。検査・判定・確定はすべて人とatomの持ち分で、モデルは最初の分類一回だけを受け持ちます。
分類フローの末尾にはintegrity_check_clickup_notify atom(10.1)が付いています。このatomはレポートを作る段階とは別に、整合性検査が失敗した瞬間、会議を待たずにコラボレーションツールへカードを投げます。Gap Reportが週次のリズムだとすれば、このatomはそのリズムを破って入ってくる割り込みです。
q_158(削除されたアイテムへの参照)のように、ビルド自体を壊しかねない違反は月曜の会議まで待てません。カスケードがそれを捕まえた瞬間、コラボレーションツールに「P0疑い: q_158 参照切れ」が自動生成され、データ担当に割り当てられます。Gap Reportはその割り込みを1週間単位で集め直し、推移として見せる後ろの盤面です。2つの層が一緒に回ってこそ、「急ぎは即時、全体像は週次」という2拍子が噛み合います。
LLMの分類を人が検証したという事実は、口頭のままでは蒸発します。そのためレビューの段階にはhuman_review_attestation_evidence_mandatory atom(10.2)が掛かっています — 人によるレビューには証拠が必須です。
上のトランスクリプト③の段階 — 412を降格し、158を昇格させたあの判断 — は、レポートのフッターにレビュアーID・タイムスタンプと「変更した項目」のリストとして記入されます。次の四半期に誰かが「なぜ412がリリースに含まれたのか」と尋ねたら、「2026-W21のレビューで、意図された古い言い回しの引用と判定。禁止用語辞書に例外を追加」という記録が答えます。これがなければ、LLMの分類は、検証されたことのない自動出力と区別がつきません。
再依頼④の結果としてモデルがレンダリングした1ページは、こういう形です。上のトランスクリプトの分類がそのまま流れ込んでいます。
# Alpha Gap Report — 2026-W21
## 要約
- 検査cascade 47件の違反候補 → 7グループに分類
- P0確定3件 / P1 4件 / P2 1件 / 分類不可1件
- リリースブロック: q_142(行き止まり), q_158(参照切れ)
- 人のレビューによる変更: voice_412 降格(P0→P1), q_158 昇格(P1→P0)
## P0 — 即時対応 (人が確定)
| ID | 違反 | 分野 | 備考 |
|---|---|---|---|
| q_142 | dungeon_021 行き止まり | レベル/ナラティブ | LLM・人が一致 |
| q_158 | 削除されたit_9920への参照 | データ | 人が昇格 |
## P1 — 検討後に決定
- reward_curve dungeon_017: exp+35%/gold+31% (バランス, 意図確認待ち)
- voice 511・512・513: elder トーン逸脱3件まとめ (ナラティブ, ライター判定)
- voice_412: 古い言い回しの引用 (ナラティブ, 禁止用語例外処理済み)
- loc_overflow ui_btn_enhance: EN/TH 切れ (UI)
## P2 — 観察
- doc_link 404 (文書整合, ビルド影響なし)
## 分類不可 — 人の確認が必要
- k_017_charge cooldown 0.0 (バランス, 設計意図不明)
## 推移 (先週比)
- P0: 3件 (先週5件)
- P1: 4グループ (先週22件 — グループ分類でカウント方式を変更)
- 偽陽性: 人の修正2/8 = 25% (先週12%, ↑ — グループ化後に標本が縮小)
---
レビュー: 이민수 / 2026-W21 / 変更2件 (証拠: §レビューログ)
推移の偽陽性比率が25%に上がったことを隠していない点に注目してください。標本が8個に小さくなり、人が2個を直したので、算術的に25%です。レポートは、良く見せるために数字を作りません。先週の12%と単純比較すれば悪化に見えますが、分類方式がグループ単位に変わって標本が変わったという文脈が、一行で添えられています。1週間の比率だけで結論を出さないという原則が、ここで働いています。
著者のプロジェクトAで、Gap Reportのワークド分類を導入する前後を比較します。以下の数値のうち処理比率・時間は議事録とコラボレーションツールのタイムスタンプから取った実測で、チェッカーの偽陽性率は標本が週ごとに揺れるため方向だけを記します。
| 項目 | 導入前 | 導入後 | 根拠 |
|---|---|---|---|
| 47行の一次分類の所要 | 人が約40分 | LLM 1回+人のレビュー約12分 | 会議前の作業ログ(実測) |
| 検査結果→決定への反映 | 一部のみ | 大部分 | 議事録との対照(実測、正確な%は未集計) |
| P0の平均解消時間 | 3〜5日 | 1〜2日 | コラボレーションツールのカード作成→完了タイムスタンプ(実測) |
| LLM分類の人による修正率 | — | W21基準2/8 | 著者の推定(未検証、週ごとに変動) |
| 整合性失敗の認知遅延 | 会議まで待機 | 即時(atomが通知) | clickup_notify導入の効果(方向) |
修正率2/8を自慢のように書かなかった理由があります。それは1週間の標本に過ぎず、ある週にはモデルが5件を読み違えます。確実な利得は分類労働が40分から12分に減ったことであり、モデル分類の正確度そのものは毎週揺れます — モデルを信頼しているからではなく、人が12分以内に検証できる形に加工してくれるからこそ速くなったのです。
| パターン | 処方箋 |
|---|---|
| 47行を人が毎回手作業で分類 | LLMの一次分類→人の検証へ分業 |
| LLMのseverityをそのまま確定 | severityは「提案」、確定は人(③の段階) |
| 根が同じ違反を個別にカウント | グループ化の依頼をプロンプトに明記 |
| レビューの事実が口頭でしか残らない | human_review_attestation atomで証拠を強制 |
| 急ぎの整合性失敗が会議まで待機 | clickup_notify atomで即時通知 |
| 推移の数字をモデルがでっち上げる | 推移は人が入力、モデルはレンダリングのみ(④の段階) |
| レポートが長くなり会議で読まれない | 1ページを強制、元のログは別途保存 |
setup 1. 検査カスケード(または手元のlint・整合性チェッカーの集まり)の出力を、1つのファイルに集めましょう。 2. severity基準の3段階(P0ブロック / P1検討 / P2観察)を、チームで一行ずつ合意しておきます。 3. レビュアーID・タイムスタンプをレポートのフッターに記入するテンプレートを作ります。
prompt
以下は週次検査の出力だ。週次会議用に分類してくれ。severity(P0/P1/P2)は根拠を一行添えて『提案』だけにして(推測なら「推定」)、確定は私がする。同じ根の違反はまとめ、担当分野(名前ではなく)も推薦してくれ。判定できないものは正直に「分類不可」として外してくれ。
[生ログ貼り付け]
verify 1. P0候補の全件を人が1件ずつ検証し、降格/昇格を記録しましょう(③の段階)。 2. モデルがまとめた項目が本当に根が同じか、1つだけ逆にたどって確認しましょう。 3. 推移の数字が人の手から出た値か(モデルが勝手に埋めていないか)を、フッターで確認しましょう。
一人で作業しているなら、atom・コラボレーションツール・週次会議はなくても構いません。チェッカーの出力をテキストで貼り付けて上のプロンプトで分類だけ受け取り、P0候補3件だけを自分の目で検証して、その場で処理しましょう。分類をモデルに任せ、検証する項目をP0だけに絞ること — その一点が、一人規模で最も大きく時間を節約します。レポートの1ページは、Notionのメモ1枚で代替しても構いません。
スプリント締め切りの2日前、戦闘アーティストが社内メッセンジャーに短い動画を1本投げてきました。新規武人クラスの3段コンボです。1段目と2段目は刃風がうなっているのに、3段目では何の音もしませんでした。無音。本人はサウンドをすべて付けたと言い、サウンド担当はファイルをすべて渡したと言いました。どちらも嘘をついているわけではありませんでした。サウンドファイルは確かにリポジトリにありました。combo3_swing_final_real.wavという名前で。ゲームコードが探していた名前はsfx_K012_combo3_swing.wavでした。一文字も重なっていません。
この無音事故の追跡に、その日の午後が丸ごと消えました。クリップ1つ、サウンド1つの問題ではありません。名前を人が自由に付けられる限り、この種の事故は四半期ごとに数十件ずつ生まれ直します。本章は、その自由をルールに変える話です。
本章で答える質問 - アセット1万個の規模では、なぜ名前が自由ではなくルールなのか - 命名規約をatomとして強制し、lintで自動検証すると、何が閉じられるのか - スキル1つに付くアニメ・VFX・サウンド・アイコンのマッピングをAIがドラフトし、人が採用するワークド・トランスクリプト
専門外の読者のための一行。 アセット1万個やfbxファイル名の書式は、ゲームだけの事情に見えます。しかし、持ち帰っていただきたいただ一つのことは、ドメインを選びません — 「名前を自由に付けた瞬間、検索・自動化・連携がまとめて閉ざされる」。規模が大きくなれば命名は好みではなくルールにならなければならず、ルールになった名前だけをコードが自動で見つけて使えるという原則は、文書・資産・顧客レコードを扱うどんな仕事にも当てはまります。
著者がディレクションするプロジェクトAは、モバイルファーストのMMORPGです。キャラクターアニメーションアセットのおおよその規模は以下のとおりです。プレイヤークラス数と敵NPCの種類は実際の運用数値で、クリップ数と全体の推定値は著者の推定(未検証)です。
| アセット | 数 |
|---|---|
| プレイヤーキャラクターのクラス | 6 |
| 敵NPCの種類 | 80〜100 |
| キャラクター1体あたりの平均クリップ | 100〜150(著者の推定) |
| 全体クリップの推定 | 約10,000〜15,000(著者の推定) |
1万個。これは引き出し1万個と同じです。ラベルの付いていない引き出し1万個を前に「攻撃モーションはどこだったか」を探すのは、人の記憶力に賭けをする行為です。そしてその賭けは必ず負けます。見つからなければ、結果は二つに一つです。作業時間が2倍に延びるか、見つからなかったので同じ動作を新しく作るかです。後者のほうがたちが悪いです。アセットが肥大化するうえに、後から同じ動作が2つ、微妙に違う形で転がり回ることになるからです。
名前が自由領域である場合、閉ざされるのは検索だけではありません。「スキルIDからアニメーションファイルをコードが自動で見つけてくる」自動ルーティングも一緒に閉ざされます。名前から規則を読み取れなければ、コードはスキル一つひとつについてどのファイルを使うかを手で書いたマッピングテーブルを抱えていなければなりません。そのテーブルは、新規キャラクターが入ってくるたびに手作業で増えていきます。
プロジェクトAのアニメーションファイル名は、5つのスロットに固定されています。
<role>_<id>_<category>_<action>_<variant>.fbx
char_K001_idle_default_v1.fbx
char_K001_locomotion_walk_forward.fbx
char_K001_combat_attack_combo1_v2.fbx
char_K001_react_hit_heavy.fbx
enemy_E021_combat_skill_aoe_v1.fbx
5つのスロットはすべて、定められたenumに従います。自由入力が許されるスロットはidの一つだけで、そのスロットさえ[A-Z]\d{3}形式で縛られます。
| スロット | enum数 | 例 |
|---|---|---|
| role | 4 | char, enemy, pet, mount |
| id | 形式固定 | K001, E021, P003, M005 |
| category | 8 | idle, locomotion, combat, react, death, social, cinematic, system |
| action | カテゴリー別10〜30 | walk, run, attack, skill_aoe, hit_heavy |
| variant | 形式固定 | default, v1, v2, _short, _long |
ここでの核心は書式そのものではなく、書式をどこに記録しておくかです。命名規約をWiki文書の1ページに書いておくだけなら、それは誰も読まないラベルです。著者はこの規約をChar_Anim_Naming_Conventionという、信頼できる唯一の情報源(single source of truth)となるatomにし、人もlintもLLMもすべてこのatom一つだけを参照するようにしました。書式が文書ではなくatomとして固定化された瞬間、命名は「推奨事項」から「通過しなければならない関門」へと性格が変わります。
actionスロットのenumは無限に増えうるというのが弱点です。そこで、カテゴリー別の標準actionを辞書として管理します。
combat:
- attack_basic
- attack_combo1
- attack_combo2
- skill_<skill_id>
- parry
- dodge_forward
- dodge_back
react:
- hit_light
- hit_heavy
- knockback
- stagger
- stun
locomotion:
- idle
- walk_forward
- run_forward
- sprint
- jump_start
- jump_loop
- jump_land
新規actionを辞書に入れるかどうかは、手続きで判断します。四半期あたり3キャラクター以上が使いうるか、既存のactionでは本当に表現できないか、カテゴリーが明確か、そして最も重要なこととして — variantで吸収できるのではないか、です。variantで処理できるならactionは増やしません。action辞書が100個以内に保たれていれば、運用が健全であるサインです。ただし、これを絶対の上限として受け取りはしません。新規ジャンルや新規クラスが入ってくれば、一度に30〜40個増えることもあります。防ぐべきは数字ではなく、無秩序な増殖です。
書式をatomに記録したら、次はそのatomを自動で強制する検証器が必要です。人が毎回目視で5つのスロットを検査するわけにはいきません。以下がそのlintの背骨です。
# anim_naming_lint.py
import re, yaml
NAMING_PATTERN = re.compile(
r"^(?P<role>char|enemy|pet|mount)_"
r"(?P<id>[A-Z]\d{3})_"
r"(?P<category>idle|locomotion|combat|react|death|social|cinematic|system)_"
r"(?P<action>[a-z_]+?)"
r"(?:_(?P<variant>v\d+|short|long|light|heavy|left|right|forward|back))?"
r"\.fbx$"
)
ACTION_DICT = yaml.safe_load(open("char_anim_naming_convention.yaml"))
def check(filename):
m = NAMING_PATTERN.match(filename)
if not m:
return f"命名規則違反(5スロット形式不一致): {filename}"
category, action = m.group("category"), m.group("action")
# skill_<id> 形式は動的actionなのでprefixのみ検査
base = "skill" if action.startswith("skill_") else action
if base not in ACTION_DICT.get(category, []):
return f"action enum外({category}): {action}"
return None
新しいfbxがリポジトリに入った瞬間、この検査が走ります。違反ならcommitがブロックされます。ここで重要なのは、違反を人の責任にしないという点です。無音事故を起こしたアーティストを責める代わりに、「その名前はそもそもcommitできてはいけなかった」という方向へ、責任を道具に押し付けます。人はミスをし、道具はそのミスを防ぐ。これが命名システムの基本姿勢です。
命名が強制されると、その見返りとして自動ルーティングが解放されます。
def play_skill_animation(character, skill_id):
anim_path = f"char_{character.id}_combat_skill_{skill_id}.fbx"
if not exists(anim_path):
anim_path = f"char_{character.id}_combat_skill_default.fbx" # fallback
play(anim_path)
手で書いたマッピングテーブルが消えます。新規キャラクターや新規スキルが入ってきても、アニメファイルを規約どおりに追加するだけで、コードは1行も変わりません。無音事故に戻ってみると、もしあのサウンドファイルがsfx_K012_combo3_swing.wavという規約名でしか入れなかったとしたら — そもそもcombo3_swing_final_real.wavはcommit段階ではじかれていたはずで、あの日の午後は丸ごと生き残っていたはずです。
variantスロットは、action enumを守る安全弁です。同じ動作のバージョン(v1、v2)、長さ(_short、_long)、強度(_light、_heavy)、方向(_forward、_back)はすべてvariantで吸収し、actionを細かく分岐させる代わりに受け止めます。そしてゲームコードは、そのvariantをコンテキストに応じて選んで使えます。
def select_variant(base_action, context):
if context.distance < 3:
return f"{base_action}_short"
if context.distance > 10:
return f"{base_action}_long"
return base_action
命名規約がコードの分岐点になるわけです。
命名がL1だとすれば、スキルとアセットをつなぐマッピングはL2です。スキル1個は通常、アニメーション2〜3個、VFX1〜3個、サウンド2〜5個、UIアイコン1個を引き連れています。平均してアセット10個。スキルが200個なら、マッピング対象は約2,000個です。この規模を人の頭で管理するのは不可能です。そこでスキル1つにつきyamlを1枚置き、そのスキルのアセットはその1枚からだけ読むように縛ります。
---
skill_id: skill_K001_combo1
description: K001 コンボ1 (3打連続)
type: melee_combo
animations:
- clip: char_K001_combat_attack_combo1_v2.fbx
role: main
bone_alignment: spine_03
vfx:
- asset: vfx_K001_combo1_slash.vfx
socket: weapon_tip
timing_ms: [0, 150, 300]
- asset: vfx_hit_blood_light.vfx
socket: target
timing_ms: [150]
sound:
- asset: sfx_K001_combo1_swing.wav
volume: 0.8
timing_ms: 0
- asset: sfx_hit_metal_light.wav
volume: 0.6
timing_ms: 150
ui_icon: icon_skill_K001_combo1.png
ui_tooltip_key: skill_K001_combo1_tooltip
verified: true
---
この1枚が、スキル1つのアセット全体です。そしてこのyaml内のすべてのアセットパスは、11.1の5スロット規約に従います。命名lintが崩れれば、このマッピングも一緒に崩れます。2つの層はペアで動作します。
マッピングが1か所に集まると、影響追跡が自動で解放されます。あるVFXを作り直そうとするとき、それがどのスキルに影響するのかを手で探し回る必要がありません。
def find_skills_using(asset):
affected = []
for path in glob("skills/*.yaml"):
skill = yaml.safe_load(open(path))
for cat in ("vfx", "sound", "animations"):
for entry in skill.get(cat, []):
if entry.get("asset") == asset or entry.get("clip") == asset:
affected.append(skill["skill_id"])
return affected
# find_skills_using("vfx_hit_blood_light.vfx")
# → ["skill_K001_combo1", "skill_K005_combo2", "skill_E021_attack_basic", ...]
アセット差し替えの会議に、影響を受けるスキルの一覧が自動で添付されます。「これを変えたらどこに影響しますか?」という質問が出る前に、答えがすでに議事録の横に置かれています。
マッピングにもlintが付きます。すべてのアセットファイルが実際に存在するか、animations.mainとui_iconがそれぞれ1つずつあるか、timing_msがアニメーションの長さの範囲内にあるか、そして — すべてのアセットパスが11.1の命名規約を通過するか、です。最後の項目が、2つの層を束ねる釘です。ビルド時に自動で走ります。
ここまでの命名lintとマッピングlintが1つのゲートとしてどうつながるのかを、フローで整理します。
flowchart TD
A[新規アセット/スキルのcommit] --> B{5スロット命名lint
Char_Anim_Naming atom}
B -->|違反| X[commitブロック
違反メッセージを返す]
B -->|通過| C{マッピングyaml lint}
C -->|アセット不在 / main・icon欠落| X
C -->|命名規約違反のアセット参照| X
C -->|通過| D[アセットプール統計の更新]
D --> E{LLMの命名・マッピング候補だったか?}
E -->|はい| F[人が採用/棄却
可逆段階]
E -->|いいえ| G[ビルドに反映]
F -->|採用| G
F -->|棄却| H[候補を破棄
可逆、コスト0]
G --> I{モーションキャプチャ・音声収録の発注?}
I -->|はい| J[不可逆段階に突入
後戻り不可]
I -->|いいえ| K[可逆アセットの維持]
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class A,D data;
class B,C,E code;
class F,I human;
class K pass;
class X,H,J fail;
このフローの末尾に可逆/不可逆の境界があるという点に注目してください。yamlの修正、LLM候補、キーフレームまでは、すべて可逆です。気に入らなければ破棄すれば済み、コストはほぼ0です。しかし、モーションキャプチャの撮影、声優の音声収録、シグネチャーボイスのキャスティングへ進んだ瞬間、不可逆に変わります。俳優とスタジオの予約、収録ブース、契約、市場の認知が懸かってきます。だからこそ、すべての命名・マッピング・ペルソナの決定は、不可逆段階の直前、つまりyamlとLLM候補とキーフレームという可逆領域の中で終えなければなりません。
ここまでがシステムで、ここからはAIがどこに入ってくるのかを、実際のセッションそのままでお見せします。新しい火炎スキルskill_K012_flame_burstのアセットマッピングのドラフトをLLMに任せる場面です。
プロンプト(全文):
以下は我々のプロジェクトのキャラクターアニメーション命名規約(atom)と既存アセットプール統計、
そして類似スキルyaml 1件である。
[命名規約]
<role>_<id>_<category>_<action>_<variant>.fbx
role: char|enemy|pet|mount / category: idle|locomotion|combat|react|death|social|cinematic|system
[アセットプール TOP使用アセット]
sfx_hit_metal_light.wav (47スキル), vfx_dust_kickup.vfx (38スキル),
vfx_fire_burst_med.vfx (22スキル), sfx_fire_whoosh.wav (19スキル)
[類似スキル yaml]
skill_id: skill_K005_fire_slash / type: melee_fire
animations: [char_K005_combat_attack_fire_v1.fbx]
vfx: [vfx_K005_fire_slash.vfx, vfx_fire_burst_med.vfx]
sound: [sfx_K005_fire_swing.wav, sfx_fire_whoosh.wav]
新規スキル: skill_K012_flame_burst (K012 武人, 範囲火炎爆発, 1打)
このスキルのマッピングyamlドラフトを作成せよ。既存アセットプールを最大限再利用し、
新規制作が必要なアセットは[NEW]タグを付けて表示せよ。
Claudeの生の出力(要約・整理なし):
skill_id: skill_K012_flame_burst
description: K012 武人 範囲火炎爆発 (1打)
type: aoe_fire
animations:
- clip: char_K012_combat_skill_flame_burst_v1.fbx # [NEW] 新規モーション必要
role: main
bone_alignment: spine_03
vfx:
- asset: vfx_fire_burst_med.vfx # 再利用 (プール22スキル)
socket: ground
timing_ms: [200]
- asset: vfx_K012_flame_ring.vfx # [NEW] 範囲表現用の新規
socket: ground
timing_ms: [200, 350]
sound:
- asset: sfx_fire_whoosh.wav # 再利用 (プール19スキル)
volume: 0.8
timing_ms: 0
- asset: sfx_explosion_low.wav # [NEW] 爆発音の新規と推定
volume: 0.9
timing_ms: 200
ui_icon: icon_skill_K012_flame_burst.png # [NEW]
ui_tooltip_key: skill_K012_flame_burst_tooltip
verified: false
参考:
char_K012_combat_skill_flame_burst_v1.fbxではactionがskill_flame_burstになっているが、命名規約上、skill actionはskill_<skill_id>形式が正しいかどうか確認が必要。skill_K012とskill_flame_burstの どちらを使うのか規約に明記されていないため推定した。
人による検証/拒否:
最後の参考が、まさに弱点を突いていました。LLMが知らないのではなく、規約atomにそのスロットの形式が曖昧に書かれていたのです。実際、プロジェクトAの規約はskill_<skill_id>形式を使います。したがってファイル名はchar_K012_combat_skill_K012_v1.fbxが正解です。LLMが作ったskill_flame_burstは拒否です。そしてsfx_explosion_low.wavは[NEW]と表示されましたが、アセットプールを見直すと、sfx_impact_heavy.wavが47スキルで爆発音としてすでに使われていました。LLMに渡したプール統計がTOP4までだったため、見落としたのです。新規制作の代わりに再利用へ切り替えます。
再依頼:
2点を修正して再作成せよ。
1. skill action形式は skill_<skill_id> である。K012スキルならactionは skill_K012。
2. アセットプールに sfx_impact_heavy.wav (47スキル, 爆発/衝撃音)がある。
sfx_explosion_low.wav の新規制作の代わりにこれを再利用せよ。
全体プール統計は以下のとおり。[全38種添付]
この1サイクルでLLMがやったことは「それらしいドラフト」であり、人がやったことは「規約の曖昧な箇所の発見・プールの欠落の発見・再利用の決定」です。LLMは新規アセット候補をあまりにも簡単に[NEW]と打つ傾向があるため、再利用の判断は最後まで人が握ります。それでも、空の画面でyamlをゼロから組むのと、採用・拒否すべきドラフトを受け取って直すのとでは、作業負担が違います。
上のトランスクリプトこそ、進歩的適用の一場面です。命名・マッピングの運用は2つの段階に分かれます。
保守的段階では人が命名を与え、マッピングを組み、自動化は検証(lint)と追跡(find_skills_using)だけを受け持ちます。現在、ほとんどのMMORPGのキャラクター・アセット運用はここにあります。進歩的段階では、命名ドラフト、マッピングドラフト、さらにNPCペルソナの生成までLLMが候補を出し、人の手に残る決定は「どの候補を採用するか」の一つに絞られます。
進歩的段階が定着するには、3つのものが揃っていなければなりません。1つ目は命名規約のlintエンジンです。LLMが出した命名候補も、人が組んだものとまったく同じように5スロットlintを通過して初めて採用されます。上のトランスクリプトでLLMのskill_flame_burstが拒否されたのが、このゲートです。2つ目はNPCペルソナの自動生成器です。キャラクターyamlをvoice_profile・anim_set・skill_setの3軸に分解しておけば、LLMが「50代の武人、慎重、低いトーン」のような描写を受け取り、3軸の候補をそれぞれ分けて提案できます。NPC100体の3軸をゼロから組むのと、ペルソナごとに数個の候補から選ぶのとでは、負担が違います。3つ目はマッピング候補の生成器です。find_skills_usingの逆方向 — 「この新しいスキルに合う既存アセット」の検索をアセットプール統計と結び付け、スロット別の再利用候補を提案します。新規制作のコストを下げ、再利用率を高める双方向の効果です。
3つの要素はすべて同じインフラ(yaml・lint・アセットプール統計)の上で動きます。命名規約とマッピングyamlが信頼できる唯一の情報源として整列しているときにだけ稼働し、整列が崩れればLLMに渡す入力そのものがなくなります。
この3つの要素が2010年代にも理論的には可能だったという点は、押さえておく価値があります。詰まっていたのは3か所でした。動作が何であるかを自然言語で理解できず5スロット候補を出せなかったこと、voice・anim・skillを切り分けて束ねるのは人の直観の領域だったこと、「似た雰囲気のVFX」をテキスト描写で探すのが難しかったことです。2023年以降のLLMの発展で、3か所すべてが支援可能な領域に入ってきました。紙の上にしかなかった進歩的なキャラクターアセット化ビジョンのかなりの部分が、実務適用の段階へ移ってきたわけです。
プロジェクトAの命名・マッピング導入前後の比較です。検索時間とオンボーディング期間は著者が実際に体感・記録した方向性であり、比率の項目は四半期の振り返りで集計した実測です。絶対値の一部は著者の推定(未検証)であることを明記しておきます。
| 項目 | 導入前 | 導入後 |
|---|---|---|
| 動作の検索時間(アニメーター) | 5〜10分 | 30秒 |
| 重複制作の比率 | 12〜15% | 1〜2% |
| 新規キャラクターのルーティングコード変更 | 50〜100行 | 0行 |
| 新規スキルのアセット欠落事故 | 四半期あたり5〜8件 | 0〜1件 |
| 未使用アセットの累積(ライブラリー比率) | 約30% | 約8% |
| 新人アニメーターのオンボーディング | 2週間 | 3日 |
最後の項目が、最も静かですが大きな効果です。命名規約のatom一つが、そのままオンボーディングガイドになります。新人アニメーターには「名前はこの5つのスロットで付け、lintに止められたらlintの言うことを聞く」という一文だけで、初日から作業が可能になります。
| パターン | 処方 |
|---|---|
| 命名規約をWiki文書としてだけ置く | 単一atomとして固定化+lintで強制 |
| action enumの無限増殖 | 辞書+新規追加の手続き |
| 命名検証なしのcommit | 自動lintでcommitをブロック |
| コードにハードコードされたマッピングテーブル | 命名ベースの自動ルーティング |
| variantなしでactionを細かく分岐 | variantスロットで吸収 |
| アセットマッピングがコード・シート・文書に分散 | yaml1ファイルに統合 |
| LLMのマッピング候補を検証なしで採用 | 命名lint+人による再利用判断 |
| 命名違反を人の責任にする | lintを補強し、責任を道具へ |
setup — アニメーションファイル名を<role>_<id>_<category>_<action>_<variant>.fbxの5スロットで定義し、カテゴリー別のaction辞書をyaml1ファイルに集めましょう。このyamlをチームの信頼できる唯一の情報源として宣言しましょう。
prompt — LLMに「[命名規約yaml]+[アセットプール統計]+[類似スキルyaml1件]」を渡し、新規スキルのマッピングyamlドラフトを依頼しましょう。再利用アセットと新規制作アセット([NEW]タグ)を区別するよう明示しましょう。
verify — LLM出力のすべてのアセットパスを命名lintに通しましょう(前掲のanim_naming_lint.py)。通らなければ拒否します。通過した候補のうち[NEW]タグは、アセットプールを再度洗い直し、再利用できるかどうかを人が判断します。
anim_naming_lint.py1ファイルで掛けましょう。企画会議の冒頭に、ペットのリストが上がってきます。オオカミ系12種、ネコ系8種、鳥系5種。誰も「では1匹ずつ作ってみよう」とは言いません。キャラクターと違い、ペットは最初から「50種を量産すること」が前提だからです。問いは「どうやって1種をうまく作るか」ではなく、「一度作った骨格を何種に共有させるか」から始まります。
キャラクターは1種1種がユーザーにとって固有の存在なので、1種ずつ丹精を込めて作ります。一方、ペットやマウント(騎乗用の乗り物)は「同じ骨格で色とアビリティだけを変えたバリエーション」がほとんどのため、設計の最初から命名規則・テンプレート・lintをそろえて量産パイプラインを敷きます。1種を作り込んだあと、それが12セットに複製されるまま放置すると、色だけが違う12匹のオオカミに同一のアニメーションクリップが別々に入り、フォルダーが4ギガに膨れ上がります。それは量産ではなく、量産をしなかった結果です。核心は「どれだけうまく作るか」ではなく、「どれだけ少なく作り、どれだけ多く共有するか」です。
そこで本章では、オオカミ系ペットのテンプレート1種をyamlで定義し、その骨格を継承するインスタンスをAIに量産させ、lintで検証し、何パーセントが廃棄されるかを測定するという一連の流れを最後まで追いかけます。
キャラクター・ペット・マウントの3つはアセット構造が似ていますが、ユーザーの意識に占める比重が異なります。キャラクターは、ユーザーがゲーム時間の100%をともに過ごす自分自身です。ペットはそばに置く仲間として50〜70%の時間をともにし、マウントは移動のときだけ取り出す道具として10〜20%にとどまります。意識に占める比重が低いほど、ユーザーはディテールを見なくなります。キャラクターに注ぐ丹精をマウントにも同じだけ注ぐのは、毎日座るデスクとたまに広げる折りたたみ椅子を同じ予算で管理するようなものです。
そこでペット・マウントは「テンプレート-インスタンス」構造で運用します。骨格・動作・基本アビリティを収めたテンプレートを1種作り、色・アイコン・微細なアビリティだけを変えたインスタンスをその上に載せます。インスタンスはテンプレートが持つアセットの90%を共有するため、実際に新規で作るのは残りの10%だけです。この分離を図にすると次のとおりです。
左のテンプレートの塊を一度作れば、右のインスタンスは色とアイコンとアビリティ1行を差し替えるだけで済みます。先ほど述べた「4ギガのフォルダー」は、この分離を省いたために90%のアセットが12回複製されたときの姿です。
ペット・マウントの命名規則は、11.1のキャラクター命名から1スロット減らした形です。キャラクターはchar_<id>_<category>_<action>_<variant>の5スロットを使いますが、ペット・マウントはvariantを省略して4スロットでいきます。variantが必要な場合はactionに統合します。
pet_<id>_<category>_<action>.fbx
mount_<id>_<category>_<action>.fbx
例:
pet_P003_idle_default.fbx
pet_P003_combat_bite.fbx
mount_M005_locomotion_run.fbx
アセットマッピングのyamlも、キャラクターの様式からvfx・soundスロットを削って軽くします。これらのスロットを丸ごと抱えたままのインスタンスは空欄だらけの様式になり、lintが毎回空振りの警告を出します。
ここからが本題です。オオカミ系テンプレート1種を定義し、そこからインスタンスを量産してみましょう。
AIに量産させる前に、人がテンプレート1種を手作業で確定します。この1種がインスタンス数十種の品質基準になるため、自動化はしません。オオカミ系(canine)テンプレートは次のように定めました。
# pet_template_canine.yaml
template_id: pet_template_canine
skeleton: skel_quadruped_medium # 四足中型の共用骨格
shared_animations:
- clip: pet_template_canine_idle_default.fbx
- clip: pet_template_canine_locomotion_walk.fbx
- clip: pet_template_canine_locomotion_run.fbx
- clip: pet_template_canine_combat_bite.fbx
shared_abilities:
- id: pet_template_canine_passive_speed
description: 仲間の移動速度 +3%
- id: pet_template_canine_active_bite
description: 単体対象への噛みつき、クールダウン 12s
bt_ref: bt_pet_canine_default # 追従 + 戦闘補助の基本 BT
instance_overridable: # インスタンスが変えてよいフィールドのホワイトリスト
- visual_skin
- ui_icon
- ui_tooltip_key
- extra_ability # インスタンスあたりアビリティ1種まで追加許可
ここで核心の仕掛けになるのがinstance_overridableです。インスタンスが触ってよいフィールドをホワイトリストで固定します。AIが量産の途中でスケルトンや共有アニメーションを勝手に変えようとすれば、このリストにないフィールドに触れたことになるので、lintが捕まえます。「変えてよいもの」を先に定義することが、量産のシートベルトです。
次は、インスタンス10種を量産させたプロンプトの全文です。要約せず、そのまま掲載します。
[プロンプト]
あなたはペットデータの作成を支援するアシスタントだ。以下のテンプレートを基準に、
オオカミ系ペットのインスタンス10種のyamlを生成せよ。
[テンプレート] pet_template_canine.yaml
(上記yaml全文を貼り付け)
[ルール]
1. 各インスタンスは template: pet_template_canine を必ず明記する。
2. overridesには instance_overridable ホワイトリストのフィールドだけを入れる。
ホワイトリストにないフィールド(skeleton, shared_animations など)には絶対に触れない。
3. visual_skinはオオカミの自然なバリエーションでなければならない(色・模様・サイズ)。
4. extra_abilityはインスタンスあたり最大1種。passive または active のどちらか一方。
既存の shared_abilities と効果が重複してはならない。
5. ui_icon, ui_tooltip_keyは pet_id に合わせた命名規則に従う。
6. pet_idは pet_P003 ~ pet_P012 で付与する。
出力はyaml 10個のブロックのみ。説明文は付けるな。
ルール2が、ステップ1のホワイトリストと対になります。ルール4の「既存のshared_abilitiesと効果が重複してはならない」という制約は、AIがアビリティを安易に複製するのを防ぐためのものです。こうした制約をかけないと、後で見るとおり、AIはもっとも安全な選択肢(既存アビリティのコピペ)に収束します。
AIが吐き出した出力から、成功・失敗・グレーが混ざったままの状態を抜粋します。きれいに整えることはしません。
# 出力 1 — 正常
pet_id: pet_P003
template: pet_template_canine
overrides:
visual_skin: wolf_gray
ui_icon: icon_pet_P003.png
ui_tooltip_key: pet_P003_tooltip
extra_ability:
id: pet_P003_active_howl
description: 5秒間 敵の移動速度 -20%、クールダウン 30s
# 出力 2 — 正常
pet_id: pet_P004
template: pet_template_canine
overrides:
visual_skin: wolf_black
ui_icon: icon_pet_P004.png
ui_tooltip_key: pet_P004_tooltip
extra_ability:
id: pet_P004_passive_crit
description: 仲間のクリティカル率 +2%
# 出力 5 — グレー (アビリティが共有アビリティと事実上重複)
pet_id: pet_P007
template: pet_template_canine
overrides:
visual_skin: wolf_brown
ui_icon: icon_pet_P007.png
ui_tooltip_key: pet_P007_tooltip
extra_ability:
id: pet_P007_passive_speed_boost
description: 仲間の移動速度 +3% # ← sharedのpassive_speedと効果が同一
# 出力 8 — 失敗 (ホワイトリスト外のフィールド侵犯)
pet_id: pet_P010
template: pet_template_canine
overrides:
visual_skin: wolf_white
ui_icon: icon_pet_P010.png
shared_animations: # ← overridableホワイトリストにない
- clip: pet_P010_combat_pounce.fbx
extra_ability:
id: pet_P010_active_pounce
description: 跳躍攻撃、クールダウン 20s
# 出力 9 — 失敗 (命名規則違反)
pet_id: P011 # ← 'pet_' 接頭辞の欠落
template: pet_template_canine
overrides:
visual_skin: wolf_silver
ui_icon: pet11_icon.png # ← icon_pet_P011.png 規則違反
ui_tooltip_key: pet_P011_tooltip
extra_ability:
id: pet_P011_passive_dodge
description: 仲間の回避 +1%
10種のうち、正常はP003・P004・P005・P006・P008・P012の6種、アビリティ重複でグレーなのがP007の1種、ホワイトリスト侵犯・命名違反で失敗したのがP009・P010・P011の3種でした。AIはルール4を課したにもかかわらずP007で共有アビリティを写してきて(もっとも安全な選択)、ルール2を課したにもかかわらずP010でスケルトンのアニメーションに手を出しました。制約を明示しても、量産物の一定割合は漏れるのが現実です。だから次のステップが必要になります。
人が目で10種を逐一確認する代わりに、lintを回します。lintのルールは、ステップ1のテンプレートのホワイトリストと11.1の命名規則からそのまま引いてきます。検査項目は4つです。
flowchart TD
A[インスタンスyaml 10種] --> B{templateフィールドが
存在 & 有効?}
B -->|なし/誤記| F[REJECT: テンプレート参照エラー]
B -->|OK| C{overridesフィールドが
ホワイトリスト内?}
C -->|リスト外フィールド侵犯| F2[REJECT: ホワイトリスト違反]
C -->|OK| D{pet_id・ui_icon
命名規則を通過?}
D -->|違反| F3[REJECT: 命名規則違反]
D -->|OK| E{extra_abilityが
sharedと重複?}
E -->|重複| W[WARN: アビリティ重複を検討]
E -->|固有| P[PASS]
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class A data;
class B,C,D,E code;
class P pass;
class F,F2,F3,W fail;
各インスタンスが4つのゲートを通過すればPASS、途中で引っかかればREJECTまたはWARNに落ちます。実際の検証結果を表に整理すると、次のとおりです。
| pet_id | template | ホワイトリスト | 命名 | アビリティ重複 | 判定 |
|---|---|---|---|---|---|
| pet_P003 | OK | OK | OK | 固有 | PASS |
| pet_P004 | OK | OK | OK | 固有 | PASS |
| pet_P005 | OK | OK | OK | 固有 | PASS |
| pet_P006 | OK | OK | OK | 固有 | PASS |
| pet_P007 | OK | OK | OK | 重複 | WARN |
| pet_P008 | OK | OK | OK | 固有 | PASS |
| pet_P009 | OK | OK | 違反 | — | REJECT |
| pet_P010 | OK | 侵犯 | — | — | REJECT |
| P011 | OK | OK | 違反 | — | REJECT |
| pet_P012 | OK | OK | OK | 固有 | PASS |
PASS 6、WARN 1、REJECT 3。WARNはアビリティを1行変えれば生かせますが(P007)、REJECTの3種は廃棄します。
このワンサイクルの廃棄率は、REJECT 3 / 全体10 = 30%です。WARNまで「手直しが必要なもの」として括ると、手直し率は40%です。この数字が量産パイプラインの健康指標です。廃棄率が30%なら、ペット50種を確保するには約72種を生成させる必要があるということです(50 / 0.7 ≈ 71.4)。生成は安いので、この程度のオーバーシュートは許容できます。ただし、廃棄率が回を重ねても下がらないなら、それはプロンプトの制約が足りないというシグナルです。
そこで、廃棄理由をプロンプトにフィードバックします。REJECT 3種の理由(命名の欠落、ホワイトリスト侵犯、アイコン規則違反)を集め、再依頼に1行ずつ追加しました。
[再依頼の追加ルール]
7. pet_idは必ず 'pet_' 接頭辞で始める。(前のバッチで P011 が欠落)
8. ui_iconは例外なく icon_<pet_id>.png 形式である。(pet11_icon.png のような変形は禁止)
9. overridesに shared_animations / skeleton / bt_ref を絶対に入れるな。
動作を変えたい場合は extra_ability だけで表現する。(P010 の事例)
この3行を追加して次のバッチ10種を回したところ、REJECTが3から1に減りました。廃棄率は30%→10%。廃棄理由をルールへ昇格させるこのフィードバックこそ、量産品質を回を追うごとに引き上げるメカニズムです。人は毎回50種をチェックする代わりに、廃棄理由をルール1行に移す仕事だけをします。
マウントはペットよりもう一段単純です。スキルもBT(BehaviorTree、ビヘイビアツリー)もなく、移動パラメーターや戦闘可否のようなデータしかありません。そのため、マウントのインスタンスは事実上、表の1行です。
# mount_template_equine.yaml ベースのインスタンス
mount_id: mount_M005
template: mount_template_equine
overrides:
visual_skin: horse_white
movement:
run_speed: 7.0
sprint_speed: 12.0
combat:
allow_combat: false # 戦闘中は使用不可
dismount_on_damage: true
ui_icon: icon_mount_M005.png
マウント量産のlintはさらに短くなります。命名・テンプレート参照・ホワイトリストに加えて、「movementパラメーターが許容範囲内か」(例:sprint_speedがwalk_speedより大きいか、上限を超えていないか)だけを検査すれば足ります。ペットで作ったパイプラインをそのまま使い、ゲート数だけを減らした形です。マウントに戦闘機能を付けるのは慎重であるべきです。allow_combatをtrueに開いた瞬間、ゲームの複雑度は2倍になり、ペット・キャラクターシステムとの衝突検証を新たにやり直すことになります。
ペット・マウントにキャラクターのパターンをフルに適用した場合と、テンプレート-インスタンスで単純化した場合を、著者のプロジェクトAで比較しました。以下の数値のうち、時間とアセット数は著者の推定(未検証)であり、廃棄率とアセット共有率は実測に沿った比率です。
| 項目 | フル適用 | テンプレート-インスタンス |
|---|---|---|
| ペット1種のアセット作業時間 | 1〜2週間(著者の推定) | 3〜5日(著者の推定) |
| ペットライブラリーのアセット数 | 約2,000(著者の推定) | 約600(70%削減) |
| インスタンス1種あたりの新規アセット比率 | 100% | 約10% |
| 初回バッチの量産廃棄率 | — | 30%(実測ベース) |
| フィードバック後の廃棄率 | — | 10%(実測ベース) |
| ユーザーの体感(ペットの多様性) | 基準 | ほぼ同じ |
標本と測定。 上の表は、著者の環境の1プロジェクト(プロジェクトA)におけるペット1ラインの観察です(n=1ライン)。「70%削減」「約10%」は独立した測定ではなく、同じ行の推定アセット数(約2,000→約600)から導いた算術比率なので、前提の絶対値が推定である以上、この百分率も推定として読むべきです。廃棄率30%・10%は、初回バッチからフィードバックまでの単一の量産サイクルで得た実測ベースの値であり、反復測定の標本ではありません。あなたのチームの削減根拠として引用せず、同じ方法でご自身のラインで直接測ってください。
最終行が、本章全体の結論です。アセットの90%を共有し、廃棄率を測定しながら量産しても、ユーザーが感じるペットの多様性はフル制作とほとんど差がありませんでした。先ほどの4ギガのフォルダーは、ユーザーが結局見分けられないディテールにアセットを12セット複製したときに支払うコストです。量産を前提に敷けば、減るのは運用コストであって体験ではありません。
| 落とし穴 | 処方 |
|---|---|
| キャラクターシステムをペット・マウントへそのまま移植 | variantスロット・vfx・soundを削った4スロット構成に変える |
| 同じスケルトンのペットを独立アセットとして複製 | テンプレート1種+インスタンス、ホワイトリストで共有を強制 |
| AIの量産物を人のチェックなしでコミット | lintの4ゲート+廃棄率測定 |
| 廃棄率が回を重ねても下がらない | 廃棄理由をプロンプトのルールへ昇格(フィードバック) |
| ペットにキャラクター級のスキルを付与 | インスタンスあたりextra_ability 1種の上限 |
| マウントに戦闘機能を付与 | allow_combatは慎重に、複雑度×2を覚悟 |
ペット・マウントはユーザー体験への影響が小さいぶん、AIの自由度がキャラクターより大きくなります。コンセプトを適合するテンプレートにマッチングし、アビリティ候補を提案し、インスタンスyamlを量産する仕事は、AIが速くこなします。ただし、自由度が大きいからといって検証を抜けば、先ほど見た30%の廃棄物がそのままビルドに混ざります。人の持ち場は2つです。第一に、テンプレート1種を手作業で確定し、品質基準を固定すること。第二に、何がなぜはじかれたのかを読み取り、次のバッチの漏れが減るように制約を磨くこと。量はAIが満たし、基準線とその補正は人が握る——この分業が、このシステムを回します。
setup
1. ペット1系統(例:オオカミ)の共用スケルトン・共有アニメーション4種・共有アビリティ2種を決め、pet_template_<계열>.yamlとして保存してください。
2. テンプレートにinstance_overridableホワイトリスト(変えてよいフィールド)を明記してください。
3. lintの4ゲート(テンプレート参照/ホワイトリスト/命名規則/アビリティ重複)をスクリプトで準備してください。
prompt 4. テンプレートyamlの全文+量産ルール(ホワイトリスト外のフィールド禁止、アビリティ重複禁止、命名規則)を貼り付けて、インスタンス10種を依頼してください。 5. 出力は「yamlブロックのみ、説明禁止」で形式を固定してください。
verify 6. lintを回してPASS/WARN/REJECTを分類し、廃棄率を計算してください。 7. REJECTの理由を集めてプロンプトにルールを1行ずつ追加し、次のバッチを回してください。廃棄率が下がるかを確認してください。
一人で作るゲームなら、lintスクリプトがなくても大丈夫です。ペット1系統のテンプレートyamlを1枚手で書き、AIに「このテンプレートから色・アイコン・アビリティだけを変えたインスタンス5種。スケルトンと共有アニメーションには絶対に手を触れないこと」と依頼してみましょう。受け取った5種にざっと目を通し、スケルトンに触れたもの・命名規則を破ったものだけを捨ててください。捨てた理由を、次の依頼に1行書き足してください。テンプレート1.1と「捨てた理由のフィードバック」さえあれば、ツールなしでも本章の核心は機能します。
第一読者:アートチームと協業するゲームプランナー・アートディレクター(中規模(10〜50人)チーム) 一人/趣味の読者向け縮小バージョン:§12.1.8「一人ならこれだけ」
AIで生成したコンセプトアート100枚を会議室の壁に貼った日のことを覚えています。30秒で印刷された100枚のうち、アートディレクターが選んだのは3枚で、97枚はその場で捨てられました。誰かはそれを「97%の無駄」と呼びました。しかし手で描いていたら、その3枚にたどり着くために作家は2週間を費やしたはずです。何が無駄なのかが、逆転していたのです。
本章が扱うのは、その逆転を運用に変える方法です。核心は一行です。AIアートは可逆な段階(コンセプト・テクスチャー探索)では思う存分量産し、不可逆な段階(最終レンダリング・モーションキャプチャー・ビルド反映)の前には人が守るゲートを置きます。捨ててもよい場所では99枚を捨て、後戻りできない場所では1枚もそのまま通しません。アートツールの使い方は他の本に十分書かれているので、本章はそのツールをプランナーのパイプラインに安全に組み込む位置だけに集中します。
アートアセットがコンセプトからインゲームまで進む道のりは7段階です。著者のプロジェクト(以下「プロジェクトA」)のキャラクターアセットラインをそのまま書き写すと、次のようになります。重要なのは段階の数ではなく、その真ん中を貫く可逆/不可逆の境界線です。
flowchart TB
subgraph 가역["可逆 — 捨ててもコスト0(AI量産を積極活用)"]
direction LR
C1["1 コンセプト
2Dイラスト"] --> C2["2 モデルシート
正面・側面・背面"]
C2 --> C3["3 3Dモデリング"]
C3 --> C4["4 テクスチャー
マテリアル量産"]
end
가역 -.->|"不可逆ゲート
人によるレビュー通過必須"| 비가역
subgraph 비가역["不可逆 — 戻すには再作業・再収録・再配布"]
direction LR
I5["5 リギング・スキニング"] --> I6["6 アニメーション
モーションキャプチャー"]
I6 --> I7["7 インゲーム統合
最終レンダリング・ライブ公開"]
end
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
class C1,C2,C3,C4 ai;
左の4段階(コンセプト〜テクスチャー)は可逆です。コンセプトを100枚生成して97枚を捨てても失うのはトークンコストだけですし、テクスチャーを5回生成し直してもファイルを上書きすれば終わりです。だからこの区間は、AI量産がもっとも大きなROI(Return on Investment、投資対効果)を出す場所です。量産ツールは、セルフホスティングするStable Diffusion(SDXL)/ComfyUIが主軸です。理由はIP保護です — 資産を外部のクローズドなサービスにアップロードせずローカルで動かし、キャラクターでファインチューニングしたLoRAとControlNetで、同じ人物の一貫性を生成のたびにコントロールできます。クローズド型ツール(Midjourneyなど)は初期のムードボードを素早く敷くときだけ限定的に使い、一貫性・反復コントロールが必要な本番の量産はSD/ComfyUIに持ち込みます。
右の3段階(リギング以降)は不可逆です。モーションキャプチャーはスタジオと俳優のスケジュールに縛られ、最終レンダリングがビルドに載ってライブに公開されれば、ユーザーの記憶とコミュニティの反応がついて回ります。一度越えてしまえば、戻すコストが作るコストより大きくなります。だから境界線の上に人が守るゲートが立ちます。AIが可逆区間でどれだけ量産しても、不可逆へ渡るアセットは人によるレビューを通過したものだけです。
この1枚の図が本章の骨格です。「AIをアートにどこまで使うか」という問いは、実は「この作業は境界線のどちら側か」という問いなのです。
可逆区間の最初の段階であるコンセプト量産を、1サイクル最後まで見せます。抽象的に「AIがコンセプトを出す」とだけ書いては、何が実際に出てきて何が廃棄されるのか分かりません。以下はプロジェクトAで学者ギルドのシニアNPCのコンセプトを量産したセッションを忠実に再現したものです。プロンプトはそのままコピーして使えますし、出力は実際のセッションを再構成したものです。
ここでもっとも頻繁に間違える場所があります。プロンプトを「ビジュアル描写」から始めることです。会社のフィードバックatom image_prompt_design_intent_firstが釘を刺している原則は正反対です — 画像プロンプトも設計意図が先です。外見の形容詞を並べるのではなく、このキャラクターがゲームの中でどんな機能・ナラティブを背負うのかを先頭に置きます。
# concept_brief_scholar_senior.yaml — コンセプト量産の入力
asset_id: npc_scholar_senior_01
role: 学者ギルドのシニア — 封印弱化を初めて観測した人物
function: メインクエスト発注NPC (プレイヤーが信頼すべき情報源)
narrative_seed:
- 30年間鐘楼で封印の脈を記録してきた人
- 感情を数字の裏に隠す (scholarly_strict トーン)
style_anchor: semi-realistic, painted, 東アジアファンタジー # L0ビジョンで固定
forbidden: anime スタイル · 現代の衣服 · 一般的なファンタジー魔法使いローブ
functionとnarrative_seedが外見より先です。「なぜこのキャラクターはこういう見た目でなければならないのか」を入力が持っていてこそ、量産結果から「なぜこちらの方が良いのか」を判断できます。
添付した concept_brief をもとに、キャラクターコンセプトの方向提案を6案生成せよ。
これは探索用の量産だ — 最終ではなく、アートディレクターが選ぶ候補だ。
ルール:
1) function と narrative_seed を視覚に翻訳せよ。単なる美形は禁止。
(例: 「感情を数字の裏に隠す」 → 表情・小物・姿勢でどう表れるか)
2) style_anchor から外れるな。forbidden 項目は絶対禁止。
3) 6案は互いに十分異なること。微細なバリエーション6枚に探索価値はない。
4) 各案に一行の意図根拠を付けよ (なぜこの方向が function に合うのか)。
5) お前が「style_anchor と衝突しうる」と疑う案は別途表示せよ。
出力形式: (案番号 | 核心ビジュアル | 意図根拠 | 衝突疑い)。それ以外の説明は禁止。
このプロンプトでビジュアル描写が占める分量はほとんどありません。大部分は、出力形式を固定し禁止事項に釘を刺すことに使われています。AIに自由を与えると、学習データの平均 — 一般的なファンタジーの魔法使い — へ回帰してしまうからです。意図翻訳の義務、style_anchorの固定、6案の多様性の強制、そして「衝突したら申告せよ」という逃げ道が、回帰を防ぐ4本のかんぬきです。
| 案 | 核心ビジュアル | 意図根拠 | 衝突 |
|----|--------------------------------------|-----------------------------------|------|
| 1 | 真鍮の拡大鏡を恒久装着、インク染みの指 | 観測こそアイデンティティ — 道具の身体化 | |
| 2 | 表情のない老学者、手に計器盤の巻物 | 感情を数字の裏に隠す = 無表情+記録物 | |
| 3 | 鐘楼の背景、背後で冷えていく封印の脈の光 | 職務空間が人物を説明 | |
| 4 | 華やかな星刺繍のローブ、手に魔力の球体 | 威厳の強調 | ★ |
| 5 | 片目が義眼、生涯の観測の痕跡 | 30年の職務の身体的代償 | |
| 6 | 若く端正な学者、美形の強調 | 好感度優先 | ★ |
出力の中でもっとも価値があるのは表ではなく、★印の2行です。4案(華やかな魔法使いのローブ、魔力の球体)と6案(美形優先)に、AIが自ら「style_anchor・forbiddenと衝突する可能性がある」と申告しました。ルール5の逃げ道がなかったら、この2案は他の4案と同じ顔で表に紛れ込んでいたはずです。疑わしい箇所をAI自身に手を挙げて表示させること — それが自由放任の量産と制御された量産を分けます。
この出力をそのまま受け取りません。アートディレクターが6案をbriefに照らして一度チェックします。実際このセッションでは、判定は次のように分かれました。
forbidden: 일반 판타지 마법사 로브への正面からの違反です。このキャラクターは魔法を使う人ではなく、魔力を観測・記録する人です。functionの誤訳です。narrative_seed: 30년 직무의 신체 대가と食い違います。このNPCの説得力は「長くやってきた人」の摩耗から生まれます。若くきれいな顔はナラティブを削ります。ここでの廃棄2件は損失ではありません。手で描いていたらこの2方向が間違いだと分かるまでに数日かかったはずのところを、量産が6案を同時に広げ、1時間以内にふるい落としました。
1案(拡大鏡の身体化)と5案(義眼)の方向を統合せよ。
- 真鍮の拡大鏡 + 片目の義眼を一人の人物に統合
- 感情抑制(scholarly_strict): 表情は無、小物だけで職務を語る
- forbidden 再確認: 魔法使いローブ・魔力の球体・美形の強調はすべて禁止
これはアートディレクターが手作業の仕上げに渡す「最終候補1案」を作る段階だ。
AIは拡大鏡と義眼を一人の老学者に統合した単一の方向を改めて答え、その1枚がコンセプトアーティストの机に渡り、手作業で仕上げられました。量産(6案)→ 廃棄(2案)→ 収束(1案)→ 人による仕上げという1サイクルがここで閉じます。AIが作ったのは最終アセットではなく、アートディレクターが選ぶ候補の幅でした。
この一巡が、本書全体のShow基準です。AIが何を吐き出し、何が廃棄され、人が何を仕上げるのかを一度でも最後まで見なければ、「AIでコンセプトを量産した」という文は空虚です。
上のセッションでは6案中2案が廃棄されました。コンセプトライン全体で見れば、廃棄はさらに積み上がります。会議室の壁に貼った100枚のうち、採用は3枚でした。
この比率を正直に扱っておきます。これは導入初期のコンセプトセッション数件を直接カウントした方向性の値であって、精密な母集団比率ではありません(著者の推定、未検証 — キャラクターの性格やブリーフの品質によって大きくぶれます)。したがって「正確に何%」ではなく、「手作業の時期より廃棄をはるかに自由にできるようになった」という方向で読むのが正しいのです。
重要なのは、廃棄率0%が目標ではないという点です。紙1枚が高ければ、その1枚をとことん磨き込みます。紙100枚が30秒で印刷されるなら、99枚を捨てても負担はなく、その分だけ探索の幅が広がります。廃棄率が上がるのは探索の深さが深まっているという信号です。廃棄率そのものを減らそうとする運用は — たとえば「AIが出したものはなるべく使おう」という圧力は — 探索の価値も一緒に削ります。§12.1.2で4案・6案をためらいなく捨てられたのは、捨てるコストが0だったからです。
コンセプトと並んで、可逆区間でROIが大きいもう一つの持ち場がテクスチャーです。3Dモデルに着せるマテリアルを生成する段階ですが、ここでもAIが入る欄と決定論が受け持つ欄がはっきり分かれます。
flowchart TD
A["UV unwrap
(人)"] --> B["ベーステクスチャー
(AI生成またはペインティング)"]
B --> C["ノーマル・ラフネス・メタリック
決定論的抽出 (Materializeなど)"]
C --> D["エンジンimport +
ライティングプレビュー"]
D --> E{"アートディレクターによるレビュー
(可逆 — 再生成自由)"}
E -->|"トーン不一致"| B
E -->|"通過"| F["アセットID・マテリアルキー登録
(L3 マスターデータ)"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class C code;
class B ai;
class A,E human;
class F data;
AIが入るのはベーステクスチャーの1欄だけです。ノーマル・ラフネス・メタリックのようなPBRマップは、AIに毎回違うものを出させるのではなく、決定論的な抽出ツールが受け持ちます。同じベースから同じマップが出てこそ、マテリアルが一貫するからです。これは§6.2の都市ジェネレーターで、報酬カーブをAIに任せずルールブックが押さえていたのと同じ分担です — 決定論で保証できるものはコードが、探索が必要なものはAIが。
ベーステクスチャーでさえ、すべてのアセットにAIが適しているわけではありません。キャラクターの顔のように、微細なディテールがゲームのアイデンティティを左右する場所では、依然として人の手が優先です。だからレビューゲートが「トーン不一致」を捕まえたら、自動廃棄ではなく再生成へ戻します。ここまでが全部、境界線の左側 — 何度やり直しても失うもののない可逆区間です。
境界線を越える直前に、可逆区間で量産されたアセットがゲーム全体のトーンと食い違っていないかを検査します。これは人の目だけでは漏れが出る場所なので、コードが1次チェックを担います。
# visual_regression.py — アセット差し替え時の意図外変化の検出 (骨格)
# 入力: アセットID + 差し替え前/後の同一条件レンダーキャプチャ
# 出力: 変化等級 (人の検収ゲートへ alert)
def compare_renders(asset_id, before_png, after_png, threshold=(1.0, 5.0)):
diff = pixel_diff(before_png, after_png) # 0~100 正規化
if diff > threshold[1]:
return ("BLOCK", f"{asset_id}: 大きな変化 {diff:.1f}% — 検収前の不可逆進入は禁止")
elif diff > threshold[0]:
return ("WARN", f"{asset_id}: 軽微な変化 {diff:.1f}% — 意図の確認が必要")
else:
return ("PASS", f"{asset_id}: 変化なし")
この30行が、「テクスチャーを1枚差し替えたら別のキャラクターの影が壊れた」という事故を、不可逆への進入前に捕まえます。重要な設計は、BLOCKが自動廃棄ではなくレビューゲートにalertを上げるだけだという点です — 意図された変更(リデザイン)までコードが殺してしまうと、作家たちは1〜2四半期のうちに「オフにしよう」と言い出します。疑わしい候補は機械が拾い、不可逆へ渡すかどうかは人が決めます。
レビューが捕まえるもう一つはスタイルの一貫性です。AIの出力は毎回微妙に違うため、量産されたコンセプト・テクスチャーがゲームのトーンを保っているかを、人が最後に見ます。このゲートを通過したものだけが、リギング・モーションキャプチャー・最終レンダリングという不可逆の段階へ進みます。一度モーションをキャプチャーしてビルドに載せてしまえば、一貫性の事故は再作業・再収録・再配布でしか直せないからです。
プランナーがAIでコンセプト・テクスチャーを量産しても、実際に絵を描くアートチームは別組織です。ここでの協業の核心は、アートチームが企画チームのツールやコンベンションを学ぶ必要がないようにすることです。プロジェクトAのアートガイド(96_ArtGuide/)は、これを自動化で解決します。
アートの決定事項は企画チームがmdで書き、_convert_md_to_html.pyがhtmlに変換した後、_SyncToArtRepo.batが別のアートリポジトリへpushします。アートチームはそのリポジトリでhtmlだけを見ます — mdのコンベンションも、企画チームのSVNも知らなくてよいのです(パイプラインの図は§12.2.4)。
そしてこの決定文書は7つのドメイン(00_Common・01_Character〜07_Env)に分かれ、それぞれが自分のスタイルルールを持って統合ゲートで合流します。これが次章(12.2)で扱うArtGuideの7領域ですが、核心だけ先に言うとこうです — スタイルのルールブックを1つの枠ではなく7つの引き出しに分けておけば、AI量産のプロンプトが毎回作家の頭の中で新しく組み立てられるのではなく、引き出しから取り出せるようになります。§12.1.2のstyle_anchor・forbiddenが、まさにその引き出しから出てきた入力です。ルールブックが分離されていてこそ、量産結果が一般的なファンタジーの平均へ回帰しません。
だからといって、すべてのゲームが7領域を全部備えるべきというわけではありません。カジュアルなジャンルなら、キャラクター・環境の2枠でも十分です。分離は漸進的に、インターフェースは狭く。
本章の数値は3種類だけです。(1) 方向・比率 — 「100枚量産して採用3枚」は著者の経験に基づく方向性の値(未検証)なので、絶対値ではなく「可逆区間では廃棄コストが0に収束する」という方向として読みます。(2) 測定値 — ビジュアルリグレッションの変化率(diff %)、一貫性事故の件数、BLOCK処理の件数はvisual_regression.pyが数字で吐き出すので、会議で「感覚」の代わりに数字で話せます。一方、「継続率(リテンション)が上がった」はアート一つで左右されるものではないので、因果を断定しません。
(3) リスクは運用コストの中に置きます。AIアートの3つのリスク — 学習データの著作権、スタイル一貫性の毀損、アーティストの雇用 — は、ROI計算の外ではなく中にあります。著者の方針は、可逆区間ではAIを積極活用し、不可逆へ渡す最終アセットは手作業で仕上げる、そしてビルドに直接入るアセットのAI出力比率は0を原則とする、というものです。ただし、これは一つのポリシーにすぎません — ライセンスが明示されたモデルだけを使い、最終アセットまでAIを活用するチームもあります。法務ポリシーは会社ごとに違い、本書は正解ではなく境界線の引き方を提示します。
3つのリスクのうち、もっとも見落とされやすいのは3つ目です。AIを「アーティストを置き換える量産機」ではなく「探索の幅を広げてアーティストの決定権を強める補助」として位置づけなければ、ツールはKPI上は成功しても組織から拒否されます。これはfeedback atom design_intent_vs_automation_boundary(設計意図 vs 自動化境界)が釘を刺した場所でもあります。
| パターン | なぜ失敗するのか | 処方 |
|---|---|---|
| AIコンセプトを最終アセットとして直接ビルドに投入 | 不可逆の段階を人によるレビューなしで通過 | 境界線の前にゲート(§12.1.1) |
| プロンプトを外見描写から始める | functionの誤訳 — 美形の魔法使いへ回帰 | 設計意図を先に(§12.1.2、image_prompt_design_intent_first) |
| 量産6案が微細なバリエーション | 探索価値なし、廃棄するものがない | 多様性の強制(§12.1.2) |
| 廃棄率を減らそうとする | 探索の深さも一緒に削る | 可逆区間の廃棄は信号と見る(§12.1.3) |
| テクスチャーのPBRマップまでAI生成 | マテリアルの一貫性が呼び出しのたびに揺れる | 決定論的抽出の分離(§12.1.4) |
| ビジュアルリグレッションなしでアセット差し替え | 意図外の変化が不可逆へ漏れる | visual_regression.pyゲート(§12.1.5) |
一人ならこれだけ:アートチームもマスターデータもなくて構いません。自分のゲーム(または好きなゲーム)のNPCを一人選び、§12.1.2の
concept_brief形式でfunctionとnarrative_seedを外見より先に書き、6案量産プロンプトをそのまま貼り付けて一度回してみましょう。出てきた6案のうち意図と食い違う1案を選んで「これはfunctionの誤訳だ、廃棄してやり直し」と反論してみると、可逆区間の廃棄が損失ではなく探索だということが体に入ってきます。
チームなら、次の一歩から始めましょう。パイプラインに可逆/不可逆の境界線を明示的に1本引きます(§12.1.1)。どの段階までが「捨てても0」で、どこからが「戻すと高くつく」のかを合意し、その境界の上に人によるレビューゲートを置きます。境界が引かれれば、「AIをどこまで使うか」という毎回ゼロから始まる戦いが、「この作業は境界のどちら側か」という一度の判定に変わります。
setup → prompt → verifyで要約すると — setup: パイプラインに可逆/不可逆の境界線とレビューゲートを定義します。prompt: §12.1.2の形式で設計意図を先に入力し、6案を量産しつつ禁止事項・多様性・申告を強制します。verify: 可逆区間で意図の誤訳1件を自分で選んで廃棄・再依頼の1サイクルを閉じ、不可逆への進入前にvisual_regression.pyで意図外の変化をチェックします。
木曜日の統合レビュー。同じ画面に新規アセットを7つ並べて見た瞬間、私たちは同時に笑ってしまいました。学者キャラクターはグレートーンの重厚なシルエットなのに、その隣で炸裂するスキルVFXが蛍光ピンクだったのです。どちらも、それぞれの領域では完璧な決定でした。キャラクターディレクターは自分の_STYLE_GUIDE.mdをそのまま守っていましたし、VFXアーティストも「よく目立つように」という私の仕様を忠実に守ってくれていました。誰も間違っていないのに、同じ画面に置くと2つのゲームが喧嘩していたのです。
この場面こそ、ArtGuideを7領域に分割する理由であり、同時に7領域を再び束ねなければならない理由でもあります。ArtGuideはゲームのビジュアル憲法です。領域に分ければ分野別のディレクターが自治を持ち、決定が速くなります。しかし統合レビューで再び束ねなければ、先ほどの蛍光ピンクのような事故が四半期ごとに積み上がります。プランナーがこのバランスのどの地点に手を入れるのか。それがこの章のすべてです。
著者がディレクターとして働いたプロジェクトA(東洋ファンタジー調のモバイルファーストMMORPG)のデザインリポジトリには、96_ArtGuide/というフォルダがあります。番号96はリポジトリの整列規則上、アートガイドがほぼ最後に来るように付けたもので、その下が7つのドメインに分かれます。抽象的な「プロジェクトのアートフォルダ」ではなく、以下がそのフォルダの実際のサブ構造です。
図のポイントは2つです。第一に、7つのドメインが横並びで対等に自治を持ちます。1つのフロアに7つの作業室が並んだオフィスを思い浮かべてください。各部屋の責任者がその部屋の決定権を握りつつ、廊下ですれ違うときに「同じゲームだ」という感覚は失わないようにしなければなりません。第二に、その上に00_Commonが載っています。7つの部屋すべてが従うべき共通規約、すなわち全体のカラーパレットと質感の基準と時代のトーンがここに住んでいます。06_UIは9.1.3で扱ったUI協業標準と同じドメインなので、この章では境界線だけを引いて先へ進みます。
7つのドメインに、プランナーが同じ強度で関与するわけではありません。プランナーは意図とナラティブを決定し、アートはビジュアルを決定するという原則はすべてのドメインで同じですが、意図がビジュアルをどこまで引っ張っていくかはドメインごとに異なります。
| 領域 | プランナーの関与 | プランナーが越えてはならない一線 |
|---|---|---|
| 01_Character | 強い | コンセプト・性格・勢力・役割まで。顔の比率や筆のタッチは範囲外 |
| 02_Animation | 中程度 | スキルモーションの「種類・リアクション」まで。フレームのタイミングは範囲外 |
| 03_Monster | 強い | 敵のコンセプト・勢力・生態まで。鱗のパターンのディテールは範囲外 |
| 04_NPC | 強い | 役割・関係・voice_profileまで。衣装の刺繍は範囲外 |
| 05_VFX | 弱い | 「ゆっくり発射、大きな爆発、紫色」まで。パーティクル数は範囲外 |
| 06_UI | 強い | 情報構造・優先順位まで(9.3)。ピクセル単位の余白は範囲外 |
| 07_Environment | 中程度 | 雰囲気・ランドマークの意図まで。木のポリゴンは範囲外 |
右側の列がこの表の本当の中身です。関与が「強い」と書かれたドメインでも、プランナーが越えてはならない一線があります。キャラクターのコンセプトは強く引っ張りつつも、顔の比率にまで手を出せば、その瞬間キャラクターディレクターの自治が崩れます。そして、強いと弱いの境界そのものがジャンルによって揺れます。ホラーゲームならVFXが恐怖の核心なのでプランナーの関与は強くなり、カジュアルパズルならキャラクターへの関与はむしろ弱くなります。上の表はプロジェクトAのジャンルを基準にしたものであって、普遍的な法則ではありません。
各ドメインは、標準のドキュメント一式で運営します。01_Character/ドメインの実際のファイル構成を見てみましょう。
01_Character/
├── _STYLE_GUIDE.md — キャラクター全体スタイル (憲法)
├── _COLOR_PALETTE.md — 色·材質ガイド
├── _PROPORTION_REFERENCE.md — 比率·シルエットのルール
├── _DO_AND_DONT.md — 許可·禁止
├── individual/ — キャラクター別シート
│ ├── K_001_director.md
│ ├── K_007_scholar.md
│ └── ...
└── _REVIEW_LOG.md — レビュー履歴
_STYLE_GUIDE.mdがドメインの憲法です。個別のキャラクターシート(individual/)は、すべてこの憲法の上で変奏されます。憲法が揺らげばその下のすべてのキャラクターが揺らぐため、このファイル1枚がドメインで最も頻繁にレビューされるドキュメントです。骨格は次のとおりです。
---
title: 01_Character Style Guide
layer: L1
---
## 1. トーン
- 19世紀産業革命以前、韓国ファンタジーの雰囲気
- 写実的な比率 (7~7.5頭身、デフォルメ禁止)
## 2. 色
- 彩度: 普通 (実写の60~70%水準)
- メインパレット: 00_Common 継承
- キャラクター別アクセント色 (1~2個)
## 3. 衣装ルール
- 勢力別の衣装区分 (学者 → グレー + 紫アクセント)
- 職業·階級に応じた衣装ディテール
## 4. DO
- 5m距離からシルエットだけで誰か識別可能
- 勢力アイデンティティを視覚で表現
## 5. DON'T
- 日本アニメスタイル
- 非時代劇要素 (現代の衣装·小物)
- 彩度過多
ここで1行が重要です。## 2. 색상(「2. 色」)にある「메인 팔레트: 00_Common 상속」、すなわち「メインパレットは00_Commonを継承する」という行です。キャラクタードメインが色を独自に決めるのではなく、上位の共通規約を受け継ぐという明示です。この1行こそ、冒頭の蛍光ピンク事故を構造的に防ぐ仕掛けです。すべてのドメインの_STYLE_GUIDE.mdが、色だけは00_Commonを継承するようにすれば、少なくとも色の衝突は憲法の段階で遮断されます。
ここで、プロジェクトAが実際にぶつかった最も現実的な問題が登場します。アートチームはMarkdown(マークダウン)を読みません。正確に言えば、読めと強要してはいけません。アーティストにgit diffとfrontmatterとMarkdownの見出し階層を学習させるコストは、その学習で得られる協業効率より、ほぼ常に大きいのです。企画チームのツールをアートチームにそのまま突きつけた瞬間、協業はむしろ遅くなります。
そこでプロジェクトAのパイプラインは、「企画チームはmdで決定し、アートチームはhtmlだけを見る」という1行に要約されます。
flowchart LR
A["企画チーム: ArtGuideを決定
(_STYLE_GUIDE.mdを更新)"] --> B["_convert_md_to_html.py
(md → 読みやすいhtml)"]
B --> C["_SyncToArtRepo.bat
(別のアートSVNへpush)"]
C --> D["アートチーム: htmlのみ閲覧
(md学習0)"]
D -. フィードバック .-> A
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
class B,C code;
class A,D human;
鍵になるのは2つの自動化アセットです。_convert_md_to_html.pyは、ドメインのMarkdownガイドを、アーティストがブラウザで快適に読めるhtmlに変換します。カラーパレットは実際の色チップとして、DO/DON'Tは視覚的な対比としてレンダリングされます。_SyncToArtRepo.batは、そのhtmlを企画リポジトリではなく別のアート専用リポジトリへ送り込みます。リポジトリを分離する理由は単純です。アーティストが自分のリポジトリだけを受け取るようにすれば、企画チームの内部mdの履歴、作業中のドラフト、ほかのドメインの決定過程にさらされません。アーティストが見るのは、確定した決定の読みやすい成果物だけです。Markdownの学習コストが0に落ちます。
この構造で、プランナーには責任が1つ追加されます。mdを更新したら、必ず変換・同期のステップを回さなければなりません。更新だけして同期を抜かすと、アートチームは昨日の決定で今日の絵を描くことになります。決定と伝達の間のこの1マスが空いていると、自治も統合も意味を持ちません。
非プランナーとの協業の延長線上で、コンセプト段階に生成AIを使うときにプランナーが守るべき原則が1つあります。プロジェクトAの内部規約名ではimage_prompt_design_intent_first、かみ砕いて言えば「画像プロンプトにも設計意図を先に書く」です。
生成画像でコンセプトを探索するときによくある失敗は、プロンプトが成果物の外見描写だけで埋まることです。「灰色の道袍(トポ:韓国の伝統的な外衣)をまとった50代の東洋人男性、落ち着いた表情、実写風」のように。このプロンプトは絵こそ出力しますが、なぜそうあるべきかを含んでいないため、アートディレクターがその絵を変奏するときに道に迷います。設計意図優先の原則は、プロンプトの前に意図ブロックを強制します。
[設計意図]
- 役割: 学者勢力の精神的支柱、プレイヤーの最初のメンター
- 読み取られるべきもの: 5m距離でも「知識人·非戦闘」シルエット
- 勢力シグナル: 学者 = グレー + 紫アクセント (00_Common 継承)
- 禁止: 武器携帯、華やかな甲冑 (戦闘職と誤読される)
[プロンプト]
グレーの道袍の50代東洋人男性学者、紫色のオッコルム(結び紐)アクセント、
武器なし、落ち着いた学究的な表情、19世紀以前の韓国ファンタジー、
実写比率7.5頭身、彩度普通、...
意図ブロックがプロンプトの上にあれば、その絵は決定の一部になります。次の人が同じキャラクターの別のポーズを出力するとき、外見をなぞるのではなく、意図をあらためて満たすことになります。画像が気に入らず破棄するときも、「意図のうち何が読み取れなかったのか」で議論できます。外見だけが書かれたプロンプトは「自分の好みではこちらの方がいい」で終わり、検証する根拠を残しませんが、意図が先に立つプロンプトは、何が満たされ何が欠けたのかを検討できる記録を残します。
ここでツールの選択が、意図優先の原則とかみ合います。「同じキャラクターの別のポーズ」を意図どおりに繰り返し生成するには、プロンプトだけでは足りません。プロジェクトAのツール分担は§12.1.1のとおりです。本格的な量産はセルフホスティングのSD(SDXL)/ComfyUIにキャラクターLoRA(顔・衣装の固定)とControlNet(ポーズ・シルエット)をかけ、意図ブロックが要求する「5m距離での識別性」をポーズが変わっても維持し、IPも一緒に守ります。クローズド型ツール(Midjourneyなど)は、初期のムードボード程度にだけ使います。
ドメインの自治とプランナーの関与が実際にどう回っているのか、01_Characterの1サイクルを最初から最後までたどってみます。以下は、著者が学者キャラクター(K_007_scholar)のコンセプト仕様のドラフトをAIに任せた、実際の作業の再構成です。要約せず、ぎこちなかった出力と著者の拒否まで、そのまま掲載します。
① 著者がAIに投げたプロンプト(全文)
君は我々のプロジェクトのキャラクターコンセプト補助作家だ。以下の規約を守ってK_007_scholarの
コンセプト仕様書のドラフトをマークダウンで書け。
規約:
- 01_Character/_STYLE_GUIDE.md 準拠: 19世紀以前の韓国ファンタジー、7~7.5頭身、
彩度普通、日本アニメスタイル禁止。
- 色は00_Commonパレットを継承し、学者勢力のアクセントは紫。
- voice_profile 5項目(語調/口癖/禁止語/感情デフォルト/関係呼称)を必ず含める。
- コンセプト·性格·勢力·役割までだけを決定し、顔の比率や筆のタッチのような視覚ディテールは
空けておけ(キャラクターディレクターの自治領域)。
- 出力は individual/ キャラクターシート形式。
② Claudeの生の出力(失敗を含む、手を加えていない)
# K_007 学者 (Scholar)
## 役割
学者勢力の精神的支柱であり、プレイヤーの最初のメンター。
## 外見
- 身長180cm、7.5頭身、痩せ型
- 白い髭、深いしわ、炯々とした眼光
- グレーの道袍に紫のオッコルム(結び紐)
- ★ 推奨: かすかに輝く金色の瞳で神秘感を強調
## 性格
博学だが傲慢ではなく、弟子に厳格だが温かい。
## voice_profile
- 語調: ゆっくりと穏やか
- 口癖: 「...そうじゃな」、文末を長く
- 感情デフォルト: 平静
- 関係呼称: プレイヤーを「若き友」と呼ぶ
③ 著者の検証・拒否(どこがなぜ間違っていたのか)
出力には2か所の欠陥がありました。第一に、## 외형(外見)の「★ 추천: 살짝 빛나는 황금빛 눈동자」(推奨:かすかに輝く金色の瞳)という行。これは明白な越権です。瞳の色のようなビジュアルのディテールはキャラクターディレクターの自治領域なのに、AIがコンセプト段階で釘を打ってしまいました。そのまま放置すれば、ディレクターが「企画で決まったことだから」と自分の判断を引っ込めるか、無視して衝突するかのどちらかです。第二に、voice_profileが5項目ではなく4項目です。「禁止語」が丸ごと抜けています。メンターキャラクターがどんな言葉を絶対に口にしないのかは性格と同じくらい重要なのに、AIが欠落させました。
④ 再リクエスト(正確に2か所だけ修正)
二つだけ直せ。
1. ## 外見から「金色の瞳」の推奨行を削除しろ。瞳の色はキャラクター
ディレクターの決定領域だ。外見項目はシルエット·体型·勢力色までだけを書き、
細部の色·材質は「(ディレクター決定)」として空けておけ。
2. voice_profile に抜けている「禁止語」項目を追加しろ。学者メンターとして
言わない言葉(悪態、下品な冗談、現代語)を明示しろ。
このサイクルの教訓はツールではなく境界です。AIは素早く、もっともらしいドラフトをくれましたが、プランナーが越えてはならない一線(ビジュアルのディテール)を代わりに越えてしまい、必ず入れるべきもの(禁止語)を落としました。検証の基準は「うまく書けているか」ではなく「ドメイン自治の境界を守っているか」でした。AIをキャラクターコンセプトに使う限り、この境界のチェックは人が最後まで握っていなければなりません。
7つのドメインが自治を持つと、冒頭の蛍光ピンクのように、ドメイン間で不一致が生まれます。よく出るパターンは決まっています。
| 不一致のパターン | 実際の例 |
|---|---|
| キャラクターと環境のトーン差 | キャラクターは重厚なのに背景が華やかでちぐはぐに見える |
| キャラクターとVFXの色彩衝突 | キャラクターはグレートーン、スキルVFXは蛍光ピンク |
| NPCとMonsterの境界が曖昧 | 友好NPCなのにモンスターのように威圧的に見える |
| UIとキャラクターの色味の不一致 | UIは冷たいトーン、キャラクターは温かいトーン |
この不一致を捕まえる仕掛けが、週1回の統合レビューです。手順は単純です。
週1回 ArtGuide 統合レビュー (木曜日)
─────────────────────────────────
1. その週の新規アセット5~10個をランダム抽出
2. 同じ画面に一緒に配置 (インゲームシミュレーション)
3. 七つのドメインディレクター + ゲームディレクターが同時検収
4. 不一致を発見 → 当該ドメイン _STYLE_GUIDE 補強
または 00_Common 上位規約を補強
鍵は3番と4番です。レビューをドメインディレクターたちが同時に行うこと、そして発見された不一致を、個別アセットを直して終わりにするのではなく、ガイドドキュメントへ還元することです。冒頭の蛍光ピンク事故は、そのアセット1つをグレーに変えて終わらせると、翌週に同じ形で再発します。代わりに00_Commonへ「スキルVFXの彩度はキャラクターパレットの彩度+20%以内」という規約を追加すれば、同じ事故が構造的に閉じられます。月4回積み重なるこのサイクルこそ、自治がサイロに固まるのを防ぐ唯一のガードレールです。
7領域分離がもたらした変化を、プロジェクトAの運営経験から整理すると次のとおりです。以下の数値のうちサイクル日数と時間は、著者の運営経験に基づく著者の推定(未検証)であり、正確な測定値ではなく、分離前後の方向とおおよその比率としてだけ読むべきものです。
| 項目 | 分離前 | 分離後 | 性格 |
|---|---|---|---|
| アート決定サイクル | 1〜2週間 | 3〜5日 | 著者の推定(未検証) |
| 領域間の一貫性事故 | 四半期ごとに複数件 | 顕著に減少 | 方向のみ |
| ゲームディレクターのアートレビュー時間 | 週あたり多くの時間 | 大幅に減少 | 方向のみ |
| 新規領域ディレクターのオンボーディング | 数か月 | 約1か月 | 著者の推定(未検証) |
| アートアセットの破棄率 | 高い | 減少 | 方向のみ |
表を正直に読めば、断言できるのは、すべての項目が同じ方向に動いたという事実だけです。最も明確な効果は、ゲームディレクターの時間が回収されたことです。自治のない構造では、すべてのアート決定がゲームディレクター1人の机を経由しなければなりませんでした。1つの机に書類が積み上がる構造です。7領域に分けると、書類は7つの机に分散されました。書類の総量は同じですが、どの机も潰れません。
ここで、最もよくある誤解を断ち切らなければなりません。自治は責任の分散ではありません。時間の再配分です。ドメインディレクターが自分の領域を決定するからといって、ゲーム全体のビジュアルの責任が7つの欠片に散らばるわけではありません。統合レビューでその7つが再び1か所に集まり、最終的なビジュアルの責任は依然として1人に収束します。自治を責任回避の口実にした瞬間、つまり「それは私のドメインの外なので」が口癖になった瞬間、冒頭の蛍光ピンクは誰の問題でもないままビルドに入ります。
そして1つ、ただし書きがあります。7領域の自治は規模の関数です。小規模(〜10人)のチームでは、むしろオーバーエンジニアリングです。ディレクター1人が帽子を5つかぶっている段階なら、ドメインガイド7枚ではなく統合ガイド1枚で十分です。自治は、机が足りなくなったときに初めて値打ちを発揮します。
| パターン | 処方箋 |
|---|---|
| 領域を分離せず、すべての決定をゲームディレクターに集中させる | 7領域の自治を導入(ただし中規模(10〜50人)から) |
| ドメインの_STYLE_GUIDEが不在 | 各ドメインの憲法作成をゲートとして強制する |
| 領域間の統合検証がない | 週1回の統合レビュー+ガイドへの還元 |
| プランナーがビジュアルのディテールまで決定する | 意図・ナラティブの仕様までにとどめ、ディテールはディレクターへ |
| 自治がサイロに固まる | 統合レビューで00_Commonへ還元する |
| md更新後の同期漏れ | _convert_md_to_html.py→_SyncToArtRepo.batを習慣化する |
| 画像プロンプトが外見描写だけ | 設計意図ブロックを先行させる(image_prompt_design_intent_first) |
setup — 最も手のかかるドメイン1つ(普通は01_Character)から始めましょう。ドメインフォルダに_STYLE_GUIDE.md(憲法)、_COLOR_PALETTE.md、_DO_AND_DONT.md、individual/を作ります。色の項目には、必ず「メインパレット:00_Common継承」という1行を入れておきましょう。
prompt — 個別のアセットシートをAIでドラフトするときは、プロンプトに(1)当該ドメインの_STYLE_GUIDE.mdの規約、(2)「コンセプト・ナラティブまでだけを決定し、ビジュアルのディテールは『(ディレクター決定)』として空けておくこと」、(3)抜けてはならない必須項目(例:voice_profileの5項目)を明記します。画像プロンプトなら、外見描写の上に[설계 의도](設計意図)ブロックを先に書きます。
verify — 出力を受け取ったら、「うまく書けているか」ではなく「ドメイン自治の境界を守っているか」でチェックします。①プランナーが越えてはならないビジュアルのディテールをAIが代わりに決定していないか、②必須項目が欠落していないか、この2つだけを見ます。ずれた箇所だけをピンポイントで再リクエストします。毎週木曜日、新規アセット5〜10個を1つの画面に集め、ドメインディレクターたちと同時に見ましょう。不一致はアセットではなくガイドドキュメント(00_Commonまたはドメインの_STYLE_GUIDE)へ還元します。
一人ミニ版 — 1人で作るゲームなら、7つのドメインを作らないでください。00_Common1枚にカラーパレット・時代のトーン・DO/DON'Tをまとめておき、そこにキャラクター・環境・VFXのセクションを見出しだけで分けます。AIにアセットを任せるときはその1枚を丸ごとプロンプトに貼り付け、「このガイドに違反している箇所があれば指摘すること」も一緒に指示します。統合レビューは1人で週に1回、その週に作ったものを1つの画面に並べて見る5分の儀式で十分です。自治を分け合う相手がいないだけで、憲法1枚と週1回の整列という骨格は、1人チームでもまったく同じように値打ちを発揮します。
スプリントの終盤、コンセプトアーティストが社内メッセンジャーにキャラクターのラフ案を1枚投げてきました。「これ、学者ギルドのシニアで合ってますよね?」画面の中の人物は30代の男性で、革の鎧を着ていました。仕様書には40代の女性、グレーの学者ガウンと書かれていました。どこで食い違ったのか追跡してみると、コンセプトアーティストが受け取った資料は2か月前のバージョンの仕様書で、その間に外見ガイドは2回変わっていました。変わった事実を知っている人は、プランナー本人だけでした。
この事故は技術の問題ではありません。フローの問題です。仕様書の1ページがゲーム内のアセットになるまで平均4〜8週間。その間、キャラクター1人の情報はプランナーの頭の中からコンセプトアーティストへ、モデラーへ、アニメーターへと、手から手へ渡っていきます。渡す瞬間ごとに様式がずれる可能性があり、ずれたまま受け取ってしまえば、受け取った人は推測で空欄を埋めます。その推測は2か月後、社内メッセンジャーの1行になって返ってきます。
本章では、その手から手へのフローを、一人の記憶ではなくシステムの上に載せる方法を扱います。
プロジェクトAでキャラクターアセットが流れていく道は4段階です。重要なのは段階そのものではなく、段階と段階の間の転換点です。事故は段階の中ではなく、ある段階から次の段階へアセットを渡すその瞬間に起きます。
このフローは文章で説明する代わりに図で示すべきところですが、本書では全24部を通じて、手でボックスを描く代わりにClaudeからmermaidコードを受け取ってレンダリングしてきました。本章は、まさにその技法を適用して描いた結果を本文に載せます。自分の技法を自分の本文で証明するわけです。以下が、Claudeに「spec→assetの4段階フローを、転換点のゲートが見えるようにmermaidで」と依頼して受け取った出力を、そのままレンダリングしたものです。
flowchart TD
A["第1段階・仕様書
character_spec.md"] -->|仕様→ビジュアル転換| G1{ゲート1
外見6項目チェック}
G1 -->|通過| B["第2段階・コンセプトアート
concept_K_001_v3.png"]
G1 -.->|差し戻し| A
B -->|ビジュアル→3D転換| G2{ゲート2
モデルシートチェック}
G2 -->|通過| C["第3段階・3Dアセット
model_K_001.fbx"]
G2 -.->|差し戻し| B
C -->|静的→動的転換| G3{ゲート3
アセットlint}
G3 -->|通過| D["第4段階・インゲーム統合
アニメ・VFX・サウンド・コード"]
G3 -.->|差し戻し| C
D --> G4{ゲート4
総合レビュー}
G4 -->|通過| E["ビルド反映"]
G4 -.->|差し戻し| D
classDef gate fill:#fde2c8,stroke:#d2691e,color:#5a2e00;
classDef asset fill:#dbeafe,stroke:#2563eb,color:#0b2545;
class G1,G2,G3,G4 gate;
class A,B,C,D,E asset;
3つの転換点(仕様→ビジュアル、ビジュアル→3D、静的→動的)ごとにゲートが立っています。ゲートとは、決裁書類を次の部署へ回す前に様式を検査する窓口です。様式が合わなければ差し戻し(点線)となり、前の段階に戻ります。様式の合わない書類を受け取ってしまえば、次の部署は空欄を推測で埋めます。メッセンジャー事故は、ゲート1がないときに起きます。
mermaidの利点がこの図に表れています。ゲートをもう1つ追加したり段階の順序を変えたりするとき、ボックスを描き直すのではなく、テキストを1行直せば済みます。図がテキストだからバージョン管理の対象になり、仕様書の隣に一緒にコミットされます。
フローの出発点は、1枚のMarkdown(マークダウン)仕様書です。この文書が、後に続く3段階すべての入力になります。ここに空欄があれば、その空欄は消えるのではなく次の段階へ押し付けられ、推測に変わります。
以下が、実際に作成しているcharacter_specの様式です。related_atomsフィールドが、この仕様をJIT atomシステム(第11部参照)につなぎます。
---
title: 学者ギルドシニア K_001 キャラクター仕様
type: character_spec
layer: L2
related_atoms: [character_K_001, voice_profile_K_001]
status: draft
---
## 1. アイデンティティ
- 名前: (TBD)
- 役割: 学者ギルドシニア、メインNPC、仲間化可能
- 勢力: scholar_guild
- 性格: 学者_厳格、権威的だが公正
## 2. 外見ガイド
- 年齢: 40代
- 性別: 女性
- 体格: 平均よりやや大きい (170cm相当)
- 衣装: グレー + 紫アクセント、学者ガウン、眼鏡
## 3. 表情・姿勢
- 平時: 沈着、口角は下がり気味
- 怒り時: 沈黙 + 視線の遮断
- 悲しみ: 話題転換、表情変化は微細
## 4. ゲーム内の役割
- メインクエスト chapter 1·5·12 に登場
- サイドクエスト 8件を発注
- 仲間合流 chapter 7
## 5. 音声・セリフ
- voice_profile: scholar_K_001
- 代表セリフ 3つ:
- "それは記録のない話ですか?"
- "感情で決めないでください。データが先です。"
- "...あなたの選択ですね。分かりました。"
## 6. 参考資料
- 実在の学者5名の画像 (legal レビュー完了)
- refgameのキャラクタートーンを参考
外見6項目(セクション2の年齢・性別・体格・衣装、そしてセクション3の表情・姿勢)が、ゲート1のチェックリストになります。メッセンジャー事故の原因だった「40代の女性/グレーのガウン」の2行が、まさにこのセクションです。ゲート1が機能していれば、コンセプトアーティストが受け取った資料の外見6項目が現在の仕様書と一致するか、渡す前に検査されていたはずです。
仕様書をゼロから手ですべて埋める代わりに、アイデンティティと役割だけを決めておき、外見・表情のドラフトをClaudeから受け取ります。以下は、実際の1往復の全文です。要約していません。
プロンプト(全文):
以下のキャラクターのアイデンティティ・役割は確定だ。これをもとにcharacter_specの「2. 外見ガイド」と「3. 表情・姿勢」のドラフトを埋めよ。 - 役割:学者ギルドのシニア、メインNPC、仲間化可能 - 性格:学者_厳格、権威的だが公正 - 勢力:scholar_guild 制約:refgameのトーン(東洋ファンタジー、彩度低め)。外見はシルエットだけで他のメインNPCと区別できること。表情は怒り・悲しみを直接表に出さない抑制型で。
Claudeの生の出力(そのまま、手を加えていません):
2. 外見ガイド
- 年齢:40代後半
- 性別:女性
- 体格:痩せ型、165cm前後(※「権威的」を身長で表現するか姿勢で表現するか要確定 — 私は姿勢の方を推奨します)
- 衣装:濃紺の学者服、銀糸の刺繍(※勢力のカラーパレットが分かりません。scholar_guildの代表色は別にありますか?)
3. 表情・姿勢
- 平時:顎をやや上げた正姿勢、視線は相手よりやや上
- 怒り時:口数が減り、口調だけ冷たくなる
- 悲しみ:窓の外を見る動作で代替、顔はほとんど変化なし
もう1つ。「仲間化可能」なら戦闘中の表情も必要になるはずですが、それはこの仕様の範囲外ですか?
人による検証・拒否:
この出力は半分だけ採用しました。表情・姿勢のセクションはほぼそのまま受け取りました — 抑制型という制約を正確につかんでいて、「窓の外を見る動作で悲しみを代替」は仕様書になかった良いディテールです。外見は拒否しました。理由は2つです。第一に、体格を「痩せ型、165cm」としていますが、確定済みの仕様書は「平均よりやや大きい、170cm」でした。Claudeはアイデンティティ・役割しか受け取っていないので体格を知りようがなく、推測したのです — まさにゲートが止めるべき推測です。第二に、衣装の濃紺はrefgameのトーンには合っていますが、私たちのゲームのscholar_guild代表色(グレー+紫)と衝突します。Claudeが投げた質問(「勢力の代表色は別にありますか?」)が、まさにこの衝突を先回りして突いていました。
再依頼:
良い。表情・姿勢は採用。外見は次の値で固定して整理し直せ。体格 = 平均よりやや大きい170cm、衣装 = グレーの学者ガウン+紫のアクセント(scholar_guildの代表色)、眼鏡着用。戦闘の表情はこの仕様の範囲外なので外せ。
この1往復から学べるのは、Claudeが空欄を推測で埋めたその場所こそが、仕様書の空欄だったということです。分からない値に出会ったとき、Claudeの挙動は2つに分かれました。勢力の色と戦闘の表情は「これは分からない」と質問として持ち上げ、その質問はゲートのチェックリストより先に欠落を突き止めました。一方、体格は分からないという表示なしに、もっともらしい数字で埋めてしまいました。後者がある限り、人が確定済みの仕様書と1行ずつ突き合わせる検証は省略できません。
確定した仕様書がコンセプトアーティストに渡ります。フローは§12.1.2のコンセプトワークフローと同じです。AIで数十〜数百枚を量産し、ひと握りにキュレーションし、1〜3案を手作業で整えたうえでモデルシート(正面・側面・背面)を作ります。
核心は、この段階の終わりに立つゲート1です。モデルシートが第3段階(3D)へ渡る前に、次の5項目を検査します。
| 項目 | 確認基準 |
|---|---|
| 仕様書の外見6項目への適合 | 衣装・体格・年齢・性別・表情・姿勢が現在の仕様書と一致 |
| メインNPC間のシルエット区別 | シルエットだけで他のキャラクターと識別可能 |
ArtGuide 01_Character/_STYLE_GUIDEの遵守 |
領域スタイルガイドへの違反なし |
| voice_profileとの矛盾なし | 視覚的な印象が音声の印象と衝突しない |
| 縮小時の識別性 | UI・ミニマップのサイズに縮めても誰なのか分かる |
ここでimage_prompt_design_intent_first atomが働きます。コンセプトアーティストがプロンプトを書くときも、「グレーのガウンの女性学者」という外見の単語から並べるのではなく、仕様書の設計意図(「権威的だが公正」「感情を抑制する学者」)から入れます。外見キーワードだけを握って数百枚を量産すると、服の色は合っているのに眼差しが学者ではない絵が山ほど出てきます — 意図を先頭に置くのは、その「外見は合っているが印象はずれている」山をあらかじめ減らすためです。量産ツールは§12.1.1・§12.2.5と同じです — セルフホスティングのSD(SDXL)/ComfyUIにキャラクターLoRA(顔・衣装の固定)とControlNet(ポーズ・シルエットの固定)を併せてかけ、同じ人物を別のポーズで数百枚出力しても顔が崩れないようにします。
ゲート1の最初の項目「仕様書の外見6項目への適合」が、メッセンジャー事故を防ぐ直接のかんぬきです。コンセプト案がモデルシートとして固まる前に現在の仕様書と突き合わせるので、2か月前のバージョンを手に作業したずれは、この場所で引っかかります。
ここで1つ、運用上の非対称に触れておく必要があります。ここまで見てきた仕様書はすべてMarkdownですが、コンセプトアーティストと3Dモデラーは、Markdownを読むためにゲーム会社に来た人たちではありません。そこでプロジェクトAは、§12.2.4で見た一方向変換パイプライン(「企画チームはmdで決定、アートチームはhtmlだけを見る」)をspec→assetフローにもそのまま使います。企画チームが下したmdの決定をhtmlに変換して別のアートSVNへ押し込み、アートチームはhtmlだけを見ます — mdの学習コストは0です。
flowchart LR
P["企画チーム
character_spec.md"] --> CV["_convert_md_to_html.py"]
CV --> H["96_ArtGuide
character_spec.html"]
H --> SY["_SyncToArtRepo.bat"]
SY --> AR[("アートSVN
(別リポジトリ)")]
AR --> ART["アートチーム
htmlのみ閲覧"]
classDef plan fill:#dcfce7,stroke:#16a34a,color:#052e16;
classDef art fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef tool fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
class P,CV plan;
class H,SY,AR,ART art;
class CV,SY tool;
_convert_md_to_html.pyがmdを読みやすいhtmlに変換し、_SyncToArtRepo.batがその結果を企画SVNではなくアートSVNへpushします。2つのリポジトリを分離する理由はPC分離の原則と同じです — 一方の作業フローがもう一方を上書きしないように保護することです。変換は常に企画→アートの一方向で、アートチームがhtmlに手を入れても企画のmdへ逆流しません。
その変換の終着点である96_ArtGuideは、7つのドメイン(00_Common・01_Character〜07_Env)に分かれています。各ドメインは自分の_STYLE_GUIDEで自治しつつ、00_Commonが全ドメイン共通の規約(彩度範囲・命名・解像度)を束ねます(構造の図は§12.2.1)。ゲート1の3番目のチェック項目が、まさにこの01_Character/_STYLE_GUIDEの遵守かどうかです。
モデルシートが3D段階へ渡ると、8つの工程を経ます。ハイポリモデリング→リトポロジー(ゲーム用ローポリ)→UVアンラップ→テクスチャー→リギング・スキニング→テストポーズ→チェック、という流れです。この段階は、AIが最も弱い区間です。3D生成モデルはまだゲーム品質のリトポロジー・UVを出してくれないため、人と従来のツールが主役です。
代わりにこの段階には、ゲート3、すなわち自動アセットlintが付きます。人が毎回ポリゴン数を数えるのではなく、アセットがコミットされた瞬間に自動で検査されます。
| 検査項目 | 通過条件 |
|---|---|
| ポリゴン数 | キャラクターあたり標準範囲(著者の運用基準 40,000〜80,000) |
| テクスチャー解像度 | 2048×2048標準 |
| UV unwrapの効率 | 活用面積80%以上 |
| ボーン(bone)数 | 標準ボーンセットの遵守 |
| アセット命名規則 | 第11部の命名規則の遵守 |
違反が見つかると、該当する3Dアーティストに通知が飛びます。人の目利きに頼っていた検査を、決定論に移したわけです。ポリゴン数・解像度のような項目は正誤が明確なので、AIでも人でもなく、lintスクリプトの持ち分です。
ここで不可逆な段階が1つ登場します。テクスチャーを焼くレンダリング工程です。一度ベイクしたテクスチャーは元に戻せないので、レンダリング直前にゲート3がもう一度働きます。第4段階のモーションキャプチャーも同じく不可逆です — キャプチャーセッションは、俳優と機材をもう一度呼ばない限りやり直せません。不可逆な段階の前のゲートは、他のゲートより厳格に運用します。
3Dアセットにアニメーション・VFX・サウンド・コードが合わさり、ゲームの中に初めて登場します。すべての分野が一堂に会する段階であり、ゲート4(総合レビュー)が最後のかんぬきです。
| レビュー項目 | 担当 |
|---|---|
| 仕様書の意図への適合 | プランナー |
| ビジュアルのトーン・一貫性 | アートディレクター |
| アニメーションの自然さ | アニメーションディレクター |
| ゲーム内での識別性 | ゲームディレクター |
| パフォーマンス(frame負荷) | テクニカルアーティスト |
キャラクター1体あたり5人が30分〜1時間かけて見ます。この段階のlintはアセットとリソースのマッピング(Skill_Art_Resource_Mapping)が自動で回り、インゲームに実際に紐付いたリソースと仕様書が指すリソースが一致するかを検査します。統合段階でのAIの役割は、ビジュアルリグレッションテストとlintの自動化に限定されます — 何を見せるかを決めるのではなく、昨日と今日のフレームが意図せず変わっていないかをピクセルで突き合わせる、決定論的な作業です。
本章冒頭のメッセンジャー事故は、実は2つの事故が重なったものです。1つはゲート1の不在(ずれた資料が通過)、もう1つは変更追跡の不在(外見ガイドが2回変わった事実が下流へ伝播しなかった)です。2番目の事故を防ぐのが、変更影響の追跡です。
キャラクター1人のどの段階の資料でも変わると、その下流のすべての資料が影響を受けます。これを人が毎回手で計算すると、必ず抜けが出ます。そこで、チェーン上の位置を見て下流の資料を自動でかき集めるツールを置きます。
# spec_change_impact.py
# チェーンのどこかの地点が変わったら、その下流(downstream)アセットを全部集める。
CHAIN = ["spec", "concept", "model", "texture", "rig", "anim", "vfx", "ingame"]
def find_downstream_artifacts(spec_id, changed_field):
artifacts = []
chain_position = get_chain_position(changed_field) # 例: "외형.의상" → "spec"(0)
for stage in CHAIN[chain_position + 1:]: # specの下流すべて
artifacts.extend(get_artifacts(spec_id, stage))
return artifacts
# 使用: K_001の衣装が変わったら?
changed = find_downstream_artifacts("K_001", "외형.의상")
# → ["concept_K_001_v3.png", "model_K_001.fbx",
# "texture_K_001_diffuse.png", "rig_K_001.fbx", ...]
changed_fieldが"외형.의상"(外見.衣装)ならチェーン上の位置は0番(spec)で、その下流であるconcept・model・texture・rigのすべてが影響リストに載ります。このリストが自動通知で担当者たちに届きます。机の上の決裁トレーの比喩で見ると、1番のトレーを修正した瞬間に2〜8番のトレーへ自動で赤い旗が立ち、旗の立ったトレーはレビューキューに再び入ります。メッセンジャー事故は、まさにこの旗がなかったために起きました — 1番(仕様書の外見)が2回変わったのに、2番(コンセプト)に旗が立たなかったのです。
以下は、著者が運用したプロジェクトAの標準化前後の比較です。絶対的な時間・件数は著者の推定(未検証)であり、信頼できるのは方向とおおよその比率です。
| 項目 | 標準化前 | 標準化後 | 方向 |
|---|---|---|---|
| キャラクター1体(仕様書→インゲーム) | 8〜12週間 | 4〜6週間 | 約半分 |
| 段階間の推測事故 | 四半期あたり10〜15件 | 四半期あたり2〜3件 | 大幅減 |
| 変更漏れ事故 | 四半期あたり8〜10件 | 四半期あたり1〜2件 | 大幅減 |
| 総合レビュー時間(キャラクターあたり) | 分散・反復(計4〜6時間) | 30分〜1時間に集中 | 集中化 |
| 新規キャラクターデザイナーのオンボーディング | 約2か月 | 約1か月 | 約半分 |
キャラクターのサイクルがおよそ半分に縮みました。ただし、この数字を誤解してはいけません。標準化は、すべてのキャラクターを同じ速度で打ち出すコンベヤーではありません。メインキャラクターには依然として8週間近くをかけ、端役は4週間で仕上げます。標準化がやったのは、速度を均一にしたことではなく、段階ごとの時間の差を揺らぎなく維持できるようにしたことです。標準が統制に流れると、作り手の創造の時間を削る事故になって返ってきます — 標準化の目的は推測と漏れをなくすことであって、時間を圧縮することではありません。
| 段階 | AIの役割 | 強度 |
|---|---|---|
| 1. 仕様書 | ドラフト作成の補助、欠落の質問(プランナーがレビュー) | 強 |
| 2. コンセプト | Stable Diffusion(SDXL)・ComfyUIでの量産(LoRA・ControlNet)、LLMプロンプト | 強 |
| 3. 3D | 生成モデルが未成熟、人・従来ツールが主役 | 弱 |
| 4. 統合 | ビジュアルリグレッション・lint自動化 | 決定論 |
第1・2段階ではAIが強く、第3段階は人が、第4段階は決定論的なツールが受け持ちます。この分離が定着すると、各段階の責任が明確になります — どこまでがAIのドラフトで、どこからが人の決定なのか、ゲートの前で迷わなくなります。
| パターン | 処方箋 |
|---|---|
| 仕様書が外見・表情の6項目を欠落 | 第1段階で必須チェック、AIに欠落の質問を出させる |
| コンセプト段階のゲートを省略 | モデルシートが固まる前に外見6項目の突き合わせを強制 |
| 変更影響を手で計算 | spec_change_impactで自動追跡 |
| 総合レビューを最後にまとめて実施 | 段階ごとにゲートを分散 |
| アセットlintなしでビルド | ゲート3で自動ブロック |
| 4週間ですべてのキャラクターを強制圧縮 | 段階ごとの時間の差を維持 |
最初の行と3行目が、本章冒頭のメッセンジャー事故への直接の処方箋です。
setup
1. character_spec.mdの様式を1つ作ります(アイデンティティ・外見6項目・表情・役割・音声・参考の6セクション、related_atomsフィールドを含む)。
2. md→html変換スクリプト(_convert_md_to_html.pyのようなもの)を置き、アートチームにはhtmlだけを共有します。
3. 4つの転換点にゲートのチェックリストを付けます(外見6項目/モデルシート/アセットlint/総合レビュー)。
prompt
以下のcharacter_specのアイデンティティ・役割は確定だ。「外見ガイド」と「表情・姿勢」のドラフトを埋めよ。ただし分からない値は推測せず、質問として明示せよ。制約:refgameのトーン、シルエットだけで区別可能、抑制型の表情。
verify
1. AIが推測した値(特に体格・色)を確定済みの仕様書と1行ずつ突き合わせます — ずれていたら拒否し、固定値で再依頼します。
2. ゲート1のチェックリスト5項目を、モデルシートへ渡す前に通過させます。
3. 外見の1行をわざと変えてみて、spec_change_impactが下流アセットのリストを正確に吐き出すか確認します。
一人で作業しているなら、変換パイプライン・アートSVN・5人レビューは過剰です。最低限、次の2つだけ残してください。(1)character_spec.mdという1つの様式 — 外見6項目は必須、空欄は禁止。(2)外見を変えるたびに「この変更が届く下流ファイル」を仕様書のいちばん下に1行、手で書き留めておく習慣。ツールがなくても、その1行が変更漏れ事故を防ぎます。
主な読者: ユーザーフィードバックとメタゲームを読み解く必要があるMMORPGプランナー(中規模(10〜50人)チーム) 一人・趣味開発の読者向け縮小バージョン: §13.1.8「一人ならここまでで十分」
アップデートを配信した翌朝、ゲーム内アンケートの自由回答欄に312件が積み上がっていた画面を覚えています。一言だけの短い文から、5行にわたる怒りまで入り混じっていました。企画チームの誰も、その312件をすべては読みませんでした。正確に言えば、読めなかったのです。読んだとしても「だいたい強化がきついという話が多いですね」程度の印象で会議に入り、その印象は最も声の大きい5件が作った錯覚でした。312件が実際に何を語っているのかは、誰も知りませんでした。
本章では、その312件を人がすべて読まなくても「何が何件」と言えるようにする方法を扱います。核心は2つです。第一に、数百件の自由回答をトピックに束ね、感情をラベリングする退屈な分類をAIに任せます。第二に、AIのクラスタを鵜呑みにせず、人が誤分類を1件捕まえて拒否し、再依頼します。FAQ・メタゲーム分析の一般論は他の書籍にもあるので、本章はその分析をAIワークフローで回す部分だけに集中します。
FAQと自由回答は、プランナーが意図したゲームとユーザーが実際に体験しているゲームの差を映し出す鏡です。同じ質問が案内デスクに1日30回寄せられるなら、対応の人員を増やすのではなく、案内板をデザインし直すべきです。問題は、その「30回」を数える作業です。自由回答は構造化されたログではないので、GROUP BYがかけられません。「強化が高すぎます」と「財貨が足りなくて育てられません」は同じトピックですが、文字列が異なります。人が目視で束ねると312件に2〜3時間かかり、束ねる基準も人によってぶれます。
ここがAIの入る場所です。自由回答の分類は、(1)量が多く、(2)退屈で、(3)自然言語の意味判断が必要 — つまり決定論的なコードでは対応できず、人がやるには高くつく作業です。ただし、最初に釘を刺しておくことが1つあります。AIが作るのはトピッククラスタ(仮説)であって、確定診断ではありません。「強化への不満38%」はAIがラベルを付けた結果にすぎず、それが「強化を下方修正せよ」という決定に直結してはいけません。第13部全体を貫く原則が、ここでもそのまま当てはまります — KPIの定義と最終診断は人、自然言語の束ねと1次ラベリングはAIです。
自動化の本当の価値もこの点にあります。分類を自動化すると、分析自体が速くなること以上に、312件という信号が毎週の朝、分類された形でデスクに届くことが核心です。自動化の価値は時間の節約ではなく信号の露出です(チーム運営の概念 automation_signal_value_over_time_savings)。郵便受けに積もるだけだった手紙が、毎日仕分けされて担当部署に配達されるようになる違いです。
実際にどう回すのか、1サイクルを最後まで見せます。以下は著者のプロジェクト(モバイル優先MMORPG、以下「プロジェクトA」)のゲーム内アンケート自由回答をトピッククラスタリングしたセッションを忠実に再現したものです。入力プロンプトはそのままコピーして使えますし、出力は実際のセッションを再構成したものです。
まず元の自由回答を、機械が読める形式で抽出します。これはアンケートDBから取り出すだけなので、新しく書くものではありません。重要なのは、美化も要約もせず、誤字・暴言・一言だけの回答まで生のまま入れることです。分類の精度は、原文が生であるほど上がります。
# survey_freetext_2026-W21.jsonl (抜粋、312件中6件)
{"id": 0041, "text": "強化費用おかしいだろ(-_-) +10行くのに財貨が全然貯まらない"}
{"id": 0088, "text": "ボスのパターンは面白いけど報酬が渋すぎます"}
{"id": 0102, "text": "ギルド戦のマッチング長すぎ 5分以上待たされる"}
{"id": 0156, "text": "課金しないと強化できない これがゲームかよ"}
{"id": 0203, "text": "新ダンジョンの雰囲気いいですね 音楽もいいし"}
{"id": 0274, "text": "メールの報酬なんで来ないの? バグっぽい"}
添付した survey_freetext_2026-W21.jsonl(アンケート自由回答312件)をトピック6〜10個に
束ねて、各回答に否定/中立/肯定のラベルを付けて。細かく刻みすぎず、回答1件は
必ず1つのクラスタだけに。合わなければ「その他」へ。クラスタごとに件数・比率・主要感情・代表原文1件を
表で出して、割り当てに確信が持てないものは無理に入れず[曖昧]リストに分けて理由を1行ずつ。
解説は省いて、(ア)クラスタ定義 (イ)集計表 (ウ)[曖昧]リストだけ。
このプロンプトで分類を指示している言葉は最初の2行だけで、残りはすべて出力の形を縛る制約です。AIに自由を与えると、クラスタを20個に刻んだり、すべての回答をどこかへ無理やり押し込んだりします。それを防ぐのが、クラスタ数の上限(6〜10)、単一割り当て、感情の3分類、そして「確信が持てなければ[曖昧]へ出せ」という逃げ道の4行です。分類の精度はこの柵から生まれます。
(ア) クラスタ定義
C1 強化/成長コスト — 強化・財貨獲得の要求が過剰だという不満
C2 課金プレッシャー — 無課金/微課金が成長から排除されているという認識
C3 コンテンツ報酬不足 — ボス・ダンジョンなどプレイに見合わず報酬が渋いという意見
C4 マッチング/待機時間 — ギルド戦・マッチングの待ちが長いという不満
C5 コンテンツ好評 — 新ダンジョン・演出・音楽など肯定的な反応
C6 バグ/支給漏れ — 報酬未支給・不具合の報告
その他 — 上記6つに合わない単発もの
(イ) 集計表
| クラスタ | 件数 | 比率 | 主要感情 | 代表原文 |
|----------|------|------|----------|--------------------------------------------|
| C1 強化コスト | 71 | 23% | 否定 | "強化費用おかしいだろ(-_-) +10行くのに財貨が全然貯まらない" |
| C2 課金プレッシャー | 58 | 19% | 否定 | "課金しないと強化できない これがゲームかよ" |
| C3 報酬不足 | 49 | 16% | 否定 | "ボスのパターンは面白いけど報酬が渋すぎます" |
| C4 マッチング待機 | 34 | 11% | 否定 | "ギルド戦のマッチング長すぎ 5分以上待たされる" |
| C5 コンテンツ好評| 41 | 13% | 肯定 | "新ダンジョンの雰囲気いいですね 音楽もいいし" |
| C6 バグ支給漏れ | 28 | 9% | 否定 | "メールの報酬なんで来ないの? バグっぽい" |
| その他 | 31 | 10% | 混合 | (単発多数) |
(ウ) [曖昧]リスト (4件)
- id 0088 "ボスのパターンは面白いけど報酬が渋すぎます"
→ C3(報酬不足)に割り当てたが、前半はC5(好評)とも重なる。複合回答。
- id 0156 "課金しないと強化できない" → C1とC2の境界。C2へ送った。
- (ほか2件)
出力で最も価値があるのは集計表ではなく、一番下の[曖昧]リストです。AIが自分の割り当ての不確実性を報告し、人に引き渡した場所です。良いプロンプトとは、AIが「これは確信が持てません」と言えるようにするものです。
この出力をそのまま報告に載せてはいけません。人が原文のサンプルを自分で当たります。実際にこのセッションでは1件が引っかかりました。
C2(課金プレッシャー)の58件を開いて原文を流し読みしていたところ、id 0156 "課金しないと強化できない これがゲームかよ"が目に留まりました。AIはこれをC2(課金プレッシャー)に送りました。しかし、この文の一次的な痛みは「課金」ではなく「強化できない」 — つまりC1(強化コスト)です。ユーザーは強化の壁に阻まれ、その壁の原因を課金だと指摘したのであって、課金そのものが不満の核ではありません。C1とC2が隣接していて紛らわしいのは確かですが、これをC2として数えると「強化コスト」の信号が23%より小さく見え、本来手を入れるべき強化曲線が優先順位から押し出されます。誤分類1件が決定の方向を変えうる境界ケースです。
そこで拒否して再依頼します。
C1(強化コスト)とC2(課金プレッシャー)の境界が紛らわしいな。一次的な痛みが「成長の壁そのもの」ならC1、
「課金しないと排除されるという公平性」ならC2で引き直して。id 0156は「強化できない」が
核だからC1。この基準で境界にかかったものを再割り当てして、変わった件数だけ教えて。
AIは境界を引き直し、C2にあった9件をC1へ移しました。その結果、C1は71→80件(26%)、C2は58→49件(16%)に変わりました。強化コストが単一最大トピックだという絵柄は同じでしたが、その大きさが23%から26%へとくっきりしました。1往復で信号の輪郭が鮮明になります。この再割り当て件数(9件)と比率の変化は、このセッションで実際にカウントした値です(標本312件、単一週)。
ここで1つはっきりさせておきます。人が拒否したのは「AIが間違っていたから」ではありません。C2への割り当ても解釈としては成立しました。人がしたのは、クラスタ定義(=KPI定義)をより鋭く研いでAIにフィードバックしたことです。定義は人が、その定義で312件を洗い直す労働はAIが担います。
上のセッションを毎週自動で回すと、パイプラインになります。人の手が触れる場所は2か所だけです。クラスタ定義を鋭く定める場所(前)と、分類結果を決定につなぐゲート(後)。その間の312件の束ねとラベリングはAIが回します。
flowchart TB
A["元の自由回答312件
(アンケートDB抽出、美化禁止)"] --> B["1段目 AI: トピッククラスタリング
6〜10個 + 感情ラベル + [曖昧]報告"]
B --> C{"2段目 人による検証
原文サンプル + 境界ケース確認"}
C -->|誤分類・定義の曖昧さ| D["クラスタ定義の再定義
→ AIへ再割り当て依頼"]
D --> B
C -->|通過| E["週間集計表
トピック × 件数 × 感情"]
E --> F{"企画決定ゲート
(ディレクター・プランナー)"}
F --> G["3分岐: バランス再検討
・UI/チュートリアル ・バグ修正"]
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class B ai;
class C,D,F human;
class A,E data;
決定的な設計は、2段目(人による検証)がAI出力を自動通過させないという点です。自動通過型にすると、AIが一度間違って引いた境界が、毎週同じ方向に信号を歪めます。疑わしい候補([曖昧]リスト)はAIが挙げますが、クラスタ定義を直すかどうかは人が決めます。そして集計表は、それ自体が決定ではなく、決定ゲートへの入力にすぎません。「C1強化コスト26%」はディレクターに強化曲線を覗き込ませる信号であって、自動下方修正のトリガーではありません。
自由回答が「ユーザーが言ったこと」だとすれば、メタゲームは「ユーザーが実際にやったこと」です。リリースされると、プランナーが意図しなかったプレイ方式が定着しますが、それがメタゲームです。ビルドメタ(特定スキル構成への偏り)、動線メタ(好まれる狩り経路)、取引メタ(公式相場と異なるユーザー間の合意価格)のようなものです。これは自由回答と違って行動ログで定量的に測定でき、決定論的なコード(Python)が集計します。AIが入り込む場所ではありません。
核心は、2つを重ねて見ることです。上のセッションではC1(強化コスト)の不満が26%で最大でした。このとき、行動ログでビルド多様性指数(上位スキル構成への集中度)が同じ週に下がっていたなら、「言葉でも行動でも、1つのビルド・1つの成長経路へ収束しつつある」という2つの信号が同じ方向を指しています。定量と定性が一致するとき、決定の確信が固まります。逆に、自由回答は静かなのに行動ログだけが1つのビルドへ偏っていくなら、ユーザーが不便を感じながらも口にしない(=静かな離脱の直前)危険信号かもしれません。
flowchart LR
A["定性: 自由回答クラスタ
(AIクラスタリング + 人による検証)"] --> C["重ね読み
同じ方向? ずれ?"]
B["定量: 行動ログ集計
(Python決定論: ビルド多様性・動線・相場)"] --> C
C --> D["企画決定ゲート"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
class B code;
class A ai;
class C,D human;
ここでも分業は明確です。行動ログの集計はAIではなくコードが行います。ビルドの占有率や取引相場は、呼び出すたびに答えが変わってはいけない決定論的な数値だからです。AIは自由回答という非構造化テキストを束ねることだけに使い、定量KPIはコードで釘付けにします。
本章の比率は、序文「一つの約束」の原則に従います。§13.1.2の「C1 23%→26%、再割り当て9件」は標本312件(単一週)で実際にカウントした値なので、絶対値ではなく「強化コストが単一最大トピック」という方向として読みます。因果は断定しません — 「FAQ分析をしたら継続率(リテンション)が上がった」のような表はありません。代わりに、このワークフローで実際に測定できるのは3つです: クラスタ検証で人が覆した誤分類の件数(0なら検証が形式的だったという信号)、週間集計の算出までにかかった時間、定量・定性信号が一致しているかどうか。
§13.1.2では、人がC2割り当ての9件を覆しました。検証を毎週回すと、この種の覆しが毎回0〜数件ずつ出ます。重要なのは、覆し0件が目標ではないという点です。検証で1件も覆らないなら、2つのうちどちらかです — AIが完璧だったか(まれです)、検証者が原文を見ずにハンコだけ押したか。後者が圧倒的に多いのです。
毎週1〜2件の境界ケースが引っかかり、それをきっかけにクラスタ定義が少しずつ鋭くなっていくとき、検証ゲートは実際に機能しています。これは、AIの分類精度を人が定期的にサンプリングしてレビューすべきだという一般原則の具体形です。同じユーザータイプが別のトピックへ散らばる誤分類は、人のレビューなしに自動分類だけを信頼すると、毎週積み重なっていきます。
| パターン | なぜ失敗するのか | 処方 |
|---|---|---|
| 自由回答を人が目視で流し読みするだけ | 声の大きい5件が312件を代表する錯覚 | AIクラスタリングで全数分類(§13.1.2) |
| 「AIさん、ユーザーフィードバックを分析して」と丸投げ | クラスタが20個に刻まれるか、無理やり割り当てられる | クラスタ数の上限・単一割り当て・[曖昧]の強制 |
| AIの集計表を検証なしで報告 | 境界の誤分類が決定の方向を変える | 原文サンプル + 境界ケースの直接確認 |
| 集計比率を決定に直結 | 「不満26%だから下方修正」と自動トリガー化 | 集計表は決定ゲートへの入力にすぎない |
| 定性だけ見て行動ログを無視 | 言葉にしない静かな離脱を見落とす | 定量(コード)・定性(AI)を重ね読み(§13.1.4) |
| 定量KPIをAIに集計させる | 呼び出すたびに数値が変わりバランスが揺れる | ビルド・相場の集計は決定論的なコードで |
3つ目が最も見落とされがちです。集計表はきれいなので、そのまま信じたくなります。しかし、id 0156の1件のように、境界の誤分類1つが優先順位を丸ごと変えることがあります。検証とは312件を読み直すことではなく、最も大きい2〜3個のクラスタの境界ケースだけを原文で確認する作業です。
一人ならここまでで十分: アンケートDBがなくても大丈夫です。自分のゲーム(または好きなゲーム)のストアレビュー・コミュニティ投稿を30〜50件だけテキストで集め、§13.1.2のプロンプトをそのまま貼り付けて一度回してみましょう。出てきたクラスタの中から「これは少しおかしいのでは」と思う割り当てを1件選び、「この回答の一次的な痛みは別のトピックだ。定義を引き直して再割り当てせよ」と反論してみると、クラスタリングがどんな判断の束なのかが体に入ってきます。
チームなら、次の一歩から始めましょう。自由回答の1週間分をsurvey_freetext_YYYY-Www.jsonlとして美化なしで抽出し、§13.1.2のプロンプトで一度回してみます。その次に、最も大きい2つのクラスタの境界ケースだけを原文で確認します。クラスタ定義を一度鋭く定めておけば、以後は毎週同じプロンプトで、再現可能な週間集計が自動的に積み上がっていきます。
主要読者: 運営(ライブオプス)指標に責任を持つライブ/データプランナー(中規模(10〜50人)チーム) 一人/趣味の読者向け縮小バージョン: §13.2.8 「一人ならこれだけ」
毎週月曜の朝、同じ場面が繰り返されていました。データチームから届いた日次ダッシュボードのキャプチャを会議の画面に映し、誰かが「DAU(Daily Active Users、日次アクティブユーザー)が少し落ちている気がします」と言うと、別の誰かが「それは先週メンテナンスがあったからですよ」と受けます。数字はそこにあるのに、その数字が異常シグナルなのかノイズなのかを判定する人の頭の中の作業が、毎週ゼロからやり直しになっていました。そしてその判定は、話す人によって違いました。
この章の結論を先に書きます。KPIで人がやるべき仕事は、何をKPIにするかを定義することと、AIが上げてきた異常シグナルを確定診断に昇格させるか拒否するかを判定すること、この2つだけです。その間に挟まる2つの仕事 — 毎日同じ時刻にrawログから数字を抽出する仕事と、前週比で何が揺れたかを自然言語で一次作成する仕事 — は、それぞれ決定論的なコードとAIが受け持ちます。KPI定義の一般論(5〜7個に絞れ、グッドハートの法則に気をつけろ)は他の書籍にも十分ありますから、この章はその定義をAIワークフローで回す場所だけに集中します。
KPI運営には、人にしかできない判断が2つあります。第一に、何をKPIにするか。第二に、各KPIの定義を1文で確定すること。この2つはゲームの価値判断なので、AIには委任できません。「Activeを5分以上のプレイとみなす」という決定には、そのゲームが何を健全さとみなすかが込められています。
問題は、この定義が一度揺れると、その上のすべての数字が一緒に揺れるという点です。「Active User」を一方のクエリでは1回のログイン、もう一方のクエリでは10分+狩り1回と取れば、DAUが丸ごとずれます。だから定義そのものより定義の一貫性を守る仕事が運営の半分を占めます。そして一貫性の検査は、人の頭ではなくコードがやるべきです(§13.2.5)。
定義が確定した後の仕事は、人の持ち場ではありません。毎日同じ時刻に数字を抽出する作業と、前週比の変動をなぞって異常シグナル候補を書き出す一次作成 — この2つは毎日繰り返され、人がやると基準がその日その日で揺れる、まさに機械とモデルに下ろすべき種類の仕事です。抽出は決定論(コード)に、一次診断はAIに渡します。人はAIが上げてきた候補を受け取り、確定するか拒否するかだけを判定します。
| 段階 | 誰が | なぜそこなのか |
|---|---|---|
| KPIの選定・定義 | 人 | ゲームの価値判断、委任不可 |
| 日次raw抽出 | コード(決定論) | 同じ入力 → 同じ数字、リグレッション検証が可能 |
| 前週比の異常シグナル一次作成 | AI | 自然言語の要約はAI向き、ただし「仮説」まで |
| 確定診断・セグメント確認の指示 | 人 | AIの仮説を昇格/拒否する、責任の持ち場 |
この分担がこの章全体の骨格です。以下で1サイクルを最後まで回してみます。
実際にどう回るのか、入力から人の判定まで1サイクルを最後まで見せます。以下は著者のプロジェクト(モバイル優先MMORPG、以下「プロジェクトA」)の日次KPI診断セッションを匿名化して再現したものです。rawログのスキーマ・抽出コードの構造・プロンプトは実際のツールを移したもので、数字は形式を見せるための例示値であり、実測KPIではありません。
まずコードが毎日09:00にログDBからKPIを抽出します。AIはこの数字を作りません — 受け取るだけです。抽出結果は、前週の同じ曜日と並べたJSONです。
// kpi_daily_2026-06-05.json — extract_kpi.py の出力(LLMへの入力)
{
"date": "2026-06-05",
"compare_to": "2026-05-29", // 前週の同じ曜日(金)
"active_def": "min10_hunt1", // 適用されたActive定義ID
"L0": {
"ltv_12m_est": {"v": 0, "prev": 0, "delta_pct": null},
"d30_retention": {"v": 0, "prev": 0, "delta_pct": null}
},
"L1": {
"dau": {"v": 0, "prev": 0, "delta_pct": -0.0},
"session_len_min":{"v": 0, "prev": 0, "delta_pct": -0.0},
"sessions_per_u": {"v": 0, "prev": 0, "delta_pct": 0.0},
"d7_retention": {"v": 0, "prev": 0, "delta_pct": 0.0}
},
"segments": {
"dau_by_platform": {"ios": 0, "aos": 0},
"dau_by_region": {"kr": 0, "sea": 0},
"dau_by_newbie": {"d0_7": 0, "d8plus": 0}
}
}
値は0で空けてあります。重要なのは構造です。各KPIに現在値・前週値・変動率が付き、一番下にセグメント分解(プラットフォーム・地域・新規/既存)が一緒に載ります。AIが「DAUが落ちた」で止まらず、「どのセグメントで落ちたのかを確認しろ」まで行くには、この分解が入力に含まれている必要があります。
添付の kpi_daily_2026-06-05.json は09:00に自動抽出された日次KPIで、compare_to は前週の
同じ曜日。L0~L1 のうち異常シグナルとみなせるものを最大2件だけ選んで。前週比の変動が
普段の曜日変動幅を外れたものに限る。普段の変動幅が不明ならでっち上げず「不明」と書いて外す。
候補ごとに指標・変動幅・推定原因の仮説1~2個・確認するセグメントを書くこと。ただし原因は必ず「仮説」とだけ
書き、確定のように断定しない。ログにないイベント・パッチなどの断定は禁止。セグメントは添付の
segments の中だけから指名すること。0件なら「0件、特異事項なし」だけ。
出力は候補ごとに5行(指標/変動/仮説/確認セグメント/確信度 高中低)、散文なし。
ルール5行がほぼすべて禁止文だというのが、このプロンプトの性格です。自由を与えると、AIはKPI診断をもっともらしい確定の物語に美化します(「ログインイベントの終了でDAUが下落しました」のように、ログにないイベントを断定します)。だから確定診断の禁止・仮説のみ・セグメントは入力の中だけ・分からなければ分からないと書くを釘で打ち付けて、AIが越えてはならない線を先に引いておきます。
以下はLLMが実際に吐き出した形を再構成したものです。要約も美化もせず、3番目の候補でルールをこっそり破った部分までそのまま残します。
[異常シグナル候補 — 2件]
候補1
- 指標: session_len_min (平均セッション長)
- 変動: 前週(金)比で下落 (delta_pct が負)。L1の中で最大の負の変動。
- 仮説: (a) 新規流入(d0_7)のセッションが短く平均を引き下げた可能性
(b) iOSの特定ビルドでセッションが早期終了している可能性
- 確認セグメント: dau_by_newbie(d0_7 vs d8plus) のセッション長の分離、
dau_by_platform(ios vs aos)
- 確信度: 中
候補2
- 指標: d7_retention
- 変動: 前週比で小幅な上昇(正)だが絶対幅は小さい。
- 仮説: ノイズの可能性あり。普段の曜日変動幅のデータがなく、異常かどうか断定不可。
- 確認セグメント: dau_by_newbie(d0_7) のコホート継続率
- 確信度: 低
[参考] 最近のログインイベント終了がセッション長下落の原因とみられます。
出力の最初の2候補はルールを守りました。仮説を仮説として書き、セグメントを入力の中から指名し、候補2は「普段の変動幅のデータがなく断定不可」と、自ら確信度を「低」に下げました。これが良い出力の姿です — AIが自分の限界を申告したのです。
問題は一番下の [参考] の1行です。ログにない「ログインイベントの終了」を原因として断定しました。ルール3違反です。これが次のステップで引っかかります。
打つべきポイントは3つです。
第一に、ルール違反。 [参考] の行は、入力JSONにないイベントを事実のように断定しました。イベントカレンダーはこの入力に含まれていなかったので、AIには知りようのない情報です。この行は拒否します。
第二に、候補1の採択。 セッション長の下落は実在し、AIが提示した2つの枝(新規コホート/iOSビルド)は入力のセグメントで実際に確認できます。採択しますが、まだ異常シグナルであって、確定した原因ではありません。 人がやるべきことは、セグメントクエリを回して2つのどちらなのかを切り分けることです。
第三に、候補2の保留。 AI自ら「断定不可」と言い、絶対幅も小さいものです。普段の曜日変動幅(曜日別の標準偏差)を抽出コードに追加するまでは、ノイズとして置いておきます。これはコード側の宿題です — AIが「普段の変動幅が分からない」と申告したのは、実は入力データの欠陥を指していたのです。
そこで再依頼します。
一番下の [参考] 行は入力にないログインイベントを断定しているので消して。候補1だけ残し、
セッション長の下落を d0_7/d8plus × ios/aos の2x2に分けて「どのマスが最も落ちたかを
確認」という1行のアクションに書き直して。原因の断定はせず、確認アクションだけ。
この1往復で終わりです。AIは [参考] の行を消し、「d0_7 × iOSのマスのセッション長を先に見ろ」という確認アクション1行で答え直しました。その出力はルールを通過し、人がそのクエリを回して — 実際に新規iOSコホートのマスが最も落ちていることを確認したら — その時はじめて「新規iOSオンボーディングのセッション離脱」という確定診断を下します。診断を下すのは、最後まで人です。
核心: AIは「どこを見るべきか」までしか知りません。「何が原因か」は、人がセグメントを切り分けて確認した後に確定します。この境界をプロンプトで強制しなければ、AIは毎回もっともらしい確定の物語に踏み越えます。
上のサイクルを図で固定しておけば、以後のすべての日次診断が同じ道を通ります。人の手が触れる場所が両端の2か所(定義・確定)だけだということが、一目で分かります。
flowchart TD
A["KPI定義
(人: 選定 + 定義1文)"] --> B["rawログDB"]
B --> C["extract_kpi.py
決定論的抽出 09:00
現在・前週・セグメント"]
C --> D{"def_diff.py
Active定義の一致検査"}
D -->|不一致 alert| A
D -->|一致| E["AI一次診断
異常シグナル ≤2件 + 仮説
+ 確認セグメント"]
E --> F{"人によるレビューゲート
ルール違反・断定を拒否"}
F -->|再依頼| E
F -->|採択| G["セグメントクエリ →
人が確定診断"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
class C,D code;
class E ai;
class A,F,G human;
3つの枝で色が違います。青系(抽出・定義diff)は決定論なので、同じ入力に同じ結果を保証します。真ん中のAIの1マスだけが非決定で、だからこそ両脇をコードが押さえます。最後の確定診断は人です。§13.2.2で [参考] の行が引っかかった場所が、まさに「F 人によるレビューゲート」です。
AIに一次診断を渡す前に、人が確定しておくべき定義には4つの罠があります。罠を知らないと、§13.2.2の入力JSON自体が毎日違う意味になります。
罠1 — Activeの定義。 「Active User」が1回のログインなのか、5分以上なのか、10分+狩り1回なのかで、DAUは倍単位で割れます。定義をID(min10_hunt1)で固定し、入力JSONに一緒に載せます(§13.2.2 ステップ1の active_def フィールド)。このIDがクエリごとに違えば、§13.2.5のdiffが捕まえます。
罠2 — Retentionの測定時点。 「7日継続率(リテンション)」の「7日」が、登録後ちょうど7日目なのか、7日以内のいずれかの日なのか、8日目なのかで値が割れます。業界標準が揺れている領域なので、自前の定義を明文化して一貫して守るしかありません。
罠3 — Outlier(外れ値)の処理。 上位少数の高アクティブユーザーが平均を引き上げます。だからL0〜L1は平均と一緒に中央値を見ます。分布の変化のほうが平均の変化より意味を持つことが多いのです。AI診断のプロンプトに平均だけを渡すと、AIは平均だけを見て分布の移動を見落とします。
罠4 — 測定時刻。 午前・午後・未明で測定値が違います。運営の自動化は毎日09:00の同時刻抽出を標準とします(§13.2.2 ステップ1)。時刻が揺れると、前週比の比較が崩れます。
この4つの罠に共通するのは、値ではなく定義が揺れるという点です。だから最も危険な事故は「DAUが落ちた」ではなく、「昨日と今日のDAUが違う定義で計算された」です。人の目ではほとんど捕まえられません。コードで捕まえます。
最も静かなKPI事故は、2つのクエリが同じ名前(DAU)を違う定義で計算することです。ダッシュボードのクエリは min10_hunt1 でDAUを数えているのに、マーケティングレポートのクエリが login1 で数えていれば、同じ会議で2人が違うDAUを持ち寄り、互いを疑います。人がSQLを1行ずつ比較して捕まえられる仕事ではないので、定義をメタデータとして切り出し、コードがdiffします。
# def_diff.py — KPI定義の一貫性検査(骨格)
# 前提: 各クエリは自分が使ったActive定義IDをメタとして宣言する。
# 例: dashboard.sql ヘッダーの -- @active_def: min10_hunt1
CANON = { # 正規定義(人が一度だけ確定する)
"DAU": "min10_hunt1",
"d7_retention": "signup_plus7_exact",
}
def parse_active_def(sql_path):
# SQLコメントヘッダーから -- @active_def: <id> を読む
for line in open(sql_path, encoding="utf-8"):
if line.strip().startswith("-- @active_def:"):
return line.split(":", 1)[1].strip()
return None # 宣言漏れも事故である
def diff(query_registry):
issues = []
for kpi, sql_path in query_registry.items():
declared = parse_active_def(sql_path)
canon = CANON.get(kpi)
if declared is None:
issues.append(f"[MISS] {kpi}: {sql_path} に定義宣言なし")
elif declared != canon:
issues.append(
f"[DIFF] {kpi}: {sql_path} は '{declared}' で計算しているが "
f"正規定義は '{canon}'。同じ名前で異なる定義 — 比較不可。"
)
return issues
この30行が、「なぜあなたのDAUと私のDAUが違うんですか?」という会議をなくします。[DIFF] DAU: marketing_report.sql は 'login1' で計算しているが 正規定義は 'min10_hunt1' とコードが出力すれば、議論することはありません。クエリを直すか、正規定義を変えるか、どちらかです。定義がコードで検査されていれば、§13.2.2のAI診断が常に同じ定義の上で回っているという保証が生まれます。定義が揺れている上に載せたAI診断は、もっともらしいたわごとです。
この検査は決定論なので、CIに掛けます。クエリをコミットするたびに自動で回ります。AIには決して任せない領域です — 定義の一致は判断ではなく比較なので、非決定なモデルが挟まると、かえって事故が増えます。
このパイプラインを敷くと、最初に思い浮かぶ自慢は「診断時間が減った」です。しかし本当の価値は別のところにあります。著者のチーム運営の概念の中に automation_signal_value_over_time_savings という1行があります — 自動化の価値は節約した時間ではなく、露出されたシグナルにある。
KPI自動化の前は、セッション長の下落のようなシグナルは、誰かが偶然グラフを覗き込まなければ見えませんでした。自動化の後は、毎日09:00に「前週比の異常シグナル2件」が自然言語で机に上がってきます。減ったのは分析時間ですが、変わったのはそのシグナルを何日で認知するかです。偶然見なければ見えなかったものが、毎日強制的に露出されます。
だからこのツールの成功は、「診断が何分短くなったか」では測りません。異常シグナルを最初に認知するまでの時間(シグナル → 認知)で測ります。この方向が壊れると — つまりAI要約が毎日「特異事項なし」だけを打って誰も読まなくなると — ツールは時間を節約しつつシグナルを殺したことになり、1〜2四半期のうちに無用の長物になります。
この章の数字は、序文「一つの約束」の原則に従います。登場したKPIの数字(DAU・セッション長の変動率)はすべて形式を見せるための例示値であり、実測ではありません — 絶対値ではなく構造として読みます。KPIの定義(Active・Retention)には業界で合意された単一の標準がないため、「自前の定義を明文化せよ」が結論です(§13.2.4)。実際に測定できるのは3つです: def_diff が捕まえた定義不一致の件数(目標0)、AI診断候補のうち人が拒否した比率、異常シグナルの認知までの時間。逆に、「KPI自動化で継続率が上がった」のような因果は断定しません。
一人ならこれだけ: ログDBがなくても構いません。自分のゲーム(または好きなゲーム)で、毎日見るKPIをちょうど3つだけ選び、定義を1文ずつ書いてみましょう(「Active = 1プレイでも開始」のように)。そして昨日・今日の値を手で2行書き、§13.2.2のプロンプトを貼り付けて、AIに「異常シグナル候補を仮説のみで、確定診断は禁止で書いてほしい」と頼んでみましょう。AIがこっそり断定する1行を見つけて、「それはログにない事実だ、外せ」と反論してみると、KPI診断における人の持ち場がどこなのか、体感として入ってきます。
チームなら、次の一歩から始めましょう。KPIを5〜8個決め、各クエリのSQLヘッダーに -- @active_def: <id> の1行を入れる規約から作ります。その次に、§13.2.5の def_diff.py の骨格(正規定義のdict+ヘッダーのパース+diff)をCIに掛けます。AI診断パイプラインはその後です。定義の一致検査が1つあるだけでも、「あなたのDAUと私のDAUが違う」という最も静かな事故を先に防げます。
| パターン | なぜ失敗するか | 処方 |
|---|---|---|
| KPIを30個並べたダッシュボード | 赤を見つけられず毎日見なくなる | L0〜L1の5〜8個に圧縮 |
| Active定義がクエリごとに違う | 同じ名前で違う数字 → 会議の不信 | def_diff.py のCIゲート(§13.2.5) |
| AIに「原因を診断して」と丸ごと委任 | ログにないイベントを断定する | 仮説のみ・セグメントは入力の中だけ(§13.2.2) |
| AI診断を無批判に採択 | もっともらしい確定の物語が意思決定の入力に紛れ込む | 人によるレビューゲートで断定を拒否 |
| 平均だけを入力に渡す | 分布の移動をAIも人も見落とす | 中央値・セグメント分解を同梱(§13.2.4) |
| 自動化を「時間の節約」だけで評価 | 要約が「特異事項なし」だけ打っても合格になる | シグナル認知時間で測定(§13.2.6) |
4番目が最も見落とされやすいところです。AIの要約は滑らかなので、そのまま信じたくなります。§13.2.2の [参考] の1行のように、滑らかな断定が1つ拒否されずに通過すると、その偽の原因が次の四半期の意思決定の入力になります。人の持ち場は要約を書くところではなく、要約の断定を拒否するところにあります。
1次読者:KPIを見て四半期の決定を下すデータ担当・ディレクター(中規模(10〜50人)チーム) 一人・趣味の読者向け縮小版:§13.3.9「一人ならここまでで十分」
月曜の朝、ダッシュボードで赤い線を1本見たことがあります。30日継続率(リテンション)が前週比で目に見えて折れていました。会議室に集まった人たちが、それぞれ一つずつ原因を挙げました。ある人は先週パッチした新規狩り場を、ある人は競合タイトルの新シーズンを、ある人はただ「季節要因」を口にしました。どれももっともらしいものでした。問題は、その日の午後が終わるまで、私たちが何を検証すべきかにすら合意できなかったことです。仮説は5個あるのに、検証するセグメントは一つも決まっていませんでした。
本章はその朝を終わらせる方法を扱います。核心は一行です。異常指標を見たら、AIに確定診断をさせるのではなく、検証可能な仮説3〜5個を出させます。 AIは「継続率が落ちた理由はXだ」と断定しません。「Xなら、このセグメントでこう見えるはずだ」という検証設計を出し、決定は人が下します。データドリブンの一般論は他の書籍に十分ありますから、本章はその一般論をAIワークフローで回す場面だけに集中します。
まず境界を打ち込んでおきます。本章全体が、次の一文の上に立っています。KPIを何と定義するかは人が決め、そのKPIが揺らいだときに、なぜ揺らいだのかの仮説を素早く広げる仕事だけをAIが手伝います。
この境界が崩れると、データドリブンそのものが崩れます。KPIの定義をAIに任せると「測定しやすいもの」がKPIになり、診断までAIに任せると、もっともらしい確定文が人の検証を飛ばして決定に直行します。だからAIにはただ一つの区間だけを開きます — 異常が捕捉された後、人が決定を下す前、その間の「何を疑い、何を確認するか」を広げる区間です。
この分担は、第13部の前の章と同じ背骨を共有しています。rawログはPythonが決定論で抽出し(13.1)、KPIの定義・階層は人が固定し(13.2)、本章ではその上で異常が捕捉されたときの解釈の補助だけをAIが担います。抽出は決定論、定義は人、解釈の補助はAI。三つが混ざらないことが、このパート全体の安全装置です。
著者のプロジェクト(モバイル優先のMMORPG、以下「プロジェクトA」)には、この補助を支える実在のログが敷かれています。チームメモリーフォルダ配下の_economy_log/(トークン・時間の経済性ログ)、_scores_latest.json(指標スコアのキャッシュ)、_roi_report.md(ROI(Return on Investment、投資対効果)レポート)がそれです。本章のワークド・トランスクリプトは、これらのログから抽出した異常シグナルを入力として受け取ります。
一つの異常指標が決定につながる全体のループを、まず図で固定しておきます。この図の中でAIが入るマスはただ一つ、「仮説の生成」だけです。その前(抽出)も後ろ(検証・決定)も、人とコードの持ち場です。
flowchart TB
A["異常指標の検知
(ダッシュボードalert / KPIしきい値逸脱)"]
A --> B["ステップ1 決定論: Python抽出
rawログ → セグメント別数値
(いつ・どこで・誰が折れたか)"]
B --> C["ステップ2 AI: 仮説3~5個を生成
確定診断は禁止
各仮説 = 検証するセグメント + 予想パターン"]
C --> D{"ステップ3 人: 仮説の優先順位
最も安く反証できるものから"}
D --> E["ステップ4 決定論: Python再抽出
指定されたセグメントのみ精密集計"]
E --> F{"ステップ5 人: 決定
仮説の採択・棄却・保留"}
F -->|反証された| C
F -->|確証された| G["決定カードに記録 → ビルド反映"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class A,B,E code;
class C ai;
class D,F human;
class G pass;
人の手が触れる場所は3か所です。何が異常かを定義する場所(先頭、すでに13.2で完了)、どの仮説を先に検証するかを選ぶ場所(ステップ3)、最終決定を下す場所(ステップ5)。その間の退屈なログ集計はPythonが、仮説を素早く広げる仕事はAIが受け持ちます。AIが確定診断を下すマスは、このループには存在しません。 仮説は反証されるために存在し、反証されればステップ2に戻ります。
実際にどう回すのか、1サイクルを最後まで見せます。以下は、冒頭の月曜の朝の継続率下落を再構成したセッションです。入力プロンプトはそのままコピーして使えますし、出力は実際のセッションを忠実に再構成したものです。
まず、人が「継続率が落ちた」という感触を投げることはしません。Pythonが決定論で抽出したセグメント別の数値表を投げます。これは新しく書くものではなく、_economy_log/イベントログから抽出するだけです。
# retention_break_extract.py (骨格) — 異常区間のセグメント分解
# 入力: 日次コホート継続率ログ
# 出力: どのセグメントでどれだけ折れたか (LLM入力用の表)
def extract_break(rows, kpi="d30_retention", baseline_weeks=4):
base = mean([r[kpi] for r in rows if r.week < target_week][-baseline_weeks:])
cur = [r for r in rows if r.week == target_week]
return [
{"segment": s.name,
"baseline": round(base_by_seg[s.name], 3),
"current": round(s.value, 3),
"delta_pct": round((s.value/base_by_seg[s.name]-1)*100, 1),
"n": s.sample_size} # 標本数 — 小さければ信頼度が低い。一緒に渡す
for s in cur
]
このスクリプトが吐いた表が、AIに渡す1次入力です。核心は、標本数(n)を一緒に渡すという点です。標本の小さいセグメントの揺らぎをAIが原因と錯覚しないようにするには、人ではなくデータがその警告を持っているべきです。
# retention_break_2026Q2W3.txt (抽出結果、抜粋)
segment baseline current delta_pct n
新規(登録7日以内) 0.41 0.31 -24.4% 8,200
復帰(30日+休眠後) 0.28 0.27 -3.6% 1,100
課金(有料) 0.62 0.60 -3.2% 2,400
無課金 0.34 0.25 -26.5% 14,900
新規狩り場_プレイ 0.39 0.22 -43.6% 3,050
新規狩り場_未プレイ 0.40 0.38 -5.0% 11,200
添付したretention_break_2026Q2W3.txtは、Pythonが抽出したd30継続率のセグメント別変化
(baseline=直前4週平均、current=今週、n=標本数)。無課金・新規狩り場_プレイで
大きく落ちている。原因を診断せず、検証可能な仮説を3~5個だけ立ててほしい。仮説ごとに、
仮説の一文 / 検証するセグメント(どうさらに分割するか) / 正しければ見えるパターン /
誤りを示す反証条件、の4行で。標本の小さいセグメント(n<2000)は根拠の核心に使わず、
使うなら限界を明記。仮説は最も安く反証できる順に並べ、データで判別できないものは
「人の判断が必要」と別に表示。出力は仮説カードの一覧のみで、叙述は省くこと。
このプロンプトには「原因を探せ」という言葉が一行もありません。代わりに、仮説が備えるべき骨格と、越えてはならない線だけが書かれています。AIに自由を与えると「おそらく新規狩り場の難易度のせいだろう」というもっともらしい確定文を吐き出し、その文が議事録に記録されると、検証なしに決定になります。それを防ぐ装置が、断定の禁止、仮説ごとの検証セグメント・予想パターン・反証条件の義務化、反証コスト順の整列、「判別できないものは人へ」 の四つです。出力を診断ではなく検証設計に変えておく装置です。
[仮説1] (反証コスト最低)
仮説: 新規狩り場の難易度が無課金の新規ユーザーの参入障壁となり、7日以内の離脱を増やした。
検証セグメント: 新規狩り場_プレイのセグメントを「初回死亡回数」の区間(0~2 / 3~5 / 6+)で再分解。
予想パターン: 仮説が正しければ、初回死亡6+の区間でd7→d30の離脱率が急増するはず。
反証条件: 死亡回数と離脱率の間に単調な関係がなければ棄却。
[仮説2]
仮説: 新規狩り場そのものではなく、同じパッチに含まれた別の変更(報酬カーブ・UI)が原因。
検証セグメント: 新規狩り場_未プレイなのにパッチ後に接続したユーザーの継続率。
予想パターン: 未プレイのセグメントも一緒に下落していれば、狩り場は犯人ではない。
反証条件: 未プレイのセグメントがbaseline維持(-5%水準)なら本仮説を棄却し、仮説1へ。
[仮説3]
仮説: 外部要因(競合タイトルの新シーズン)で無課金全体が離脱し、狩り場は無関係。
検証セグメント: 課金/無課金 × 新規狩り場プレイ有無の2×2クロス表。
予想パターン: 外部要因なら、狩り場のプレイ有無に関係なく無課金全体が一様に下落。
反証条件: 下落が新規狩り場_プレイに集中していれば外部要因説は弱まる。
[仮説4] (人の判断が必要)
仮説: 測定自体の問題 — 今週のコホート定義/ログ収集に変動があった。
検証セグメント: (データでは判別不能) パッチと同時にtelemetryスキーマの変更があったか、人の確認が必要。
予想パターン: スキーマ変更があったなら、下落は実際ではなく測定アーティファクトの可能性がある。
反証条件: ログパイプラインの無変更が確認されれば棄却。
注意: 「復帰」(n=1,100)のセグメントは標本が小さいため、-3.6%をシグナルとは見なさなかった。
この出力で最も価値があるのは仮説4です。AIが「これはデータでは判別できない、telemetryスキーマの変更有無を人が確認してほしい」と、自分の限界を申告した場所です。良いプロンプトは、AIが「分からない」と言えるようにします。そして最後の行 — 標本1,100のセグメントを自らシグナルから除外したこと — がルール3の働いた痕跡です。
この仮説カードをそのまま決定に移してはいけません。人が優先順位を決め、Pythonが再び集計します。
仮説2が反証コスト最安でした。新規狩り場_未プレイのセグメントは、すでにステップ1の表にありました — -5.0%。baselineを維持していました。つまり、狩り場をプレイしなかったユーザーは無事でした。仮説2はその場で棄却され、同時に仮説3(外部要因による全般下落)も弱まりました。 外部要因なら、未プレイも一緒に落ちていたはずだからです。下落は新規狩り場をプレイしたユーザーに集中していました。
そこで仮説1に絞ってPythonを再び回しました。新規狩り場_プレイを初回死亡回数で再分解した結果、6回以上死亡した区間でd30離脱が際立ちました(方向:死亡が多いほど離脱が急になる単調な関係 — 正確な数値はビルドのtelemetryで測定し、ここでは方向のみ)。仮説1の予想パターンと一致しました。
残るは仮説4でした。人がパッチノートを確認しました — telemetryスキーマは無変更。測定アーティファクトの可能性は棄却。これで決定の材料がそろいました。
[ステップ5 人の決定 — 決定カード]
- 採択:新規狩り場の序盤難易度(初回死亡頻度)が、無課金新規離脱の1次要因。次のビルドで1〜5レベル区間の敵密度・体力の下方修正をA/Bテスト。
- 棄却:外部要因説(仮説3)、測定アーティファクト説(仮説4)。
- 保留:報酬カーブ(仮説2の残余)— 狩り場の難易度調整後も下落が残れば再点火。
- AIの役割の記録:診断0件、仮説4件+検証設計の提供。決定は人。
入力(異常シグナル)→抽出→仮説→検証→決定の1サイクルが、ここで閉じます。AIはただの一度も「原因はこれだ」と言いませんでした。検証する道だけを敷きました。これが本章のShow基準です — 「AIがデータを分析した」という文は、何を仮説し、何が反証され、人が何を決定したのかを一度でも最後まで見なければ空虚です。
仮説生成と確定診断の違いは些細に見えますが、決定の安全を分けます。二つを並べると違いは明確です。
| 確定診断(禁止) | 仮説生成(本章の方式) | |
|---|---|---|
| AIの出力 | 「継続率下落の原因は新規狩り場の難易度だ」 | 「難易度仮説 — 初回死亡6+の区間を見よ。こうなら正しく、ああなら誤り」 |
| 人の次の行動 | そのまま書き取って決定 | 最も安い仮説から反証を試みる |
| 誤っていたとき | 誤った決定がビルドへ直行 | 検証段階で棄却、コスト0 |
| 責任の所在 | 「AIがそう言った」(責任が蒸発) | 人が仮説を選んで決定(責任が明確) |
確定診断の本当の危険は、正確度ではなく検証を飛ばさせるという点です。もっともらしい一文は、会議室の疑いを眠らせます。一方、仮説カードはそれ自体が「これを確認せよ」という宿題なので、検証なしには決定に進めない構造です。AIを診断機ではなく仮説発生器として置く理由がここにあります。
データドリブンの最も深い落とし穴は、グッドハートの法則です。「測定指標が目標になった瞬間、その指標はもはや良い指標ではない」。DAUを目標に掲げると、人為的な通知でDAUだけが膨らみ、長期の継続率が削られます。問題は、この歪みがたいてい決定を下したかなり後になってから副作用として現れるという点です。
だからAIをもう1マス早く投入します。決定案をビルドに入れる前に、「このKPIを目標に掲げたら、どう攻略されうるか」をAIに先にやらせます。これは診断ではなくレッドチームです — 自分たちの決定の穴を、わざと探させるのです。
[グッドハート事前警告プロンプト]
今四半期の目標KPIはd7継続率+5%pで、達成手段の草案は7日連続ログインボーナスの 大幅強化だ。あなたがこの決定のレッドチームになって、このKPIを目標に掲げたときに 起こりうるグッドハート歪みのシナリオ3個と、各シナリオで一緒に壊れるガード指標、 そして歪みを早期に捕まえるモニタリングセグメントを表で出してほしい。断定ではなく 「こうなりうる」の形で。
AIが出したのは確定の予言ではなく、疑うべき地点のリストです。核心だけ移すとこうなります。
| グッドハート歪みのシナリオ(仮説) | 一緒に壊れるガード指標 | 早期モニタリング |
|---|---|---|
| ログインだけ踏んで核心コンテンツを未プレイ | セッションあたり戦闘回数・狩り場進入率 | d7継続率↑+戦闘回数↓が同時に発生したら警告 |
| 報酬インフレで経済崩壊 | 通貨のシンク/ソース比率、アイテム相場 | _economy_logのシンク-ソースギャップ拡大を追跡 |
| ログインボーナス終了直後の崖離脱 | d8〜d14継続率(報酬が終わった直後) | d7だけ見ず、d14をペアで |
この表の価値は正解ではなく、決定の前にガード指標をあらかじめペアで束ねておくという点です。d7継続率を目標に掲げるなら、AIが指摘した「戦闘回数」と「d14継続率」を同じ画面に並べておいて見ます。そうすれば、d7が上がっても戦闘回数が一緒に下落した瞬間 — グッドハートの歪みが始まるその瞬間 — に、副作用が四半期末まで蓄積する前に捕まえられます。単一KPIを目標に掲げる代わりにガード指標と束ねるこの習慣が、13.2で決めた「5〜7個のKPIバランス」を決定段階で実際に機能させる方法です。
ここで押さえておくことがあります。AIがこのレッドチームで生み出した価値は「時間の節約」ではありません。人がこの三つのシナリオを思いつくのにかかる時間は、長くありません。本当の価値は、決定するその場で歪みのシグナルを露出させること — ふだん見ていないガード指標を、決定のテーブルの上に引き上げるというシグナル効果です。自動化の価値は時間の節約ではなく、ふだん見えないシグナルを見えるようにすることにあります(プロジェクトAのチームメモリー概念automation_signal_value_over_time_savings)。
仮説生成が、すべての決定に同じように有用なわけではありません。決定の時間軸とデータ密度によって、AI仮説をどれだけ信頼するかが変わります。
| 決定の類型 | データ密度 | AI仮説の位置づけ |
|---|---|---|
| スキルバランスの数値変更 | 高い(シミュレーション・ログが豊富) | 仮説→検証→決定のループをそのまま、AIの補助は強い |
| UIコンポーネントの変更 | 高い(A/Bが可能) | 同様、AI仮説は有効 |
| 新規コンテンツのリリース可否 | 中間(類似コンテンツの参照のみ) | 仮説は参考、決定の重みは人側へ |
| 長期ビジョン・新規分野 | 低い(前例なし) | ループ自体が回らない — 人が決定、AIはリスクの列挙のみ |
ルールは単純です。データの厚い決定ほど§13.3.2のループをそのまま回し、データの薄い決定ほど、AIは仮説発生器からリスクチェックリスト作成器へ役割が下がります。 長期ビジョンをデータで解こうとする試みが危険なのは、未来のデータがない場所でAIが過去のデータからもっともらしい仮説をでっち上げると、その仮説がビジョンを過去へ引き寄せるからです。データのない領域の決定は、回避したりAIに丸投げしたりするのではなく、人が責任を持って下す場所として残しておきます。
[方向標識 — 埋め込みでトピック・コホートを座標化するなら(まだ時期尚早)]
処方ではなく研究動向として読んでください。第13部の二つの場所で、同じ埋め込みの発想が開きます。一つは§13.1の自由回答です — 非定型の自然言語を文埋め込みでクラスタリングすれば、§13.1.2の[曖昧]境界ケースを「二つのトピック中心の間の距離」として座標化し、どの中心からも遠い回答を「新トピックの出現」として標識できます。もう一つは§13.1.4の行動ログです — プレイログを埋め込めば、誰も事前に定義していない「創発コホート」をベクトル空間(付録Mの「地図」)のクラスタとして浮かび上がらせ、§13.3の仮説ループの「検証するセグメント」候補として投入する道が開きます(§13.3.3が前提とした「人があらかじめ定義したセグメント」という限界を1マス突き破る場所です)。ただし、クラスタは原因ではなく仮説にすぎず、小さいクラスタはシグナルではなく(§13.3.3の標本警告と同じ場所)、クラスタに名前を付けるラベリングは依然として人の仕事です(§13.1.1)。何より、圧縮が捨てた次元でライブ事故が起こりえます。だからこの発想は、経済編§8.2.7の「次元ベクトル」の手がかりと正確に同じ場所に置きます(概念の直観は付録M)— 同じtelemetryの土壌の上で、同じ節制で。telemetryが固く敷かれたチームが数年後にのぞき込む方向標識にすぎず、いまやるべきことは§13.3.2のループを正直に回すことです。
本章の数字は、序文「一つの約束」の原則に従います。グッドハートの法則は1975年にチャールズ・グッドハートが定式化した公開命題であり、プロジェクトAの_economy_log・_roi_report.md・_scores_latest.jsonは実在するチームメモリーの産出物で、整合性の失敗時にClickUpへ通知するルールintegrity_check_clickup_notifyはスコア294.93の実運用atomです(付録A.3.6・A.3.1)。§13.3.3では「初回死亡6+の区間で離脱が急になる」という方向だけを仮説検証で確認し、絶対値はビルドのtelemetryに委ねました。セグメント表(baseline 0.41など)はワークフローの形を見せるための例示構成であって、特定四半期の実測公開値ではありません — 覚えるべきは数字ではなく構造です。
| パターン | なぜ失敗するか | 処方 |
|---|---|---|
| AIに「原因は何か」と聞く | もっともらしい確定文が検証なしに決定される | 診断禁止、仮説3〜5個+反証条件を強制(§13.3.3) |
| 標本の小さいセグメントの揺らぎをシグナルに | ノイズを原因と誤認 | 抽出段階でnを一緒に渡し、しきい値を明記 |
| 単一KPIを目標に直行 | グッドハートの歪みが四半期末に爆発 | 決定前のAIレッドチーム+ガード指標のペア(§13.3.5) |
| データのない長期決定をデータで | 過去の仮説が未来のビジョンを引き下げる | データ密度に応じてAIの役割を変える(§13.3.6) |
| 仮説を受け取って検証なしに採択 | 仮説が結論にすり替わる | 最も安い仮説から反証、未プレイのセグメントを活用 |
三つ目が最も遅れて爆発します。d7継続率が上がって決定が成功のように見えるのに、2か月後にd14の崖と戦闘回数の下落が一緒にやって来ます。決定の前にAIレッドチームを一度回す30分が、その2か月を買い戻します。
一人ならここまでで十分:ログパイプラインがなくても構いません。自分のゲーム(またはよく見ているゲームの公開指標)で、最近折れた数字を一つ選んでください。その数字をAIに投げるとき、「原因を教えて」ではなく「確定診断は禁止、検証可能な仮説3個を反証条件付きで」と頼んでみましょう。その中で最も安く確認できる仮説を一つ選び、自分でデータを一度分割してみると、「診断を受け取ること」と「仮説を検証すること」が決定の安全においてどれほど違うか、体で入ってきます。
チームなら、次の一歩から始めましょう。異常指標の抽出スクリプトがセグメント別の数値を出すとき、標本数(n)を必ず一緒に出力するよう1行を足します(§13.3.3のretention_break_extract.py)。そして次のKPI目標を決めるとき、§13.3.5のグッドハート・レッドチームのプロンプトを一度回し、ガード指標のペアを決定カードに記入しておきます。この二つだけでも、「AIが原因を診断した」が「AIが仮説を広げ、人が検証して決定した」に変わります。
主な読者:モバイルファーストのプロジェクトのUX・システムプランナー(中規模(10〜50人)チーム) 一人/趣味の読者向けの縮小バージョン:§14.1.7「一人ならここまでで十分」
PCビルドでは問題なく動いていた戦闘HUDを、初めてモバイル解像度に表示してみた日のことを覚えています。画面の半分がゲージ・アイコン・ミニマップ・クエストトラッカーで覆われ、肝心のキャラクターが見えませんでした。要素の一つひとつは、どれも必要に見えました。問題は、「何を削るか」が会議のたびに毎回ゼロからの争いになったことです。ある人はミニマップを守りたがり、ある人はチャットを守りたがりました。根拠が「感覚」だったため、結論が毎回変わりました。
この章では、その争いを終わらせる方法を扱います。核心は二つです。第一に、モバイルの制約を「感覚」ではなく検証可能なルールブックに変えます。第二に、「PCの30種をモバイルの10種に減らす」退屈で反復的な圧縮作業はAIに任せ、人はルールブック違反を捕まえるレビューだけを行います。モバイルUXの一般知識はすでに他の書籍に十分ありますから、この章はその知識をAIワークフローで回す部分だけに集中します。
モバイルの制約を表で列挙する本はたくさんあります。画面が小さく、指が太く、セッションが短く、バッテリーが減るという話です。どれも正しいのですが、表で覚えたところで、会議での「それで、このボタンはありなのか」という質問には答えが出ません。制約が数値による合格/不合格基準に変わってこそ、AIも人も同じ線を引けます。
幸い、モバイル入力制約の多くは、すでにプラットフォーム企業が公開ガイドラインとして打ち込んでいます。タッチ44pt(HIG)・48dp(Material)・コントラスト4.5:1(WCAG)・間隔8dpといった公開標準は§9.1のルールブックに従い、ここでは本章のlintが直接使う最小タッチターゲット44pt(HIG)だけをインラインで置きます。でっち上げる必要のない数値です。「ボタンが少し小さい気がする」ではなく「このボタンは38ptなのでHIGの44pt未達」と言えてこそ、人が判定してもAIが判定しても同じ判定になります。
ここに一行を加えます — MMORPGのモバイルでは横持ち両手グリップが標準であり、押す要素は左右下コーナーに、消費/スロットは中央下部に置きます(なぜ横持ちが標準なのか、三領域モデルとは何かは§9.1で扱います)。本章のすべての配置判定は、その横持ち両手グリップを前提とします。
プラットフォーム基準をPCと並べて見ると、圧縮の出発点が明確になります。PCは精密・大量(30〜50種を処理可能)、モバイル横持ちは両手のコーナー限定のため12〜16種が限界です(全体の比較表は§9.1のルールブックを参照 — 著者の推定、未検証)。したがってモバイル作業の本質は「デザイン」ではなく「PCの30〜50種をモバイル横持ちの12〜16種へ優先度圧縮」です。そしてこの圧縮は、手作業でやると退屈なうえに、やるたびに基準線がぶれます — 同じルールを疲れずに繰り返し適用する仕事なので、AIがドラフトを作り人がレビューする分担にぴったり当てはまります。
実際にどう回すのか、1サイクルを最後まで見せます。以下は著者のプロジェクト(モバイルファーストMMORPG、以下「プロジェクトA」)の戦闘HUD圧縮セッションを忠実に再現したものです。入力プロンプトはそのままコピーして使え、出力は実際のセッションを再構成したものです。
まず、PC HUD要素のリストを機械が読める表にします。これはすでにマスターデータにあるので、新しく書くのではなく抽出するだけです。
# hud_pc_inventory.yaml — PCビルド現行HUD (抜粋、30種中12種)
- id: hp_bar # HPバー
현재위치: 左上
상시노출: true
조작가능: false
- id: mp_bar # MPバー
현재위치: 左上
상시노출: true
조작가능: false
- id: skill_slots # スキル12枠
현재위치: 下部中央
상시노출: true
조작가능: true
- id: minimap # ミニマップ
현재위치: 右上
상시노출: true
조작가능: true
- id: quest_tracker # クエスト追跡
현재위치: 右側
상시노출: true
조작가능: false
- id: chat # チャットウィンドウ
현재위치: 左下
상시노출: true
조작가능: true
# ... buff_bar, party_frame, target_frame, exp_bar, currency, mail_alert ...
添付した hud_pc_inventory.yaml (PCビルド現行HUD 30種) を、モバイル横持ちグリップの両手操作
基準で優先度圧縮してください。各要素をP0 (戦闘中常時必須)〜P3 (削除または状況に応じて表示) に
分類し、常時表示 (P0〜P1) の合計は16種を超えないこと。操作可能な要素 (조작가능:true) は
両手の親指が届くコーナー (左下=移動、右下=スキル) への配置を提案してください。分類ごとに根拠を
一行ずつ必ず付け、曖昧なものは無理に決めず、別途マークして人に回してください。
出力は表 (要素 | 分類 | 配置領域 | 根拠 | 曖昧) のみ、説明は不要です。
このプロンプトで、圧縮しろという指示そのものは一行だけで、残りの分量はすべて出力を囲い込む柵だという点に注目してください。AIに自由を与えると、一般的なRPGの平均を吐き出します。だから分類基準(P0〜P1 ≤ 16種)・出力形式(表)・根拠の義務、そして「曖昧なら人に回せ」という逃げ道をあらかじめ入力しておきます。圧縮の品質はこの四つの柵が決めます。
| 要素 | 分類 | 配置領域 | 根拠 | 曖昧 |
|------------|------|--------------|----------------------------------------|------|
| hp_bar | P0 | 上部固定 | 死亡に直結、0.5秒単位の確認が必要 | |
| skill_slots| P0 | 右下(右親指) | 操作の核心、右親指が届くコーナー必須 | |
| mp_bar | P0 | 上部固定 | スキル使用可否の判断、常時必要 | |
| target_frame| P0 | 上部中央 | 対象の体力確認、戦闘判断の核心 | |
| minimap | P1 | 右上縮小 | 方向確認用、戦闘中は縮小/半透明可 | |
| buff_bar | P1 | hp_bar下 | バフ満了の確認、アイコン縮小可 | |
| quest_tracker| P2 | メニュートグル | 戦闘中は不要、平常時はトグル表示 | |
| chat | P2 | 左下タブ | 戦闘中は自動非表示、タブで展開 | ★ |
| party_frame| P1 | 左側縮小 | パーティプレイ時はP0へ昇格 (条件付き) | ★ |
| mail_alert | P3 | コンテキスト | 受信時のみ一時表示 | |
| exp_bar | P3 | 画面最下部1px | 情報価値が低い、細い線で十分 | |
| currency | P2 | メニュー内 | 戦闘中は不要 | |
常時表示(P0+P1)合計: 7種 (ルール2通過: ≤16)
曖昧2件(chat、party_frame)は人の決定が必要:
- chat: ソーシャル比重が高いゲームならP1昇格を検討
- party_frame: ソロ/パーティのコンテキストによって分類が分かれる
この出力で最も価値があるのは表ではなく、一番下の2行、「曖昧2件」です。AIが自分の限界を申告し、判断を人に渡した場所です。プロンプトに逃げ道を開けておくと、AIは無理に答えをでっち上げる代わりに、わからない場所に旗を立てます。
この出力をそのまま受け取ってはいけません。ルールブックで一度叩きます。実際にこのセッションでは1件が引っかかりました。
party_frameをAIは「左側縮小」に配置しましたが、横持ちグリップでは画面左側中央は両手の親指のどちらも届かない領域です(左手は左下の移動、右手は右下のスキルに縛られています)。ところがパーティフレームは、クリック(パーティメンバーのターゲティング)が必要な操作要素です。ルール3(「操作可能要素は両手親指の届きやすいコーナー」)違反です。AIは조작가능フラグをparty_frameで見落としました。これは入力yamlでparty_frameの조작가능が空だったせい — つまり人間側のデータ欠陥でした。
そこで再依頼します。
party_frameはパーティメンバーのターゲティングクリックが必要な操作要素です (さっきの入力から
抜けていました)。操作要素は親指が届くコーナーに置くルールで、配置をやり直してください。
ソロのときとパーティのときを分けて提案してください。
この1往復で終わりです。AIはソロ時は「非表示」、パーティ時は「下部右側(届きやすい)へ昇格」と答え直し、その決定はルールブックを通過しました。圧縮30種を人がゼロからやれば半日、AIドラフト + ルールブックレビュー + 1回の往復なら1時間以内です(著者の推定 — 正確な節約時間はチームや要素数によって変わるため、絶対値よりも「ゼロから手作業」と「ドラフト+レビュー」の構造の違いとして読むのが正しいです)。
上のセッションで繰り返された「指の領域」を図で一度固定しておくと、以降のすべての配置判定が速くなります。横に持ったスマートフォンでは、指が届き視線が頻繁に向かう下部は三つの場所に分かれます。左手の親指は左下(移動)、右手の親指は右下(スキル)のコーナーに届き、両親指の間の中央下部は消費アイテム・自動アイテム・スキルスロットを置く場所です。瞬間的な反応を要するトゥイッチ操作ではありませんが、自分が使うものや自動で消費されるものを一目で確認し、時々押す重要なグランス領域です。P0の操作・スロットは緑、指が届かず読むだけの上部・中央上方は赤です。
ルールは単純です。読むだけの情報(HP/MP/ターゲットの体力)は赤(上部・中央上方)に置いて構いません。指が届く必要がないからです。逆に押す要素は指の領域(緑・アンバー)の中でなければなりません — 移動・スキルは左右の下コーナーに、消費・自動アイテムとクイックスロット・スキルスロットは中央下部に置きます。三つとも指が届き、視線が頻繁に向かう場所です。§14.1.2でparty_frameが引っかかった理由は、この図1枚で説明できます — 押す要素を、指の領域ではない左側中央(読み取り領域)に置いたからです。
圧縮案がルールブックを守っているかを毎回目視で確認すると、また見落とします。§14.1.1の五つのルールのうち、座標・サイズで判定できるものはコードにレビューさせます。人は、コードが捕まえられない「曖昧」判定だけに時間を使います。
# hud_lint.py — モバイルHUD配置案の検証 (骨格)
# 入力: AIが提案した配置案 (要素ごとの座標・サイズ・조작가능・분류)
# 出力: ルールブック違反のリスト
MIN_TAP_PT = 44 # Apple HIG 最小タッチターゲット (pt)
def in_action_zone(e, w, h):
"""横持ちグリップで指が届く領域: 左・右下コーナー + 中央下部スロット帯。"""
x, y = e["x"] / w, e["y"] / h
bottom = y > 0.55
left_corner = bottom and x < 0.30 # 左手親指 = 移動
right_corner = bottom and x > 0.70 # 右手親指 = スキル
center_slot = (y > 0.72) and (0.35 <= x <= 0.65) # 中央下部 = 消費・クイックスロット
return left_corner or right_corner or center_slot
def lint(elements, screen_w, screen_h):
issues = []
for e in elements:
# ルールA: 操作/スロット要素は指の領域 (両コーナー + 中央下部) になければならない
if e["조작가능"] and not in_action_zone(e, screen_w, screen_h):
issues.append(f"[A] {e['id']}: 操作・スロット要素が指の領域の外に配置 "
f"(x={e['x']}, y={e['y']})")
# ルールB: タッチターゲット最小サイズ (HIG 44pt)
if e["조작가능"] and min(e["w"], e["h"]) < MIN_TAP_PT:
issues.append(f"[B] {e['id']}: タッチターゲット {min(e['w'], e['h'])}pt "
f"< {MIN_TAP_PT}pt (HIG未達)")
# ルールC: P0/P1常時表示の総量
onscreen = [e for e in elements if e["분류"] in ("P0", "P1")]
if len(onscreen) > 16:
issues.append(f"[C] 常時表示 {len(onscreen)}種 > 16種 (過密)")
return issues
この30行があれば、会議で「このボタン、小さくないですか」は議論の種ではなく判定の対象になります。[B] skill_slots: タッチターゲット 40pt < 44pt (HIG未達)とコードが出力すれば、意見を集める必要はありません。直せばいいのです。これは9.1(HUD)で扱ったlintゲートをモバイルの次元に移したものです — 決定論で捕まえられるものはコードが、非決定的で判断が必要なものは人が受け持つという分担は、モバイルでもそのまま成立します。
サイクル全体を一目で見るとこうなります。
flowchart LR
A["PC HUD 30種
(マスターデータから抽出)"] --> B["AI圧縮
P0〜P3分類 + 配置"]
B --> C{"hud_lint.py
ルールブック自動検証"}
C -->|違反| D["再依頼
(漏れ・誤配置の修正)"]
D --> B
C -->|通過| E["人によるレビュー
「曖昧」判定のみ"]
E --> F["モバイルHUD確定
12〜16種前後"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class C code;
class B ai;
class E human;
class A data;
class F pass;
人の手が触れる場所は2か所だけです。入力データをきれいに入れる場所(先頭)と、ルールブックが捕まえられない曖昧な判断を下す場所(最後)。その間の退屈な30種の圧縮は、AIとlintが回します。
本章に出てきた数値の出典だけを短く記録しておきます(本書全体の数値原則は序文「一つの約束」を参照)。タッチ44pt(HIG)・48dp(Material)・コントラスト4.5:1(WCAG)はプラットフォームの公式標準であり、「常時情報8〜12種」と「圧縮半日→1時間」は著者の経験に基づく推定(未検証)なので、絶対値よりも方向として読みます。モバイルHUDで実際に測定可能な指標はルールブック違反件数(lint 0)、常時表示要素数(目標 ≤12)、誤タップ率(telemetry)であり、継続率(リテンション)のような結果指標はHUD一つで左右されないため、因果関係を断定しません。
| パターン | なぜ失敗するのか | 処方 |
|---|---|---|
| PC HUDをそのまま縮小移植 | 30種が6インチを覆い、ゲームが見えない | §14.1.2の圧縮セッション |
| 「AIよ、モバイルUIを作って」と丸ごと委任 | ルールブックなしでは一般的なRPGの平均が出てくる | ルールブック(§14.1.1)を先にプロンプトに入力する |
| 圧縮案を目視だけでレビュー | タッチサイズ・親指ゾーン違反を毎回見落とす | hud_lint.pyで自動検証 |
| 根拠なしの「これは削ろう」会議 | 結論が毎回変わる | P0〜P3 + 一行根拠の強制 |
一人ならここまでで十分:マスターデータがなくても大丈夫です。自分のゲーム(または好きなゲーム)のPC HUD要素を手で10〜15個だけ書き出してyamlにし、§14.1.2のプロンプトをそのまま貼り付けて一度回してみましょう。AIの分類に同意できない項目を1個見つけて「根拠をもう一度示せ」と反論してみると、圧縮がどのような判断の束なのかが体に入ってきます。
チームの場合は、次の一歩から始めましょう。現行HUD要素のリストをhud_pc_inventory.yamlとして抽出し(すでにマスターデータにあります)、§14.1.4のhud_lint.pyのルールブック3行(タッチサイズ・親指ゾーン・総量)を先にコードとして固定しておきます。ルールブックがあれば、AIの圧縮案でも人の試案でも、同じ物差しで測れます。
アルファビルドを初めてPCで動かした日、企画チームのメッセンジャーチャンネルにスクリーンショットが1枚投稿されました。モバイルでは画面下部いっぱいに広がっていたバーチャルジョイスティックが、27インチモニターの真ん中に手のひらサイズで浮かんでいました。誰かが一言コメントを付けました。「これ、マウスでどうやって掴むんですか?」コアロジックは無事でした。戦闘も、インベントリも、クエストもそのまま動いていました。壊れていたのはただ一つ、入力と画面をモバイル前提で固定していた部分でした。
同じゲームをiOS・Android・PCの3か所に出すと、運営の単位が×3になりそうに思えますが、実際はそうではありません。コアロジックは1つで、そこにプラットフォーム適応レイヤーが×3で付きます。問題は、「どこまでがコアで、どこからが適応レイヤーか」を人がいちいち判断するのは難しいという点です。iOSでは動くのにAndroidだけ壊れる分岐、PCでだけ意味のあるキーマッピング — こうした違いは頭の中に収まりきりません。そこで本章の核心は、プラットフォーム制約をルールブック(rulebook)として明文化し、そのルールブックを根拠にAIに分岐案を生成させ、最後にlintがルール違反を検出するワークフローです。
まず、違いの地形を見ます。以下は、プロジェクトA(著者が企画ディレクターとして参加しているモバイル優先のMMORPG)でPC補助リリースを検討しながら整理したプラットフォーム制約表です。数値のうち公開標準に基づくものは出典を併記し、それ以外はプロジェクト内部の合意値です。
| 領域 | iOS | Android | PC |
|---|---|---|---|
| 入力 | タッチ | タッチ(+一部キーボード) | キーボード・マウス・ゲームパッド |
| 最小タッチターゲット | 44pt(Apple HIG) | 48dp(Material) | クリック — 該当なし |
| 画面 | 4.7〜6.7インチ | 4.5〜7インチ(ばらつき大) | 21〜32インチ |
| 決済 | App Store | Google Play | 独自・Steam |
| 通知 | APNs | FCM | OS・独自 |
| 保存 | iCloud | Google Drive・独自 | Steam Cloud・独自 |
| OSの世代交代サイクル | 1〜2年 | 1年(断片化が大きい) | 5〜10年 |
iOSとAndroidは決済・保存・通知のAPIが異なりますが、ユーザーが見る画面と操作はほぼ同じです。PCは入力・画面・視覚効果が丸ごと異なります。そのため運営負担は直感に反して×3ではなく×2に近いのです — iOSとAndroidの間の距離が短いためです。
ここで重要なのは表そのものではなく、この表を人が読む文書ではなく機械が読むルールブックに変えることです。そうして初めて、AIが分岐案を作るときの根拠になり、lintが違反を検出できるようになります。
プロジェクトAのフォルダ構造は、コア1つにプラットフォーム適応レイヤー3つを付ける形です。
game/
├── core/ — ゲームロジック(プラットフォーム非依存)
│ ├── combat/ inventory/ narrative/ ...
├── platform/ — プラットフォーム適応レイヤー
│ ├── ios/ → input/ payment/ notification/
│ ├── android/ → input/ payment/ notification/
│ └── pc/ → input/ payment/ ui/
└── shared/ — 双方で使用(ユーティリティ・レンダリング)
ルールは1つです。coreはplatformを名前で呼ばない。coreがif platform == "ios"のような文を持った瞬間、レイヤー分離は崩れます。入力を例に取ると、coreは「スキル1を使う」という意図(InputIntent.SKILL_1)だけを知っていて、その意図をタッチ座標から取り出すのか、キーボードの1から取り出すのかは、各platformレイヤーが責任を持ちます。
この線を引いておくと、次の段階が可能になります。新しいプラットフォームを追加するとき、coreに手を触れずにplatform/配下のフォルダを1つ埋めるだけで済みます。以下は、この線が実際にどう分かれるのかを1枚で見た図です。
iOSとAndroidのボックスは同じ青系で、PCだけがオレンジです — 違いの大きさを色で示しました。運営負担の非対称性がここで一目で分かります。
核心の転換点はここです。プラットフォーム制約を散文の文書に書いておくと、人は忘れます。代わりに宣言的なルールブックファイル1つに集めます。プロジェクトAで使っているplatform_rules.yamlの抜粋です(実際のファイルから本章用に核心ルールだけを抜き出しました)。
# platform/platform_rules.yaml
targets:
ios:
min_touch_pt: 44 # Apple HIG
contrast_ratio: 4.5 # WCAG SC1.4.3
gamepad: optional # iOS 17+ 標準
forbidden_in_core: ["import platform.ios", "StoreKit", "APNs"]
android:
min_touch_dp: 48 # Material
contrast_ratio: 4.5
forbidden_in_core: ["import platform.android", "BillingClient", "FCM"]
pc:
min_target_px: 24 # WCAG SC2.5.8 (ポインター)
input: ["keyboard", "mouse", "gamepad"]
forbidden_in_core: ["import platform.pc", "SteamAPI"]
required_intents: ["MOVE_FORWARD", "ATTACK", "SKILL_1", "SKILL_2"]
このファイルが3つの役割を同時に果たします。(1) AIが分岐案を作るときに読む仕様、(2) lintが検証する基準、(3) 人が合意を記録する単一の情報源です。forbidden_in_coreが特に重要です — coreフォルダ内に絶対に登場してはならないトークンのリストなので、レイヤー侵犯を機械的に検出する根拠になります。
ここからは実際の作業を最初から最後までたどります。状況はこうです。PC補助リリースを準備する中で、モバイルにしかなかった入力ハンドラーをPC用に分岐する必要があります。ルールブックを根拠にClaudeからPC入力アダプターの草案を受け取り、lintで検証し、拒否・再依頼まで1サイクルを回します。
以下は、私たちのプロジェクトのプラットフォームルールブックの抜粋と、既存のiOS入力ハンドラーです。
[platform_rules.yaml 上記の抜粋を貼り付け]
# platform/ios/input/touch_handler.py from core.input.input_intent import InputIntent def map_touch_to_intent(touch_event): if touch_event.location in skill_button_1_area: return InputIntent.SKILL_1 if touch_event.is_drag_up: return InputIntent.MOVE_FORWARD return Noneこれを参考に
platform/pc/input/keyboard_handler.pyの草案を作ってください。制約:(1) coreはimportしてよいが、platform.ios/androidは絶対にimportしないこと、(2)required_intentsの4つをすべてマッピングすること、(3) キーマッピングはユーザーが変更できるようdictに分離すること。確信のない部分は明示してください。
草案です。キーマッピングをモジュール上部のdictに分離して、ユーザーがカスタマイズできるようにしました。
# platform/pc/input/keyboard_handler.py from core.input.input_intent import InputIntent DEFAULT_KEYMAP = { "w": InputIntent.MOVE_FORWARD, "1": InputIntent.SKILL_1, "2": InputIntent.SKILL_2, # ATTACKはマウス左クリックを想定 — キーボードのデフォルトは未指定 } def map_keyboard_to_intent(key_event, keymap=DEFAULT_KEYMAP): return keymap.get(key_event.key.lower()) def map_mouse_to_intent(mouse_event): if mouse_event.button == "left": return InputIntent.ATTACK return None⚠️ 確信のない部分:
required_intentsにATTACKが含まれていますが、私はこれをキーボードではなくマウスにマッピングしました。ルールブックのrequired_intentsが「入力デバイスを問わずマッピングされていればOK」なのか、「単一ハンドラー内ですべて処理」なのか、仕様の記述がないため前者と仮定しました。後者であれば、lintがATTACK欠落として検出する可能性があります。
ここでClaudeが指摘した⚠️のポイントが、まさに罠でした。私たちのlint(platform_lint.py)は、ハンドラーのファイル単位ではなくプラットフォーム単位でrequired_intentsの充足を検査します。ATTACKがkeyboard_handler.pyになくても、mouse_handler側にあれば通過です。ところがClaudeが作った出力は、マウスマッピングをkeyboard_handler.pyファイルの中に一緒に入れてしまいました — ファイルの責任が混ざっています。構造上は通過するでしょうが、私たちのフォルダ規則(入力デバイスごとのファイル分離)に違反します。拒否です。
拒否理由は2行で明確です。(1) マウスマッピングは別のmouse_handler.pyに分離すること。(2) ATTACKをキーボードでも使えるよう、Spaceをfallbackとして置くこと。
再依頼の後に受け取った分離版をplatform_lint.pyにかけました。lintはルールブックを読んで次を検査します。
$ python platform_lint.py platform/pc/
[core-leak] PASS — core/ 内にforbiddenトークン0件
[intent-cover] PASS — pc: MOVE_FORWARD, ATTACK, SKILL_1, SKILL_2 (4/4)
[touch-target] SKIP — pcはmin_target_px=24 (UIレイヤーで別途検査)
[no-cross-import] PASS — platform.pcがplatform.ios/androidを未参照
intent-coverが4/4で出ることが核心です。AIが作った草案がルールブックの基準を満たしているかを、人の目ではなくスクリプトが確定しました。この1行が、マルチプラットフォーム運営で人が毎回頭の中で検算していた作業を置き換えます。
このサイクルを図に圧縮すると次のようになります。
flowchart LR
R[platform_rules.yaml
ルールブック] --> P[プロンプトに
ルールブック+既存ハンドラー]
P --> A[Claude分岐案
+ 不確実表示]
A --> H{人による検証}
H -->|拒否: ファイル責任の混在| P
H -->|受け入れ| L[platform_lint.py]
L -->|FAIL| P
L -->|PASS| M[ビルド分岐へ]
R -.基準を提供.-> L
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class L code;
class A ai;
class H human;
class R data;
class M pass;
ルールブックがプロンプトとlintの両方に基準を供給している点が、この構造の中心です。AIが生成し、人が判断し、lintが確定する — 3つの役割が同じルールブックを見ています。
ハンドラーがそろえば、ビルドは単純な組み立てです。coreとsharedは固定で、platformフォルダ1つだけを差し替えます。
[core/ + shared/ + platform/ios/] → iOSビルド
[core/ + shared/ + platform/android/] → Androidビルド
[core/ + shared/ + platform/pc/] → PCビルド
CIではこの3つを順次ではなく並列で回し、各ビルド直後にplatform_lint.pyを自動実行します。順次で回すとビルド時間が3倍になり、lintを外すとルール違反がデプロイ段階まで生き残ります。並列ビルド+自動lint、この2つがマルチプラットフォームCIの最小要件です。
リリースサイクルはプラットフォームごとに異なるため、ビルドが通過したからといって同時にデプロイはしません。iOSの審査は通常1〜3日かかるため頻繁なリリースには保守的で、Androidは数時間以内に反映されるためより頻繁に出せて、Steamは1〜2日ほどです。同じ変更でもiOSが最も遅く出ることになるので、ホットフィックスのスケジュールは常にiOS基準で逆算します。
コードの下では画面も分かれます。経験上、推奨される分布は共通コンポーネント80%、プラットフォームバリアント(サイズ・位置だけ異なる)15%、プラットフォーム専用5%です。ただしこの比率はジャンルによって揺れます — カジュアルパズルなら共通が90%まで上がり、MMORPGは入力の違いのためバリアントがさらに増えます。
専用コンポーネントはプラットフォームの魅力を生かす場所なので、何でも共通化するのが答えではありません。モバイルのバーチャルジョイスティック・振動、PCのキーマッピングUI・ゲームパッド設定のように、そのプラットフォームでだけ意味のあるものがここに入ります。ただし専用が30%を超えたら、それは魅力ではなく運営負担のシグナルです — lintにplatform-specific-ratio警告を仕掛けておけば、人が忘れてもビルドが指摘してくれます。
ここまでがAI補助の限界線でもあります。プラットフォームの違いは大部分が決定論的なルールの領域なので、AIが自由に候補を探索するよりも、ルールブックを満たす分岐案を生成するのに使われます。入力マッピングの推薦、Figma案のプラットフォームバリアント変換、多言語×マルチプラットフォームのテキスト適応くらいがAIが実質的に貢献するポイントで、その出力は常にlintを通過しなければなりません。進歩的な自動化の前に、アダプターの標準化が先です。
レイヤー分離の最大の効果は新規プラットフォーム追加の速度です。単一コードベースにif文を積み上げてPCを付け足すと、実質的には新しいゲームを作るコストに近くなりますが、coreに手を触れずにplatform/pc/だけを埋めれば、その時間は大きく減ります。新規プラットフォーム追加がどれだけ速くなるかの比率はプロジェクトごとに異なるため、具体的な倍率は断定しません — ただ私たちの内部検討では、PC補助追加のスケジュールが単一コード前提に比べて半分以下に減ると見積もりました(著者の推定、未検証)。副次効果として、プラットフォームごとの事故が隔離され、core変更の信頼度が上がります(1か所だけ直せば3つのビルドに一貫して反映されます)。
よく踏む罠と処方は次のとおりです。
| 罠 | 処方 |
|---|---|
coreにif platform == ...分岐が激増 |
forbidden_in_core lintで遮断し、アダプターに分離 |
| AI分岐案を人の目だけでチェック | platform_lint.pyでintent-coverを確定 |
| 入力デバイスのマッピングを1ファイルに詰め込む | デバイスごとにハンドラーを分離(keyboard/mouse) |
| 専用コンポーネント30%+ | platform-specific-ratio警告、共通化の検討 |
| ビルド通過後すぐ3プラットフォーム同時デプロイ | リリースサイクルの違いに合わせてiOS基準で逆算 |
罠の共通点は「人が記憶で防ごうとした」という点です。ルールブックに書いてlintにかければ、人が忘れてもビルドが覚えています。
setup. プロジェクトにplatform/platform_rules.yamlを作り、上記の抜粋のようにプラットフォームごとのmin_touch、contrast_ratio、forbidden_in_core、required_intentsを書きましょう。数値はでっち上げず、公開標準から持ってきます(タッチ44pt・48dp・コントラスト4.5:1などの公開標準は§9.1のルールブックに従います。PCのポインターターゲット24pxはWCAG SC2.5.8です)。
prompt. ルールブックの抜粋+既存の1プラットフォームのハンドラーを一緒に貼り付けて、こう依頼してみましょう。「このルールブックを守ってplatform/<新プラットフォーム>/input/ハンドラーの草案を作ってください。forbidden_in_coreのトークンを絶対に入れず、required_intentsをすべてマッピングし、確信のない部分を⚠️で表示してください。」
verify. ルールブックを読んで次を検査するplatform_lint.py(40行のスクリプトで十分です)を回しましょう。(1) coreフォルダ内のforbidden_in_coreトークン0件、(2) プラットフォームごとのrequired_intentsすべてマッピング済み、(3) platformフォルダ間のcross-importなし。1つでもFAILならプロンプトに戻り、拒否理由を書いて再依頼します。
一人で作業していてビルドCIもないなら、ルールブックをYAMLの代わりにMarkdownのチェックリスト1枚に縮めましょう。「ターゲット≥44pt、coreにプラットフォームimport禁止、意図4つをマッピング」の3行で十分です。lintスクリプトの代わりに、AIに成果物を渡して「このチェックリスト3項目を1つずつ通過/失敗で判定してください」と指示すれば、人の検算の代わりになります。核心はツールの規模ではなく — 基準を頭の外に書いておき、生成と検証を分離することです。
QAビルドを受け取ったチームメンバーBが、片手でスマホを握ったまま眉をひそめました。「スキルを3回押したのに、2回しか出ないんです」。画面をのぞき込むと、親指がスキルボタンを押すその瞬間、同じ指がボタンの横1/3を覆っていました。マウスでテストしていたときには一度も起きなかった問題です。マウスには指がありませんから。
この場面が、タッチとマウスの本質を一行で要約しています。どちらも「一点を指す」入力ですが、一方は指すための道具が画面を覆い、もう一方は覆いません。同じ行動を二つの入力で別々に解かなければならない理由は、ここから始まります。本章ではまず二つの入力の違いを整理し、入力マッピングをAIに提案させたあと、衝突・到達性を自分の手で検証するワークド・トランスクリプトという一本の背骨を、最後までたどります。
指の太さ、視界の遮り、マルチタッチの上限、精度がすべて異なります。表に固定する前に、一つの場面で感覚をつかんでおきましょう。マウスカーソルは1ピクセルのペン先で、指は直径1センチ近いハンコです。ペン先は文字を書けますが、一度に1文字ずつです。ハンコは速く押せますが文字は書けず、押す瞬間に紙が見えなくなります。
| 属性 | タッチ | マウス |
|---|---|---|
| 精度 | 約7〜10mm(指の接触面) | 1px単位 |
| 視界の遮り | 指が接触点の周囲を覆う | なし |
| ホバーの可否 | ほぼ不可(接触=入力) | 自由(移動≠入力) |
| 同時入力 | 2〜10点のマルチタッチ | 左・右・中・ホイール |
| ドラッグ/タップの区別 | 時間と距離で推論が必要 | クリック/ドラッグが明確 |
| ハプティックフィードバック | 可能 | ほぼなし |
この中でデザインへの影響がもっとも大きい2行が、「視界の遮り」と「ホバー」です。視界の遮りは結果をどこに表示するかを強制し、ホバーの不在は、モバイルではツールチップという情報チャネルが丸ごと一つ消えるという意味です。残りの4行は、この2行から派生する細部に近いものです。
公開標準がこの違いを数値で打ち付けています — タッチ44pt(HIG)・48dp(Material)・コントラスト4.5:1・タッチターゲット24CSSピクセル(WCAG SC2.5.8)といった公開標準は§9.1のルールブックに従います。これらの数字は好みではなく人体と測定の産物であり、マッピングを検証するときに当てる物差しも、結局この標準なのです。
移動・攻撃・スキルの3つの行動を二つの入力で解くと、次のように分かれます。一つの行動に方式が3つずつあるというのは、正解がないという意味ではなく、ゲームのアイデンティティが選択を強制するという意味です。
著者が作業しているプロジェクトA(モバイル優先のMMORPG)は、移動についてモバイルはⓐ+ⓑのハイブリッド(ジョイスティックと自動移動の併用)、PCはWASD+自動移動を採用しました。攻撃はモバイルがⓑ+ⓐ(敵をタップしてからボタン)、PCはⓐ・ⓑの自由選択です。スキルはモバイルがⓐ、またはターゲティング時はⓑ、PCはキー1〜8にマウス照準を載せます。同じゲーム、同じ行動なのにマッピング表が2枚できるという点が、本章のすべてです。
問題は、マッピング表が長くなるほど衝突が隠れることにあります。スロットの長押し(スキル照準)と画面ドラッグ(カメラ回転)が同じ領域で重なったらどうなるでしょうか。1〜8のキーがスキルに割り当てられているのに、誰かがインベントリのショートカットも1で提案してきたら? 人の目で表をなぞるだけでは見落とします。だからこそ、マッピングはAIに提案させつつ、検証はツールに任せるワークフローが必要です。
ここからが本章の背骨です。PCのキーボード/マウスマッピングの草案をClaudeに提案させ、その出力を人が検証し、衝突・到達性のlintを回して却下し、再依頼する1サイクルを丸ごと載せます。要約はしません。生の出力には間違いも含まれていて、私たちはその間違いを捕まえる過程を見ようとしているのです。
あなたはPC向けMMORPGの入力マッピングを設計する。以下の行動リストをキーボード/マウスにマッピングしたJSONを提案すること。
行動: 移動(前後左右)、ダッシュ、ジャンプ、通常攻撃、スキル1〜8、インベントリ、マップ、キャラクター画面、メニュー、視点ロックトグル、ズームイン/ズームアウト、コンテキストメニュー(補助行動)、自動移動トグル。
制約: - 業界の慣習に従うこと(WASD移動、ESCメニューなど)。 - 一つの物理キー/ボタンに二つの行動を重ねないこと。 - 各項目に
action、input、noteフィールドを付けること。 出力はJSON配列のみ。
[
{"action": "move_forward", "input": "W", "note": "標準の移動"},
{"action": "move_back", "input": "S", "note": "標準の移動"},
{"action": "move_left", "input": "A", "note": "標準の移動"},
{"action": "move_right", "input": "D", "note": "標準の移動"},
{"action": "run", "input": "Shift", "note": "押している間ダッシュ"},
{"action": "jump", "input": "Space", "note": "標準"},
{"action": "attack_basic", "input": "Mouse1", "note": "左クリックの主行動"},
{"action": "skill_1", "input": "1", "note": ""},
{"action": "skill_2", "input": "2", "note": ""},
{"action": "skill_3", "input": "3", "note": ""},
{"action": "skill_4", "input": "4", "note": ""},
{"action": "skill_5", "input": "5", "note": ""},
{"action": "skill_6", "input": "6", "note": ""},
{"action": "skill_7", "input": "7", "note": ""},
{"action": "skill_8", "input": "8", "note": ""},
{"action": "inventory", "input": "I", "note": "Tabはターゲット切り替えに譲る"},
{"action": "map", "input": "M", "note": ""},
{"action": "character", "input": "C", "note": ""},
{"action": "menu", "input": "Escape", "note": "標準"},
{"action": "cam_lock", "input": "Mouse3", "note": "ホイールクリックで視点ロックをトグル"},
{"action": "zoom_in", "input": "WheelUp", "note": ""},
{"action": "zoom_out", "input": "WheelDown", "note": ""},
{"action": "context_menu", "input": "Mouse2", "note": "右クリックの補助行動"},
{"action": "auto_move", "input": "Mouse2", "note": "右クリックダブルで自動移動"}
]
出力はきれいに見えます。だから危険なのです。一番下の2行を見てください。context_menuとauto_moveが、どちらもMouse2(右クリック)に割り当てられています。制約に「一つのキーに二つの行動を重ねないこと」とはっきり書いたにもかかわらず、モデルは「ダブルクリックで区別できる」という言い訳をnoteに添えて重ねました。これが、AIの出力をそのままビルドに入れてはいけない理由です。人が表をなぞると23行中22行目と23行目の衝突は見落としやすく、モデルは自分の衝突を合理化します。
だから検証を目ではなくコードに任せます。衝突(同じ入力の重複)と到達性(必須行動の欠落、両手の親指コーナーの外)を検査する小さなlintを回します。
# input_lint.py — 入力マッピングの衝突・到達性検査
import json, sys
from collections import defaultdict
REQUIRED = {"move_forward","move_back","move_left","move_right",
"attack_basic","menu","inventory","map"}
def lint(mapping):
errors, warns = [], []
seen = defaultdict(list)
for m in mapping:
seen[m["input"]].append(m["action"])
# 1) 衝突: 同じ入力に2つ以上の行動
for inp, acts in seen.items():
if len(acts) > 1:
errors.append(f"CONFLICT {inp} <- {', '.join(acts)}")
# 2) 到達性: 必須行動の欠落
actions = {m["action"] for m in mapping}
for r in sorted(REQUIRED - actions):
errors.append(f"MISSING required action '{r}'")
# 3) 空のnote警告(設計意図の未記載)
for m in mapping:
if not m["note"].strip():
warns.append(f"NO_NOTE {m['action']} ({m['input']})")
return errors, warns
data = json.load(open(sys.argv[1], encoding="utf-8"))
errs, warns = lint(data)
for e in errs: print("[ERROR]", e)
for w in warns: print("[WARN] ", w)
print(f"\n=> {len(errs)} error(s), {len(warns)} warning(s)")
sys.exit(1 if errs else 0)
上のJSONをclaude_map.jsonとして保存し、lintを回した実際の出力はこうです。
[ERROR] CONFLICT Mouse2 <- context_menu, auto_move
[WARN] NO_NOTE skill_1 (1)
[WARN] NO_NOTE skill_2 (2)
[WARN] NO_NOTE skill_3 (3)
... (skill_4~8も同様)
=> 1 error(s), 8 warning(s)
lintは、人の目が見落としたただ一つの衝突を正確に拾い上げました。到達性検査は通過しました(必須8行動がすべて存在)。空のnote8件は警告にすぎずビルドは止めませんが、設計意図の未記載という負債をあらわにします。次は却下の理由を持って、モデルに突き返します。
lintの結果、
Mouse2にcontext_menuとauto_moveが重なっているため却下する。ダブルクリックでの区別は右クリックに遅延が生じ、戦闘中に誤作動する。auto_moveは別の入力に分離すること。また、skill_1〜8のnoteが空になっている — 各スロットがどの系統のスキルなのか、1行ずつ埋めること。
{"action": "context_menu", "input": "Mouse2", "note": "右クリック = 補助/コンテキスト行動のみ"},
{"action": "auto_move", "input": "Numpad0", "note": "自動移動トグル、戦闘キーと物理的に分離"},
...
{"action": "skill_1", "input": "1", "note": "近接の主力スキル"},
{"action": "skill_8", "input": "8", "note": "緊急回避/生存スキル — 小指の到達限界のためQへの再配置を検討"}
再出力の最後の行が興味深いところです。モデル自身が「8番キーは小指の到達限界」だとして、到達性の問題を自己申告しました。これは次節で扱う到達性検証と、まさに同じテーマです。lintをもう一度回すと0 error(s)で通過します。核心はこれです。AIは23行の草案を素早く作りますが、その草案の合法性は、人が定義したルール(REQUIRED集合、衝突の定義)とコードが保証します。提案はモデル、判定はツール、決定は人です。
1回の物理入力がゲーム行動に変換される経路を描いておくと、先ほどのlintがどの地点に挟まるのかが見えてきます。
flowchart TD
A[物理入力
タッチ座標 / キー・マウス] --> B{入力分類}
B -->|接触200ms↓ & 5px↓| C[タップ / クリック]
B -->|接触200ms↑ or 5px↑| D[ドラッグ]
C --> E[マッピングテーブル参照]
D --> E
E --> F{lint通過
マッピングか?}
F -->|衝突・欠落| G[ビルド遮断
input_lint.py]
F -->|正常| H[ゲーム行動ディスパッチ]
H --> I[行動実行]
I --> J[フィードバック出力
視覚+ハプティック/サウンド]
G -.修正後に再提出.-> E
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class B,E,F code;
class G fail;
左上から入ってきた物理入力は、まずタップかドラッグかに分類されます(次節の200ms/5px基準)。分類された入力はマッピングテーブルを参照しますが、そのテーブルがビルドに入る前にinput_lint.pyを通過しなければならない、という点がこの図の核心です。衝突や欠落があればディスパッチ段階に進めず、遮断されます。マッピング検証はランタイム以前、ビルドゲートで終わらせるべきです。
ここからは、マッピングが通過したものとして、そのマッピングが指と出会う表面を設計します。
原則1 — 最小タッチ面積。 Apple HIGの44pt、Materialの48dpが下限です。HD画面ではおおよそ100px(Retina 2倍環境では200px)前後に取れば、二つの標準を同時に満たします。これを下回ると、冒頭の「3回押して2回」が統計として現れます。
原則2 — 親指の到達領域。 モバイルMMORPGは横持ち両手グリップが標準で、押す要素は左右の下コーナーに、消費アイテム/スロットは中央下部に置きます(3領域モデルの根拠は§9.1)。P0行動(左=移動、右=攻撃・スキル)は左右下の2つのコーナーの中に、あまり見ない情報は到達限界の外である上部に置きます。2つのコーナーを合わせても画面の半分に満たないという点が、入力設計の核心です。次のSVGが、横向きモードでの両手の親指の到達領域と中央下部のスロット帯を示しています。
濃い扇形が親指が無理なく届く場所で、淡い扇形が手を伸ばさないと届かない限界です。14.3.3の再出力でモデルが申告した「8番キーは小指の限界」がPC版の話だとすれば、モバイル版での相当する間違いが、まさにこの淡い領域にP0ボタンを置くことです。
原則3 — 視界の遮りの回避。 指は接触点だけを隠すのではなく、その上に手全体が画面を覆います。右下のスキルをタップすると、右下の約1/4が見えなくなります。だから行動の結果(ダメージ数字、状態変化)は、指が届かない領域に表示します。左側のジョイスティックを握る手はキャラクターとミニマップの位置を侵すので、ミニマップは右上に移します。
原則4 — ドラッグ/タップの区別。 マウスと違い、タッチはユーザーの意図を時間と距離で推論しなければなりません。ゲーム全体で一つの基準に統一します — たとえば接触200ms以内かつ移動5px以内ならタップ、それ以上ならドラッグ。この2つの数字がばらつくと、「タップしようとしたらキャラクターが転がった」といった意図の失敗が積み重なります。先ほどのmermaidの分岐点が、まさにこの判定です。
原則5 — ハプティック。 振動は、画面を見ていなくても伝わる唯一のチャネルです。ただし、すべての入力に振動を付けるとノイズになります。通常のタップは無振動、スキル使用は短く、決済確認のような危険行動は強く、敵撃破は微細に — 4〜5種類以内で運用します。
マウスは、タッチにはない3つの贅沢を享受します。ホバー、複数ボタン、カーソルの精度です。
原則1 — ホバー。 マウスは押さなくても指せます。スキルスロットにマウスを載せると名前・クールタイム(クールダウン)・説明のツールチップが表示され、クリックすると使用されます。タッチにはこの中間状態がないので、ホバーはPCが情報をさらに載せられる通路です。ただし、ホバーだけに依存する情報はモバイル版で行き場を失うという点を、14.3.3のマッピング段階であらかじめ意識しておく必要があります。
原則2 — 複数ボタン。 左クリックは主行動、右クリックは補助/コンテキスト、ホイールクリックは視点リセット、ホイールはズーム。先ほどのlintが捕まえた衝突は、まさにこの右クリックに二つの行動を重ねた事例でした。ボタンが多いからと全部埋めようとして、衝突を作ってしまうのです。
原則3 — キーボード標準。 ESC=メニュー、M=マップ、1〜8=スキル、WASD=移動、Shift=ダッシュ、Space=ジャンプ。ユーザーが学ばなくても推測できなければなりません。標準から外れるキーには相応の理由をnoteに書いておきます。14.3.3でTabをインベントリではなくターゲット切り替えに譲った決定が、その例です。
原則4 — 視点制御。 マウスドラッグで視点を回しつつ、カーソルを画面にロックするゲームモードと解除するUIモードを明確にトグルします。このトグルが曖昧だと、メニューを閉じたのにカーソルが消えるという混乱が生じます。
原則5 — マクロ・自動化の許容範囲。 自動攻撃・自動移動をどこまで許容するかは、ゲームのアイデンティティの問題です。緩めすぎるとPCがマクロ画面になり、一律に禁止するとモバイルから移ってきたユーザーの参入障壁が高くなります。正解はスペクトラムのどの点をゲームの色に合わせて選ぶかであって、両極端ではありません。
原則はプラットフォームごとに異なりますが、ユーザーが同じ行動に対して受け取る「感触」は、プラットフォームが変わっても同じであるべきです。モバイルからPCに移ってきたユーザーに、ボタンの光り方の意味を学び直させてはいけません。
| 状況 | タッチ | マウス |
|---|---|---|
| 入力認識 | ボタンの発光 + 短いハプティック | ボタンの発光 + クリック音 |
| 入力失敗 | ボタンの揺れ + ハプティック | ボタンの揺れ + 警告音 |
| クールタイムの進行 | 円形ゲージ | 円形ゲージ |
| 使用可能への回復 | 発光 + ハプティック | 発光 + サウンド |
視覚チャネル(発光・揺れ・ゲージ)は両者で同一とし、補助チャネルだけをプラットフォームに合わせてハプティック↔サウンドで分けます。この一貫性が、マルチプラットフォームユーザーの学習コストを半分に減らします。
| パターン | 処方 |
|---|---|
| ボタンが標準下限(44pt/48dp)未満 | 100px前後に強制 |
| 指で隠れる領域に結果を表示 | 隠れない領域へ移動 |
| ハプティックの乱発 | 4〜5種類以内 |
| 右クリックに二つの行動が重複 | lintで衝突を遮断してから分離 |
| ホバー専用の情報をモバイルにそのまま | モバイルはタップ/長押しの代替チャネル |
| キーマッピングの固定 | ユーザーカスタマイズを許可 |
| 両プラットフォームに同一マッピングを強制 | プラットフォームごとに自然なマッピング |
この表の4行目が、14.3.3のワークド・トランスクリプトの結論です。右クリックの衝突は、人の目で表をなぞるだけではほぼ毎回見落とされ、lintをビルドゲートに掛けておけばほぼ毎回捕まります。
input_lint.pyをプロジェクトに置きます。REQUIRED集合を自分のゲームの必須行動に置き換えます。python input_lint.py claude_map.jsonで回しましょう。ERRORが0になるまで、却下の理由(衝突した入力・欠落した行動)を明示して再依頼します。WARN(空のnote)は、設計意図の未記載という負債として別途記録します。一人で作る小さなゲームなら、ツールを減らしましょう。行動が10個以内なら、REQUIRED集合と「同じ入力の重複」検査の二つだけを残した20行のlintで十分です。AIにマッピングを出させ、このミニlintで衝突だけをふるいにかけたあと、実機で親指(または小指)が届くか一度押してみてください。提案はモデル、衝突の判定はコード、到達の判定は自分の手 — この三つさえ守れば、規模にかかわらず通用します。
第一読者: リリース後の運営(ライブオプス)を初めて担うプランナー(中規模、10〜50人のチーム) 一人/趣味の読者向け縮小バージョン: §15.1.7「一人ならこれだけで十分」
前提: 著者はグローバルにリリースされたモバイルMMORPGの運営を、P2E(Play To Earn)経済まで含めて経験しており、そこに現在のプロジェクトのリリース前AIワークフローを合わせて本章を書いています。ワークド・トランスクリプトは「入力→AI組み合わせ→ルールブック検証→人による選択」パターンを、運営の様式で実際に1回回した結果です。推定と観察は推定・観察と明示し、でっち上げたKPIの表は載せていません。
リリース翌日の朝のオフィスは、リリース前とは違います。マイルストーンが終わったのに仕事は減らず、むしろ単位だけが細かくなります。四半期単位で組まれていたスケジュールが、週・日・時間単位に刻まれていきます。そして毎週、同じ質問が会議室に戻ってきます。「今週末のイベント、何を回しましょうか?」
この質問が毎週白紙から始まるなら、運営チームはすぐに疲弊します。本章は、その質問を白紙から引き上げる方法を扱います。核心は2つです。第一に、イベントとシーズンを毎回新しくひねり出す代わりに、検証済みの様式のライブラリーとして蓄積しておくこと。第二に、「その様式を組み合わせて来週の候補を5つ作る」という退屈な下書き作業をAIに任せ、人はルールブック検証を通過した候補のどれを採択するかだけを決めること。ゼロから作るのと、5つの中から選ぶのとでは、作業負担が違います。
運営の標準サイクルを表で覚えさせる本は多くあります。月曜に報告し、火・水に準備し、金曜に配信するという話です。どれも正しいのですが、表を覚えるだけでは、「今週のイベント」という毎週戻ってくる決定がどのように下されるのかが見えません。運営の本質はスケジュール表ではなく閉じたループです — 候補が生まれ、検証を通過し、人が選び、ビルドとして出ていき、ユーザーデータが再び次の候補の入力になる一巡です。
このループの上で、運営の4軸(コンテンツ・イベント・バランス・CS)がそれぞれの速度で回ります。コンテンツは月〜四半期、イベントは週〜月、バランスは週〜隔週、CSは日・時間単位です。4軸がばらばらに回ると、同じユーザーデータを見ても毎週違う決定が出てきます。だからこそ4軸を1つのループに束ね、そのループの1マス(イベント候補の生成)をAIが回せる形にすることが、本章の目標です。
flowchart TD
A["入力
KPI推移・ユーザーsegment
・前回イベントの効果"] --> B["AI組み合わせ
シーズンルール × イベントテンプレート
ライブラリー → 候補5つ"]
B --> C{"ルールブック検証
インフレ上限・目的の衝突
・報酬範囲・期間"}
C -->|違反alert| D["再依頼
(違反候補の差し替え)"]
D --> B
C -->|通過| E["人による選択
ディレクターが採択/棄却"]
E --> F["ビルド・配信
(不可逆: シーズン開始・告知)"]
F --> G["ユーザーデータ・フィードバック
(次のループの入力へ)"]
G --> A
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class A,G data;
class B ai;
class C,F code;
class E human;
人の手が触れる場所は2か所だけです。いちばん上で入力(KPI・segment・過去の効果)をきれいに入れる場所、そして検証を通過した候補のどれを出すかを決める場所です。その間の退屈な「組み合わせを5つひねり出す」と「ルール違反をふるいにかける」は、AIとルールブックが回します。そしていちばん下の1行 — ビルドとして出ていったイベントが生んだユーザーデータが、再び入力として戻ってくるという点 — が、このループを運営らしくします。リリース前の設計は一度出たら終わりですが、運営は結果が次の入力になります。
このループに入る2つのライブラリー(シーズンルール・イベントテンプレート)の具体は§15.2で、最後のマス(ユーザーフィードバックの自動分類)は§15.3で見ます。本章は、ループを最後まで一巡することに集中します。
実際にどう回すのか、1サイクルを最後まで見せます。以下は、著者がリリース前のコンテンツツールで検証した「ライブラリー組み合わせ → ルールブック検証 → 人による選択」パターンを運営の様式(シーズンルール + イベントテンプレート)に移し、実際に1回回したセッションを再現したものです。入力プロンプトはそのままコピーして使えます。出力はそのセッションを再構成したものです。
まず、組み合わせの材料2つを、機械が読める形で置きます。イベントテンプレートライブラリー(検証済みの様式)とシーズンルールライブラリー、そして今週の現在状況(KPI・segment)です。ライブラリーは一度作っておけば毎週再利用します。
# event_templates.yaml — 検証済みイベントテンプレートライブラリー (抜粋、9種中4種)
- id: tpl_attendance # ログインボーナス
目的: [新規流入, 休眠復帰]
期間_推奨: 7~14日
報酬等級: 低~中
- id: tpl_coop_raid # 協力レイド
目的: [既存活性化, コミュニティ]
期間_推奨: 3~7日
報酬等級: 中~高
- id: tpl_pvp_season # PvPシーズン
目的: [コミュニティ, 既存活性化]
期間_推奨: 14~28日
報酬等級: 高
- id: tpl_limited_package # 限定パッケージ
目的: [売上]
期間_推奨: 3~7日
報酬等級: 高 (課金連動)
# season_rules.yaml — シーズンルールの断片 (抜粋)
season_inflation_cap: 四半期あたり '高' 等級報酬イベント ≤ 3回
purpose_conflict_rule: 同一週に [売上] 目的イベント2つの同時実施を禁止
overlap_rule: '高' 報酬イベントは同時2つ禁止 (疲労・インフレ)
# current_state.yaml — 今週の状況
週: 2026-W23
直近2週_売上イベント: 1回 (四半期累計 '高' 等級2回)
DAU_推移: 緩やかな下落 (直近4週 -6%, 業界観察上 '警戒' 区間)
主要_segment: 復帰可能_休眠層の比重が上昇
今後の外部日程: なし
添付したテンプレート・シーズンルール・今週の状況のyamlから、来週のイベント候補を5つだけ組み合わせて。
新しいメカニズムは作らず、添付テンプレートの組み合わせのみで。シーズンルール違反の有無は自分で表示し、
各候補がいまの信号(DAU下落・休眠復帰)になぜ合うのか、一行ずつ根拠を付けて。
目的が売上だけに偏らないよう分散させ、曖昧なものは表示してこちらに渡して。
表のみで、列は候補・テンプレート組み合わせ・主目的・期間・ルール違反・根拠・曖昧。
ルール5行のどれ1つとして「何を作るか」を語っていない点に注目してください。すべて「どの枠の中で、何を根拠に答えるか」という制約です。AIに自由を与えると、一般的なRPGの平均的なイベントを吐き出してくるからです。材料をライブラリーに制限(ルール1)、シーズンルールの自己点検義務(ルール2)、現在の信号への対応根拠義務(ルール3)、目的の分散(ルール4)、そして「曖昧なら人に渡せ」という逃げ道(ルール5) — 5行が合わさって、候補の形ではなく候補の資格を定めます。
以下は、要約していない生の出力です。ルール違反と曖昧の申告が混ざっています。
| 候補 | テンプレート組み合わせ | 主目的 | 期間 | ルール違反 | 根拠 | 曖昧 |
|-----|------------------------------|-----------|-------|-----------|----------------------------------------------|------|
| C1 | tpl_attendance | 休眠復帰 | 10日 | なし | 復帰休眠層の比重上昇に直接対応、報酬 '中' | - |
| C2 | tpl_coop_raid | 既存活性化 | 5日 | なし | DAU下落期の接続動機付け、コミュニティ刺激 | - |
| C3 | tpl_attendance+tpl_coop_raid | 休眠復帰+活性化 | 7日 | なし | ログインボーナスで復帰を誘引 + レイドで残存、結合シナジー | - |
| C4 | tpl_pvp_season | コミュニティ | 21日 | なし | 長期シーズンで残存曲線そのものを引き上げる | ★ |
| C5 | tpl_limited_package | 売上 | 5日 | ★違反 | 四半期 '高' 等級累計2回 → 追加時に上限へ近接/超過 | - |
ルール点検結果:
- C5: season_inflation_cap違反リスク。今四半期の '高' 等級報酬イベントが
すでに2回。C5を加えると3回で上限の境界。DAU下落期の売上プッシュは
ユーザー疲労を増しかねず、信号とも逆方向。
- C4: ルール違反はないものの、21日のPvPシーズンは今週1週間ではなく3週間を
占有する。'今週の候補' として適切かは運営ポリシーの判断が必要 → 曖昧と表示。
- C1・C2・C3はルール通過、現在の信号(休眠・DAU)と整合。
出力でいちばん価値があるのは、表ではなくいちばん下のルール点検3行です。AIがC5のインフレ違反を自分で捕まえて表示し、C4は「違反ではないが判断が分かれる」と人に渡しました。よいプロンプトは、AIが自分の限界を申告し、人にボールを渡せるようにします。
この出力をそのまま受け取ってはいけません。ルールブックでもう一度ふるいにかけ、その次に人が選びます。このセッションでは、実際に2つの判断が分かれました。
まず、C5は拒否です。AIがすでにインフレ違反を表示しており、ルールブックのコード(§15.1.3)も同じ判定を出しました。四半期「高」等級の上限に掛かり、DAU下落期の売上プッシュは現在の信号と逆方向です。議論することはありません。外します。
次はC4(21日のPvPシーズン)です。AIが「曖昧」として渡してきた場所です。ルール違反はありませんが、これは「今週のイベント」ではなく「今シーズンの決定」です。1週間のループで即決する案件ではなく、シーズン統合会議に上げるべきものです。そこで今週の候補からは保留し、シーズンカレンダーの議題として別に切り出します。
残ったC1・C2・C3の中から、ディレクターが選びます。現在の信号(復帰休眠層の上昇 + DAUの緩やかな下落)にいちばん合うのは、C3(ログインボーナス+協力レイドの結合)でした。ログインボーナスで休眠層を呼び込み、レイドで呼び込んだユーザーをつなぎ留める結合シナジーが、今週の信号と整合していました。C1・C2は来週の候補プールに残しておきます。
ここで終わらなかった候補が、もう1つありました。C3の採択を決めてみると、7日間の期間の最終日が、近づいている定期メンテナンス日と1日重なっていました。そこで再依頼が1回回ります。
C3を採択する。ただし7日間の期間のうち、最終日が定期メンテナンス日と重なる。
メンテナンスでイベント終盤の参加が途切れないよう、期間を調整して再提案して。
報酬総量は維持し、日程だけ前倒しして。
AIは開始日を1日前倒しし、メンテナンス前に終了するように答え直し、その調整はルールブックを通過しました。入力 → AI組み合わせ → ルールブック検証 → 人による選択 → 日程再調整という1サイクルが、ここで閉じます。
この一巡が、本書全体のShowの基準です。AIが何を組み合わせ、ルールブックが何をふるいにかけ、人が何を選び何を拒否するのか。これを一度でも最後まで見なければ、「AIでイベント候補を出す」という文は空虚です。
候補がシーズンルールを守っているかを毎週目視で確かめていると、また見落とします。§15.1.2の3つのルールのうち、数字で判定できるものはコードにチェックさせます。人は、コードが捕まえられない「曖昧」と「選択」にだけ時間を使います。
# event_lint.py — 来週のイベント候補の検証 (骨格)
# 入力: AIが組み合わせた候補リスト + シーズンルール + 四半期累計の状態
# 出力: ルール違反リスト (自動拒否ではなくalert)
def lint(candidates, season, quarter_state):
issues = []
high_used = quarter_state["high_reward_count"] # 四半期累計 '高' 等級回数
for c in candidates:
# ルールA: インフレ上限 (四半期あたり '高' 等級 ≤ 3)
if c["報酬等級"] == "高" and high_used + 1 > season["inflation_cap"]:
issues.append(f"[A] {c['id']}: '高' 等級の追加で四半期上限 "
f"{season['inflation_cap']}回を超過 (現在 {high_used})")
# ルールB: 同一週 [売上] 目的2つ禁止
sales = [c for c in candidates if "売上" in c["目的"]]
if len(sales) > 1:
issues.append(f"[B] [売上] 目的の候補が{len(sales)}件同時 → 1件に制限")
# ルールC: 目的の偏り (5件中1つの目的が過半なら分散不足)
from collections import Counter
top = Counter(c["主目的"] for c in candidates).most_common(1)[0]
if top[1] > len(candidates) // 2:
issues.append(f"[C] 目的 '{top[0]}' {top[1]}件の偏り (分散不足)")
return issues
このコードが、会議での「これは報酬が強すぎませんか?」という押し問答を、数字1行で片付けます。[A] tpl_limited_package: '高' 等級の追加で四半期上限 3回を超過 (現在 2)とコードが出力すれば、議論することはありません。外せばよいのです。§14.1(モバイルHUD)で扱ったlintゲートを、運営の次元に移したものです — 決定論で捕まえられるものはコードが、判断が必要なものは人が受け持つという分担は、運営でもそのまま成立します。
ただし、1つだけ違います。このlintは、違反を発見しても自動で候補を廃棄しません。alertを上げるだけです。§6.2(都市ジェネレーター)で見たのと同じ設計です。自動拒否型の検証を付けると、意図された変則(例: 四半期上限を知ったうえで、意図的に売上イベントを入れるキャンペーン決定)まで機械が殺してしまいます。疑わしい候補は機械が拾い、生かすか殺すかはディレクターが決めます。§15.1.2でC5を拒否したのも、lintが殺したのではなく、lintのalertを見て人が下した決定でした。
上のループがリリース前の設計ループと決定的に違う点は2つです。表で並べるより、この2つだけを正確に押さえます。
第一に、結果が次の入力になります。リリース前は、仕様書を書けばビルドまで一方向に流れます。運営では、今週のイベントが生んだユーザーデータ(参加率・離脱・売上・フィードバック)が、来週の候補組み合わせの入力(current_state.yaml)として戻ってきます。§15.1.1のループのいちばん下の矢印が、その回帰です。だから運営のKPIは「一度うまく当てること」ではなく、「毎週信号に合わせて調整すること」です。
第二に、実験コストは小さくなりますが、不可逆な地点はより鋭くなります。リリース前は一度の決定が四半期を左右しましたが、ライブでは1週間のイベントを回してみて、合わなければ翌週に変えます。ロールバック可能な実験が増えます。しかし、シーズン開始とイベント告知は不可逆です。§5.4.5で扱った「録音・キャスティング = 不可逆段階」の原則が、そのまま働きます。ユーザーがすでに見たシーズンルール・報酬は、「取り消し」てもコミュニティの認識に痕跡を残します。だから§15.1.1のループのすべての検証(AI組み合わせ・ルールブック・人による選択)は、ビルド・告知という不可逆のマスに入る前の可逆段階で終えなければなりません。C4(21日シーズン)を今週の即決から外してシーズン会議に上げたのも、この原則です — 不可逆な地点が大きい決定ほど、より長い可逆の検討を経ます。
この2つが、運営をリリース前の設計とは別の仕事にします。残り(時間単位が四半期→週、フィードバックがベータ→リアルタイム)は、この2軸の派生です。
§15.1.2のワークド・トランスクリプトは、進歩的適用の一場面です。AIが候補を組み合わせ、人は採択を決めました。しかし、すべてのチームが最初からここまで来るわけではありません。段階があります。
保守的適用では、人が候補を発議します。運営チームが月曜の会議でイベントを直接企画し、シーズンルールを手で書き、ユーザーフィードバックを手動で分類します。自動が受け持つのは、測定(KPIダッシュボード)とリグレッションテスト(ビルド検証)だけです。業界の観察では、現在の大半のライブMMORPG運営はこの段階に近いところにあります。
進歩的適用では、「イベント候補の発議」と「フィードバック分類」までAIが草案を出します。§15.1.2が前者の場面で、後者(フィードバックの自動クラスタリング)は§15.3で見ます。人の決定は、「どの候補を採択するか」「AIが分類したフィードバックをどう受け止めるか」といったメタ決定に絞られていきます。
進歩的適用が根づくには、3つがそろう必要があります。イベントテンプレート・シーズンルールが再組み合わせ可能な単位に分離・蓄積されたライブラリー(§15.1.2のevent_templates.yamlがその種)、現在の信号を入力として候補を草案の形で出す候補ジェネレーター(§15.1.2のプロンプト)、そして入ってくるフィードバックを自動分類するクラスタリング(§15.3)です。この3つが§5.3.12(ワールドBT(BehaviorTree、ビヘイビアツリー)・クエストクラウド)・§8.1.8(進歩的バランシング)と同じ骨格だという点が、本書の一貫したメッセージです — 分野は違っても、「検証済みの部品をライブラリーとして蓄積し、AIが組み合わせ候補を出し、人が採択する」という構造は同じです。
ここで、1つはっきりさせておきます。ライブラリー・候補ジェネレーター・クラスタリングのような発想は、2010年代にも理論的には可能でした。塞がっていたのは、AIがイベント告知文・ルール説明のようなユーザーが読む自然言語を書けず、日に数百〜数千件のフィードバックを自然言語で要約・分類できなかったからです。LLMの発展(2023〜)以降その2つの壁が下がり、紙の上にしかなかった進歩的運営のかなりの部分が、実現の領域に入ってきました。
| パターン | なぜ失敗するか | 処方 |
|---|---|---|
| 毎週白紙からイベントを企画 | 運営チームがやがて消耗し、候補の質がコンディション次第で揺れる | イベントテンプレートライブラリーとして蓄積(§15.1.2) |
| 「AIよ、イベントを作って」と丸投げ | ライブラリー・ルールなしでは一般的なRPGの平均が出てくる | 材料の制限 + シーズンルールの自己点検を強制(§15.1.2) |
| 候補を目視だけでチェック | インフレ・目的の偏りを毎週見落とす | event_lint.pyで自動検証(§15.1.3) |
| lintを自動拒否型にする | 意図されたキャンペーン決定まで機械が殺す | alertのみ、採択はディレクター(§15.1.3) |
| 不可逆な決定を週次ループで即決 | シーズン告知後のロールバックがコミュニティに痕跡を残す | 大きな決定はシーズン会議に分離(§15.1.4) |
| 単一KPI(DAU・売上)だけを追う | ユーザー疲労が蓄積し、信号と逆方向の候補を採択 | current_stateに多軸の信号を入力(§15.1.2) |
setup → prompt → verifyの順で、一歩だけやってみましょう。
event_templates.yamlの形式で手で書き出します(目的・期間・報酬等級だけ)。シーズンルールは1行のものが3つあれば十分です — インフレ上限、目的の衝突禁止、重複禁止。current_state.yamlに書き込んで1回回します。一人ならこれだけで十分: ライブラリーのyamlも、lintのコードも要りません。好きなゲームの直近1四半期のイベントを5〜6個だけ思い出し、「目的・期間・報酬」の3列で書き出してみましょう。それだけでも、そのゲームが毎週白紙からひねり出していたのではなく、様式を回して使っていたことが見えてきます。その表が、あなたの最初のテンプレートライブラリーです。
チームなら、次の一歩から始めましょう。直近1〜2四半期のイベントを集めてevent_templates.yamlに正規化し(検証済みの様式だけ)、シーズンルール3行を先にevent_lint.pyとしてコードに入れておきます。ライブラリーとルールがあれば、AIの組み合わせ候補でも人の試案でも、同じ物差しで測れます。
第一読者:運営(ライブオプス)を担うMMORPGプランナー(中規模(10〜50人)チーム) 一人/趣味の読者向け縮小版:§15.2.9「一人ならこれだけ」
サービス4年目の運営型ゲームの月曜会議を思い出します。翌週のイベントを何にするかが、毎週白紙から始まりました。誰かが「この前のログインイベントの報酬を少し上げて、もう一度やるのは?」と言えば、別の誰かが「それは2か月前にやった」と返し、報酬をいくら上げるかはまた勘で決めていました。会議が終わると、運営プランナー1人が半日かけてイベントの様式をゼロから埋めていました。毎週、白紙から、半日です。
問題は、アイデアが足りないことではありませんでした。運営チームはすでに、ログイン・協力・競争・復帰という検証済みのイベント骨格をいくつか頭の中に持っていました。その骨格にテーマと報酬だけを差し替えれば、1週間分のイベントができあがります。ただ、その「差し替え」を毎回手作業で、勘でやっていたため、遅く、結果もぶれていました。
本章では、その差し替えをAIに任せる方法を扱います。核心は2つです。第一に、検証済みのイベント骨格をバリエーション展開できるテンプレートyamlとして入力しておくこと。第二に、テンプレートから翌週の候補を複数出す退屈な仕事はAIに任せ、人は報酬範囲・重複をコードで弾いたうえでトーンだけをチェックすることです。イベント企画の一般論(ログインは新規流入に効き、協力は活性化に効く、といった類)はすでに他の本に十分ありますから、本章はその知識をAIワークフローとして回す場にだけ集中します。
著者の運営経験メモ(正直に) リリース後の運営(ライブオプス)を1〜2年単位で直接担った経験は、著者のキャリアの中では一部に限られます。本章のワークフローは、著者が運用している量産・チェックツール(コンテンツ・HUD)をイベント分野に移したものであり、効果の数値が業界観察+著者の推定であることは本文中でそのつど明示します。ツールの構造(テンプレートyaml・lint・チェックゲート)は、著者が実際に運用しているコンテンツ量産ツールと同じ骨格です。
イベント量産の全体の流れは4段階です。核心は、1段目(テンプレート)と3段目(lint)が決定論で、2段目だけがAIだという点です。コンテンツ量産(§6.2)・HUD圧縮(§14.1)で見たのと同じ分担です。ルールブックが入力と検証を両側から押さえてくれれば、間に挟まったAIが毎回少しずつ違うバリエーションを出しても、報酬バランスとスケジュールはぶれません。
flowchart TB
A["入力: イベントテンプレートyaml
(検証済みの骨格 — ログイン・協力・競争・復帰)
+ 今四半期のテーマ・禁止報酬・カレンダースロット"]
A --> B["2段目 AI: バリエーション候補の生成
同じ骨格 × 別のテーマ・報酬・期間
→ 候補5~10件 (様式ドラフト)"]
B --> C{"3段目 決定論: event_lint.py
報酬範囲・インフレ上限・日程重複
・直前N週の同一骨格の反復"}
C -->|違反 WARN| D["運営チェックゲート
(採用・棄却・微調整)"]
C -->|通過| D
D -->|再依頼| B
D -->|採用| E["ビルド反映 → 告知
(不可逆ゲート)"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class A data;
class B ai;
class C,E code;
class D human;
この図で人の手が触れる場所は2か所だけです。いちばん上でテンプレートと今四半期の制約をきれいに入れる場所、いちばん下でlintには捕まえられない「このテーマはいまのうちのゲームの空気に合うか」を判断する場所です。その間にある退屈な候補量産と報酬の算数は、テンプレートとAIとlintが回します。
決定的な設計は、lint(3段目)が違反を見つけても候補を自動で捨てず、運営ゲート(4段目)へWARNとして上げるだけ、という点です。その理由は§15.2.5で見ます。そして、いちばん最後の矢印(告知)が不可逆だという点が、ライブオプスを他の量産と分けます。都市NPCは気に入らなければビルド前に廃棄すれば済みますが、ユーザーに告知されたイベントを巻き戻すときは、コミュニティの信頼コストを支払うことになります(§15.2.7)。
運営チームが持つ検証済みの骨格を、様式として固定します。自由様式の仕様書のままでは、AIは何を変えればいいのか分かりません。スロットが分かれていて初めて、「このスロットだけ差し替えろ」が成立します。
# event_templates/coop_raid.yaml — 協力レイドの骨格 (検証済み、4回運営)
template_id: coop_raid
purpose: [既存_活性化, コミュニティ] # 1~2個だけ。4つ同時の追求は禁止
core_loop: 期間内にサーバー全体が貢献を累積 → 段階別に全サーバー報酬を解放
duration_range: [5, 10] # 日。10日を超えると疲労が蓄積
slots: # ← AIがバリエーションを作る欄。骨格は固定
theme: { type: 自由, 제약: 四半期_テーマ_遵守 }
boss_or_target: { type: 自由, 제약: 既存_ボス_アセット_再利用_優先 }
reward_tiers: { type: 報酬リスト, count: 3~5, 제약: reward_policy 参照 }
reward_policy: # ← lintが読む欄。バリエーション禁止
강화석_per_event_max: 30 # イベント1回の支給上限
골드_per_event_max: 50000
한정코스튬: 許可 (永久所有, 経済影響 0)
현금성재화_직접지급: 禁止
inflation_guard:
강화석_분기_누적상한: 90 # 四半期内の全イベント合算
post_event_kpi: # ← 事後の自動計測スロット
- 参加率 (イベント露出に対し1回以上参加)
- 強化石の価格変動 (事後30日, 目標 ±10%)
- イベント後の平日プレイ時間 (依存度のシグナル)
いちばん重要な分離は、slots(AIがバリエーションを作る)とreward_policy(lintが読み、AIは手を触れられない)の分離です。テーマとボスは毎回違っていて構いませんが、強化石の支給上限はゲーム経済が定めた一線です。この一線をAIが呼び出しのたびに違う数字で出してしまえば、インフレはその場で始まります。だから報酬の項目はAIが提案しつつ、報酬の量はポリシーの範囲内でしか動かないようlintが弾きます。
同じフォルダーにdaily_attendance.yaml(ログイン)、pvp_ladder.yaml(競争)、comeback.yaml(復帰)が同じ形式で入っています。この4枚が、今四半期の候補生成の入力プールのすべてです。
実際にどう回すのか、1サイクルを最後までお見せします。入力プロンプトはそのままコピーして使えますし、出力は実際の量産セッションを再構成したものです。
添付したcoop_raid.yaml(4回検証済みの協力レイド骨格)で、来週(W2)のバリエーション候補を5件だけ出して。
今四半期のテーマは「夏 — 水・祭り・暑さ」。
core_loopは絶対に触らず、slots(テーマ・ボス・報酬)だけ変えること。
報酬はreward_policyの上限内のみ。候補ごとに、なぜこのテーマ・報酬なのか1行ずつ添えて。
直前3週はログイン・PvPラダー・協力レイドだったから、協力レイドをまた回す候補には[反復注意]、
確信が持てなければ[あいまい]を付けてこちらに回して。表のみで。列は候補名・テーマ・報酬tier要約・根拠・フラグ。
6行のルールのうち、テーマや報酬を指定する行は1行もありません。すべて「ここまでだけ変えろ、これを超えるな、迷ったら申告しろ」という柵です。「夏イベントを作って」のように手綱を緩めると、ありふれたモバイルRPGの平均が出てくるからです。バリエーションの範囲(slotsのみ)、報酬上限(reward_policy)、反復注意(直前3週の履歴)、そして「あいまいなら人へ」 — この柵が骨格を守らせ、バリエーションを解き放ちます。
| 候補名 | テーマ | 報酬tier要約 | 根拠 | フラグ |
|--------------|----------------|----------------------------------|------------------------------------|------------|
| 氾濫の堤 | 川の氾濫を防ぐ | 強化石25 / ゴールド4万 / コスチューム | 協力=堤防築きの隠喩、累積貢献に直結 | |
| 灯籠流し | 夏の水祭り | 強化石20 / ゴールド3万 / 称号 | 祭りのトーン、競争より雰囲気の転換 | |
| 猛暑ボス戦 | 暑さの化身を討伐 | 強化石30 / ゴールド5万 / コスチューム | 既存ボスアセットの再利用、報酬上限 | [反復注意] |
| 海流レース | 潮流に乗って競争 | 強化石28 / ゴールド4.5万 / マウント | 協力+競争の混合 | [あいまい] |
| 錦鯉収集 | 収集型協力 | 強化石22 / ゴールド3.5万 / 称号 | ライトユーザーの参入障壁が低い | |
[反復注意] 猛暑ボス戦: core_loopがW-3の協力レイドと事実上同一。
3週間隔で同一骨格の反復 → ユーザー疲労の懸念。別の骨格(ログイン/収集)を推奨。
[あいまい] 海流レース: 協力の累積 + 個人競争を1つのイベントに混在。
purposeスロットが[活性化+競争]に増加。1~2目的の原則と衝突の可能性。
出力でいちばん価値があるのは表ではなく、いちばん下の2行です。AIが「猛暑ボス戦は3週間前の骨格と同じ」「海流レースは目的が2つに増えた」と自分の限界を申告し、人に引き渡した場所です。良いプロンプトは、AIが「これは確信が持てません」と言えるようにします。
次は、この候補の束をlintが弾く番です。
候補が報酬ポリシーと日程の重複を守っているかを毎回目視で確かめていると、また見落とします。reward_policy・inflation_guard・カレンダーで判定できるものは、コードにチェックさせます。人は、コードでは捕まえられないトーン・面白さの判断にだけ時間を使います。
# event_lint.py — イベントのバリエーション候補を検証 (骨格)
# 入力: AIが提案した候補リスト + テンプレートのポリシー + 四半期カレンダー
# 出力: WARNリスト (自動廃棄ではない — 運営ゲートへ上げる)
def lint(candidates, policy, quarter_ledger, recent_weeks):
warns = []
stone_used = sum(quarter_ledger.강화석) # 今四半期にすでに支給した累計
for c in candidates:
# A: イベント1回の報酬上限 (ポリシー)
if c.강화석 > policy["강화석_per_event_max"]:
warns.append(f"[A] {c.name}: 強化石 {c.강화석} > 上限 "
f"{policy['강화석_per_event_max']} (イベントあたり超過)")
# B: 四半期インフレ累積上限
if stone_used + c.강화석 > policy["강화석_분기_누적상한"]:
warns.append(f"[B] {c.name}: 四半期累計 {stone_used + c.강화석} > "
f"{policy['강화석_분기_누적상한']} (インフレ上限)")
# C: 直前N週の同一骨格の反復
if c.template_id in recent_weeks[-2:]:
warns.append(f"[C] {c.name}: {c.template_id} の骨格が直前2週にある (反復)")
# D: カレンダースロットの衝突 (同じ週に別の大型イベント)
if quarter_ledger.slot_taken(c.week):
warns.append(f"[D] {c.name}: W{c.week} スロットにすでに大型イベントを配置済み")
return warns
先ほどのワークド・トランスクリプトの5候補をこのコードに通すと、こう出ます。
[PASS] 氾濫の堤: 強化石 25 ≤ 30, 四半期累計 65+25=90 ≤ 90 (境界到達)
[WARN] [C] 猛暑ボス戦: coop_raid の骨格が直前2週(W-3)にある (反復)
[WARN] [B] 海流レース: 四半期累計 65+28=93 > 90 (インフレ上限超過)
[PASS] 灯籠流し: 強化石 20 ≤ 30, 四半期累計 65+20=85 ≤ 90
[PASS] 錦鯉収集: 強化石 22 ≤ 30, 四半期累計 65+22=87 ≤ 90
ここで興味深いのは海流レースです。AIは目的の衝突を理由に[あいまい]を付けましたが、lintはまったく別の理由 — 四半期インフレ累積上限の超過 — で引っかけました。強化石28を足すと四半期累計が93になり、ポリシー上の90を超えます。AIが見落とした算数を、コードが捕まえたのです。逆に猛暑ボス戦は、AIの[反復注意]とlintの[C]が同じものを指しました。人・AI・コードの3者が、それぞれ別の網で濾しているわけです。
この30行のおかげで、「今回の報酬、ちょっと強すぎないか?」がもう勘と勘の対決で終わりません。コードが[B] 四半期累計 93 > 90と出力すれば、議論の余地はありません。報酬を下げるか、候補を替えればいいだけです。
抽象的に「運営チームがチェックする」とだけ書いたのでは、このゲートが実際に何を濾しているのか分かりません。lintを通過した後、人が何を殺し、何を生かすのかを一度最後まで追いかけます。
[4段目 運営チェック — 判定]
運営プランナーは候補5件をこう処理しました。
- 猛暑ボス戦 → 棄却。 lintの[C]とAIの[反復注意]が同じ箇所を指しました。3週間で同じ協力レイドの骨格をまた回せば、「また累積貢献か」という疲労が来ます。次の四半期スロットへ繰り越しをメモ。
- 海流レース → 棄却。 lint [B]のインフレ上限超過。報酬を25に下げれば通りますが、AIの[あいまい]が突いた目的の衝突(活性化+競争)の方が、より根本的な問題でした。協力イベントに個人ランキングを混ぜると、ライトユーザーは「結局はガチ勢のお祭りだ」と感じます。報酬だけ削って生かすのではなく、丸ごと保留。
- 氾濫の堤 → 採用候補の第1位。 ただし、lintは
四半期累計 90 境界到達をPASSにしましたが、境界だという点が気にかかりました。このイベントを使うと、今四半期の強化石の余裕が0になります。6月最終週のシーズン締めくくりのプッシュに、報酬の余力がなくなります。- 灯籠流し / 錦鯉収集 → 存続。 どちらも報酬が軽く(20・22)、四半期の余裕を残します。
ここで、lintを通過した氾濫の堤を人が第1位の座から揺さぶったことが、このゲートの核心です。コードは90 ≤ 90をPASSにしました。ポリシー上は違反ではありません。しかし運営プランナーは四半期全体の報酬のリズムを見ました。lintは1つのイベントの合法性を見ますが、人は四半期の終わりのシーズン締めくくりまで見ます。だから再依頼を回します。
氾濫の堤の報酬を強化石 25 → 18 に下げたバリエーションを作り直せ。
理由: 6月最終週のシーズン締めくくりのプッシュに、強化石の余裕12を残す必要がある。
報酬の魅力が落ちるぶん、強化石の代わりに限定コスチューム・称号で
体感価値を補強する方向でreward_tiersを再構成しろ。
AIは強化石を18に下げ、限定コスチュームを2種に増やした(経済への影響が0の永久所有報酬)候補を出し直しました。lintを回し直すと四半期累計 65+18=83 ≤ 90で、シーズン締めくくりに余裕7が残りました。入力 → 候補量産 → lint → チェック → 棄却 → 再依頼という1サイクルが、ここで閉じます。
この一周が、本書全体のShowの基準です。ツールが何を吐き、何が引っかかり、人が何を殺すのかを一度でも最後まで見なければ、「AIでイベントを量産した」という文は空虚です。
自動廃棄型のlintを付けなかった理由も、このサイクルにあります。もしlintが[B]違反を自動で捨てていたら、運営チームは海流レースの本当の問題(目的の衝突)を学ぶ機会を失っていたでしょうし、氾濫の堤のように合法だが四半期のリズム上は危うい候補を揺さぶる場も消えていたはずです。疑わしい候補は機械が挙げ、採用と棄却は人が決めます。
イベントが週〜月のリズムなら、シーズンは四半期のリズムです。回し方は同じです。シーズンも検証済みの要素をスロットに分離しておけば、四半期ごとにテーマだけを差し替えられます。
| シーズンスロット | バリエーション(AI・人) | 固定(ポリシー・lint) |
|---|---|---|
| シーズンテーマ | 夏・冬・新年(自由) | — |
| シーズンパス報酬トラック | 段階別の報酬項目 | 段階数・完了難易度・報酬上限 |
| シーズンPvPランキング | ランキング報酬項目 | 報酬インフレ上限 |
| メタシャッフル | 新規キャラクター・バランス | 変更幅のガードレール(§8.1) |
シーズンパスで人がポリシーとして固定する核心の数値は完了率目標です。アクティブユーザーの70%程度が最終段階に到達するように難易度を取る、というのが業界でよく引用される基準です(著者の推定 — ゲームごとに異なるため、絶対値ではなく方向として読むのが正しいです:30%未満なら挫折、90%超なら挑戦感の不在)。この目標がスロットに入力されていれば、シーズンパスのバリエーションをAIが提案するときにも、「予想完了率」をあわせて算出するよう強制できます。
四半期カレンダーがひと目で見えてこそ、イベントとシーズンは衝突しません。運営チームの共用卓上カレンダーに近いものです。誰が見ても同じ絵を見るからこそ、衝突が減ります。
この1枚の図が、§15.2.5の判断を視覚で説明します。色が骨格の種類です。同じ色が2〜3週以内に2回現れたら、§15.2.4のlint [C]が鳴きます。 そして下のインフレゲージが赤い線(上限90)に触れる寸前なので、6月のシーズン締めくくり(W6)に使える余裕は7がかろうじて残っています — 氾濫の堤の報酬を18に下げて確保した、あの7です。
都市NPC(§6.2)やHUD(§14.1)と、ライブオプスが決定的に違う点が1つあります。告知は取り消せません。 NPCのトーンが合わなければビルド前に廃棄すれば済みますし、ユーザーはそのNPCが存在したことすら知りません。しかしユーザーに告知されたイベントは、報酬・期間・ルールがコミュニティに残ります。開始後の「イベント報酬が強すぎたので回収します」は、不可逆のコストを伴います。
flowchart LR
A["テンプレートのバリエーション候補"] -->|可逆| B["lintチェック"]
B -->|可逆| C["運営チェック・棄却・再依頼"]
C -->|可逆| D["ビルドチェック
(リリース保留可)"]
D ==>|不可逆ゲート| E["イベント告知・開始"]
E -.->|回復コスト大| F["後半の微調整のみ可能
(報酬回収・期間変更は信頼コスト)"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class A data;
class B code;
class C,D human;
class F fail;
本書全体の原則(§5.4.5のボイス収録、§8.1のライブビルド、第12部の最終レンダリングと同じメッセージ)は、ライブオプスでも同じです。すべてのチェック — 報酬範囲、インフレ上限、日程の衝突、トーン — は、告知前の可逆段階で終えなければなりません。§15.2.3\~5の量産・lint・チェック・再依頼のサイクル全体が、この不可逆ゲートの左側で回る理由です。ゲートを越えた後にできるのは§15.2.8の後半の微調整くらいで、それすらユーザーの信頼を少しずつ削っていきます。
告知後もKPIは見ます。ただし告知前のチェックと違い、ここでできるのは後半の微調整だけです。自動で計測されるシグナルと、人の処方を分けます。
| シグナル(自動計測) | 処方(人が決定) |
|---|---|
| 参加率50%未満 | 後半の報酬を小幅に強化、または期間+2日(告知の信頼範囲内) |
| 参加率95%以上 | 易しすぎ — 次サイクルの難易度をメモ、現行イベントは維持 |
| 強化石価格が事後30日で-10%超 | sinkの強化(限定ショップ)、次四半期のインフレ上限を引き下げ |
| イベント後の平日プレイ時間の減少 | イベント依存のシグナル — 平日コンテンツの魅力を補強、イベント頻度を調整 |
最後の行(平日プレイ時間の減少)が、いちばん見落とされがちなシグナルです。イベント期間のDAU(Daily Active Users、日次アクティブユーザー)だけを見ていると、イベントはいつも成功に見えます。しかしイベントが終わった後の平日にユーザーが戻ってこないなら、イベントが普段のゲームの魅力を吸い取っているということです。だから§15.2.2のテンプレートのpost_event_kpiに、「イベント後の平日プレイ時間」を最初からスロットとして入力しておきます。計測しなければ、処方はできません。
イベントの章には、「協力イベントを回したら継続率(リテンション)が30%から50%に上がった」のような表を入れたい誘惑が強く働きます。そうした数字は、検証されていなければ本の信頼を削ります。本章が言えるのは3つだけです。
第一に、方向は業界観察として語れます。 ログイン報酬を強化するイベントは短期のアクティブユーザー数を押し上げ、協力イベントはコミュニティの結束を高め、限定パッケージはイベント期間の売上を押し上げる — これは運営型ゲームを観察してきた業界の通念です。ただしどれくらいはゲーム・ユーザー構成によってばらつきが大きく、他社の数値をそのまま持ち込むのは危険です。
第二に、著者の推定は推定だと書きます。 「シーズンパス完了率目標70%」「イベント期間10日超で疲労蓄積」「イベント量産が半日→1時間」は、著者の経験に基づく推定であり、未検証の仮説です。絶対値を覚えるのではなく、構造(テンプレート+lintが白紙からの企画を置き換える)として読んでいただければ十分です。
第三に、計測できるものだけをKPIとして約束します。 継続率のような結果指標はイベント1本では左右されないので、因果を断定しません。代わりに、このワークフローが実際に計測可能にするのは次のものです — lintのWARN件数(報酬違反が0になるまで)、四半期インフレ累積(上限比)、同一骨格の反復間隔(週)、イベント別の参加率と事後の強化石価格変動。この4つは、会議で「感覚」ではなく数字で語れます。
| パターン | なぜ失敗するか | 処方 |
|---|---|---|
| 毎週白紙からイベントを企画 | 遅く、結果がぶれる | 検証済みの骨格をテンプレートyamlとして入力する(§15.2.2) |
| 「AIさん、夏イベントを作って」と丸ごと委任 | ありふれたRPGの平均的イベントが出てくる | 骨格を固定+スロットだけバリエーション(§15.2.3) |
| 報酬の量をAIが自由に提案 | インフレがその場で始まる | reward_policyをlintが強制(§15.2.4) |
| 候補を目視だけでチェック | 四半期累積・反復間隔を毎回見落とす | event_lint.pyで自動検証(§15.2.4) |
| lint通過=採用へ直行 | 四半期のリズム・目的の衝突が見えない | 人のゲートは四半期全体を見る(§15.2.5) |
| 告知後に報酬回収を試みる | 不可逆の信頼コスト | すべてのチェックを告知前に(§15.2.7) |
| イベント期間のDAUだけ計測 | 平日の魅力の侵食が見えない | 事後の平日プレイ時間スロット(§15.2.8) |
5つ目がいちばん見落とされます。lintをPASSしたからとそのまま告知へ送ると、氾濫の堤のように合法だが四半期の終わりに報酬の余力を0にする候補を揺さぶる場が消えます。コードは1つのイベントの合法性を、人は四半期全体のリズムを見ます。
一人ならこれだけ:lintのコードはなくても構いません。自分のゲーム(または好きな運営型ゲーム)でよく見かけるイベント骨格を1つ選び、§15.2.2の形式のテンプレートyamlを手で書いてみましょう(
core_loop・slots・reward_policyの3つの欄が核心です)。そして§15.2.3のプロンプトを貼ってバリエーション候補を5件出してみたあと、そのうち「報酬が強すぎる」と感じる1件を選んで、「これは今月の報酬余力を超える、下げてもう一度」と反論してみましょう。採用と棄却がどんな判断の束なのか、体で入ってきます。
チームなら、次の一歩から始めましょう。よく回すイベント骨格3〜4個をテンプレートyamlとして入力し、event_lint.pyの3行(報酬上限・四半期インフレ累積・反復間隔)からコードにします。テンプレートとこの3行があるだけでも、「毎週白紙から企画」と「報酬を勘で決める」という2つのよくある失敗を先に防げます。このワークフローは、§15.1.5の進歩的適用の骨格3要素 — イベントテンプレート・シーズンルールライブラリー、AIイベント候補生成器、事後の自動計測 — の最初の実務実装です。
主な読者:運営(ライブオプス)のユーザー対応を担うプランナー・ディレクター(中規模(10〜50人)チーム) 1人/趣味の読者向け縮小バージョン:§15.3.7「一人ならこれだけ」
まず正直に申し上げておきます。著者は、リリース後の運営(ライブオプス)を1〜2年単位で直接担った経験が長くありません。本章のかなりの部分は、24年のキャリアの上に積み重なった業界観察と隣接経験です。そのため本章は「運営はこうやるべきだ」と断定しません。代わりに、リリース前のコンテンツ量産で検証した入力 → AI → 検証 → 人の決定のサイクルを、ユーザーフィードバックという入力にそのまま当てはめると何が出てくるのかを、一度最後まで回してみます。ツールの骨格は§6.2のcity_hunting_generatorと同じで、入力だけが「都市メタデータ」から「ユーザーフィードバック100件」に変わります。
運営初週の風景は、だいたいどこも似ています。フォーラム・Discord・CSチケット・ストアレビューが1日に数百〜数千件ずつ積み上がります。人がすべて読むのは不可能で、読まなければ同じバグ報告が50件ずつ埋もれていきます。本章ではその山をLLMがトピックに束ね、感情でスコアリングするようにした上で、人は「では今週、何を直すのか」という優先順位の決定だけに入る方法を扱います。
フィードバックを4チャネル(ゲーム内アンケート・フォーラム/Discord・ストアレビュー・CSチケット)に分け、4類型(バグ・要望・不満・称賛)に分類する表は、どの運営の教科書にもあります。どれも正しい話です。問題は、その表を覚えても「今日入ってきた412件をどう処理するか」への答えが出ないことです。フィードバックを人が読んで分類する対象と見なす限り、フィードバックの量は常に運営チームの人数に勝ちます。
視点を変えます。フィードバック1件は構造化された入力です。{出典, 原文, トピック, 感情, 深刻度}という5つのスロットを持つレコードです。こう見ると、仕事の本質が変わります。「すべて読む」ではなく「トピックに束ねて優先順位を付ける」です。そしてトピックのクラスタリングと感情のスコアリングは、人がやると退屈な上にやるたびに基準がぶれますが、機械は同じ物差しを100件に同じように当てます。まさにLLMが人より得意な種類の仕事です。§6.2で都市30個を量産したあの分担(ルールブック=決定論、本文=AI、レビュー=人)が、ここでもそのまま成立します。違う点はただ一つ、最後に人がやる仕事が「本文のレビュー」ではなく「優先順位の決定」だということだけです。
フィードバック類型の分布について、一つ押さえておきます。自発的に書き込むユーザーは、満足したユーザーではなく不満のあるユーザー側に傾きます。満足した客は静かに去り、不満のある客がカウンターに戻ってくるのです。そのためフォーラム・レビューの感情分布は、実際のユーザー全体の満足度より否定側に偏る傾向があります(著者の観察 — 正確な偏りの幅はゲーム・チャネル・時期ごとに異なるため、絶対値ではなく方向として読むのが正しいです)。この偏りを頭に入れておいてこそ、クラスタリング結果で「否定60%」を見たときに、ゲームが傾きつつあると誤読せずに済みます。
実際に1サイクルを最後まで回してみます。入力はある1週間に4チャネルで集まったフィードバック100件で、出力はトピッククラスター・感情・優先順位です。入力プロンプトはそのままコピーして使うことができ、以下の出力は実際の分類セッションの形式を再構成したものです。
チャネルから集めてきた原文を、1行1レコードに正規化します。これは新しく書くのではなく、抽出と整理だけで済みます。
{"id": "fb_0001", "src": "discord", "text": "強化12段階で50回失敗しました。これ確率合ってるんですか?返金してください"}
{"id": "fb_0002", "src": "store_review","text": "グラフィックはきれいだけどラグがひどすぎてギルド戦のたびに落ちる"}
{"id": "fb_0003", "src": "cs_ticket", "text": "課金したのにダイヤが入っていません 注文番号添付"}
{"id": "fb_0004", "src": "forum", "text": "新職業のアーチャーはいつ出るんですか(泣) 事前登録のとき約束したじゃないですか"}
{"id": "fb_0005", "src": "discord", "text": "オープン初週なのに運営のコミュニケーションいいですね お知らせも早いし。これからもよろしく"}
{"id": "fb_0006", "src": "store_review","text": "特定ボス(黒狼)のダメージがありえない。フル装備なのにワンパン。バランスパッチ希望"}
{"id": "fb_0007", "src": "cs_ticket", "text": "チュートリアル5段階で進行できません ボタンが押せない (端末: Galaxy Aシリーズ)"}
// ... fb_0008 ~ fb_0100 (省略)
レコードは入力の段階ではトピック・感情・深刻度を空けておきます。その空欄を埋めるのがステップ2のLLMの仕事です。
添付したfeedback_100.jsonl(1週間分のフィードバック100件)をトピックに束ねて、感情も一緒にスコアリングして。
トピックはこのリストからだけ選んで(自由生成は禁止): 強化/確率、バランス、サーバー/性能、決済/返金、
新規コンテンツ要望、チュートリアル/オンボーディング、UI/操作、称賛/応援、その他。「その他」が8件を超えたら新トピック候補も提案して。
感情は否定・中立・肯定、深刻度はS1・S2・S3・S4で。
// (意図: S1は具体的・再現可能・機能ブロックのときだけ。単なる強い不満はS2)
確信が持てない件は「その他」に置いてidの末尾に★を付けて私に渡して、無理にはめ込まないで。
表は2つだけ — 表A(件別): id・トピック・感情・深刻度 / 表B(トピック別): トピック・件数・否定%・代表原文1件・S1含むかどうか。
このプロンプトで分類結果を左右するのは、トピックのリストでも感情の尺度でもなく、ラベルを固定し、形式を釘付けにし、逃げ道を開けておくという3つの統制装置です。トピックを自由生成に任せると、LLMは「全般的な不満」「ゲーム性の改善要望」のような、優先順位付けに使えないぼんやりしたラベルを作ります。許可ラベルのリスト(ルール1)、深刻度の定義(ルール3 — S1の狭い定義が要)、出力形式(表2つ)、そして「確信がなければ★を付けて人へ」(ルール5)。この4つが抜けると、表は優先順位のツールではなく、もう一つの読み物に成り下がります。このラベルリスト自体が、§15.1.5で述べたフィードバックL4レイヤーのカテゴリ体系です — ラベルが固定されていてこそ、四半期ごとに同じ軸で推移を比較できます。
[表A] 件別分類 (抜粋)
id | トピック | 感情 | 深刻度
fb_0001 | 強化/確率 | 否定 | S2
fb_0002 | サーバー/性能 | 否定 | S2
fb_0003 | 決済/返金 | 否定 | S1
fb_0004 | 新規コンテンツ要望 | 中立 | S3
fb_0005 | 称賛/応援 | 肯定 | S4
fb_0006 | バランス | 否定 | S1
fb_0007 | チュートリアル/オンボーディング | 否定 | S1
fb_0042★ | その他 | 中立 | S3
...
[表B] トピック別集計 (全100件)
トピック | 件数 | 否定% | 代表原文 | S1含む
サーバー/性能 | 24 | 92% | "ギルド戦のたびに落ちる" | はい(一部再現)
強化/確率 | 21 | 95% | "12段階で50回失敗しました" | いいえ
バランス | 14 | 86% | "黒狼 フル装備なのにワンパン" | はい
新規コンテンツ要望 | 12 | 8% | "アーチャーはいつ出るんですか" | いいえ
チュートリアル/オンボーディング | 9 | 78% | "5段階のボタンが押せない(Galaxy A)" | はい
決済/返金 | 8 | 100% | "ダイヤが入っていません" | はい
UI/操作 | 6 | 67% | "スキルボタンが小さくて誤タップ" | いいえ
称賛/応援 | 4 | 0% | "運営のコミュニケーションいいですね" | いいえ
その他 | 2 | - | (fb_0042★、fb_0067★ 人の判定待ち) | -
全体感情: 否定71 / 中立18 / 肯定11
「その他」2件(8件未満) → 新トピック提案は不要
確信不足の★表示2件: fb_0042、fb_0067 (人の判定を要請)
この出力で最も価値があるのは表ではなく、一番下の2行、「★表示2件」です。LLMが自分では束ねられないものを申告し、人に渡した場所です。§6.2でNPC「グレム」にAIが自ら曖昧マークを付けたのと同じ設計です。良いプロンプトは、AIが「これは確信が持てません」と言えるようにします。
この出力をそのまま受け取ってはいけません。実際に1か所、引っかかりました。
強化/確率トピックの21件が、すべてS2(不満)に分類されました。ところがそのうちfb_0001には「返金してください」が付いています。LLMはこれを「強い不満(S2)」としか見ませんでした。ここで人が介入します。強化確率への不満は — データ上、確率が仕様どおりに動いている限り — S1事故ではありません。仕様どおりに回っている確率への不満はデザイン・体感の問題であって、バグではないからです。LLMのS2判定は正しいのです。ただし「返金要求」というシグナルは、決済トピックにcross-linkしてCSが別途見る必要があります。LLMはトピックを単一ラベルでしか付けておらず、1件が2つのトピックにまたがるケースを見逃しました。
そこで再依頼します。
規則追加: 1件が2つのトピックにまたがる場合(例: 強化の不満 + 返金要求)は、主トピックのほかに
「cross」欄に補助トピックを書くこと。表Aにcross欄を追加して再出力すること。
ただし、強化確率の不満自体は、データ上確率が仕様どおりならS1ではなくS2のまま維持すること。
この1往復で終わりです。LLMはfb_0001にトピック=強化/確率, cross=決済/返金, 深刻度=S2と答え直し、★表示の2件は人が直接読んで、fb_0042をUI/操作に、fb_0067をチュートリアル/オンボーディングに再配置しました。100件を人が最初から読んで分類すれば半日、LLMドラフト + 人のレビュー + 1往復なら1時間以内です(著者の推定、未検証の仮説 — 正確な節約量はフィードバックの件数・チャネル数によって変わるため、絶対時間よりも「最初から手作業で」と「ドラフト+レビュー」の構造の違いとして読むのが正しいです)。
ここで決定的な線を引きます。上の表Bは「どのトピックが何件、どれだけ否定的か」までしか語りません。「では今週、何を先に直すのか」はLLMには出せません。それはコスト・スケジュール・ゲームのビジョンが絡む決定であり、その決定の責任はディレクターにあります。
同じ表を前にして、2つの運営チームが正反対の決定を下すこともありえます。件数だけ見ればサーバー/性能(24件)と強化/確率(21件)が1位・2位です。ところが優先順位は件数の順序とは違う方向に進みます。理由は深刻度と可逆性です。
flowchart TB
A["フィードバック100件
(4チャネル正規化jsonl)"] --> B["LLMクラスタリング
トピック・感情・深刻度のスコアリング"]
B --> C{"人によるレビュー
★件・cross・誤分類"}
C -->|再依頼| B
C -->|確定| D["トピック別集計表
件数・否定%・S1含む"]
D --> E["優先順位の決定
(ディレクター — LLM不可)"]
E --> F1["S1事故: 即時ホットフィックス
決済・チュートリアルのブロック"]
E --> F2["S2不満: データ確認後
デザイン判断"]
E --> F3["S3要望: 四半期バックログ
voiceスロットに固定化"]
F1 --> G["返信サイクル
(§15.3.4)"]
F2 --> G
F3 --> G
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class A,D data;
class B ai;
class C,E human;
この流れで人の手が触れる場所は2か所だけです。真ん中のレビューゲート(★・cross・誤分類の判定)と、一番下の優先順位の決定です。その間の退屈な100件の分類はLLMが回します。そして優先順位決定の実際のロジックは、件数ではなく次の3つの軸です。
| トピック | 件数 | 優先順位の判断(ディレクターの持ち場) |
|---|---|---|
| 決済/返金(S1) | 8 | 1位。件数は少ないが機能ブロック + 不可逆(金銭)。24hホットフィックス |
| チュートリアル/オンボーディング(S1) | 9 | 2位。新規ユーザーの離脱に直結。特定端末で再現 → パッチ |
| サーバー/性能 | 24 | 3位。最多件数だがインフラ作業 = スケジュールが長い。ホットフィックス不可、翌週 |
| 強化/確率(S2) | 21 | 維持。データ上、仕様どおりならバグではない。デザイン決定として別途検討 |
| 新規コンテンツ要望 | 12 | バックログ。否定8%(=肯定的な期待)。四半期voiceスロットに固定化 |
件数1位のサーバー/性能が優先順位3位に下がった理由はホットフィックスでは直せないインフラ作業だからで、件数6位の決済/返金が1位に上がった理由は金銭が絡む不可逆の事故だからです。この並べ替えはLLMにはできません。LLMは「決済8件、否定100%」という事実までしか出せません。それが最優先だという決定は、コスト・法的リスク・ゲームのビジョンを知る人の役割です。これが§15.1.5で述べた「AIが分類・候補を作り、人は採択とビジョンの決定に集中する」のフィードバック分野における実際の姿です。
優先順位が決まったら、ユーザーに返信します。運営(ライブオプス)では、返信の不在こそが信頼を最も大きく損なう場所です。答えることがなくても「検討中です」のほうが無応答よりましです。返信のドラフトも、LLMがトピック別に出せます。
[返信ドラフト — LLM出力、トピック別]
- 決済/返金(S1):「ダイヤ未付与の件、確認いたしました。注文番号を基準に24時間以内に遡及付与し、個別にご連絡いたします。」
- サーバー/性能:「ギルド戦時に発生する強制終了を再現確認中です。翌週のメンテナンスで優先対応する予定であり、進捗はお知らせでご案内いたします。」
- 強化/確率:「強化確率は仕様の表記どおりに適用されていることをデータで確認しました。ただし、体感難易度に関するご意見は別途検討中です。」
- 新規コンテンツ要望(アーチャー):「新職業はロードマップにあり、日程が確定し次第、最優先でお知らせいたします。」
ここで§6.2と決定的に違う点が1つあります。返信の送信は不可逆の段階です。都市のNPCは破棄して作り直せば済みますが、ユーザーが一度見たお知らせ・返信のテキストは取り消せません。「24時間以内に付与します」と自動送信したのに実際は3日かかったら、その約束はコミュニティに不可逆の痕跡として残ります。だからこそ§15.1.4の不可逆段階の原則が、フィードバック分野では他の分野よりさらに重く働きます。自動返信のドラフトはLLMが作るとしても、CSのレビューゲートを通過する前には一文字も自動送信しません。レビュアーは、日程の約束(24h・翌週)が実際の作業日程と合っているか、デリケートな事例(法的紛争・返金紛争)が自動送信プールに混ざっていないかだけを見ます。lintでは捕まえられない判断を、人が引き受ける場所です。
| 段階 | 可逆性 | 担当 |
|---|---|---|
| フィードバックのクラスタリング・感情スコアリング | 可逆(再実行自由) | LLM |
| トピックのレビュー・優先順位の決定 | 可逆(確定前) | 人(ディレクター) |
| 返信ドラフトの生成 | 可逆(破棄・書き直し) | LLM |
| 返信の送信・お知らせの掲載 | 不可逆(ユーザーの認知) | 人(CSレビュー後) |
同じフィードバックが四半期ごとに違う決定で揺れないようにするには、クラスタリングの結果を四半期振り返りの固定入力スロットとして固定化する必要があります。思いつきの「最近、強化への不満が多いみたいだ」ではなく、四半期ごとに同じラベル軸で集計された表が振り返りのテーブルの中に入ります。§15.3.2でラベルの自由生成を禁止し、許可リストに固定した理由が、ここで回収されます。
2026 Q2 ユーザーvoice(LLM自動集計、四半期累積)
チャネル4種 累積約5,000件をクラスタリング (件数は四半期の実集計 — 加工なし) 否定上位トピック: 強化/確率 > サーバー/性能 > バランス > 決済/返金 要望上位トピック: 新職業 > 新狩り場 > ギルドシステム > UI改善 四半期感情推移: Q1 否定68% → Q2 否定71% (小幅悪化 — 強化トピックが牽引)
この表が四半期の決定の入力になります。決定そのものはディレクターの役割で、入力はユーザーの役割です。四半期の推移(「Q1 68% → Q2 71%」)は方向としてだけ読みます。単一四半期の絶対値ではなく、同じラベル軸での変化の方向がシグナルです。否定%が上がったなら「どのトピックが引き上げたのか」をたどり、次の四半期の優先順位につなげます。この四半期レポートのドラフト自体もLLMが自然言語で出し、人は決定コメントだけを付けます — §15.1.5で述べた四半期レポート自動ドラフトの実際の場所です。
運営(ライブオプス)の章では、「フィードバックサイクルを導入したらNPSが20から45に上がった」のような表を入れたい誘惑が強くなります。著者はその因果を測定したことがないので、書きません。本書の原則は、次の3つのいずれかです。
第一に、実集計の件数はそのまま書きます。§15.3.2のトピック別件数(サーバー24・強化21・決済8)と§15.3.5の四半期累積は、分類結果を1件ずつ数えた値であって、見栄えのために合わせておいた比率ではありません。
第二に、推定は推定だと書きます。「100件の分類が半日→1時間」(§15.3.2)、「フォーラムの感情は否定側に偏る」(§15.3.1)は、著者の経験・観察に基づく推定であり、未検証の仮説です。絶対値を覚えるのではなく、方向(フィードバックの量は常に人数に勝つ、自発的な書き込みは不満側に傾く)として読めば十分です。
第三に、測定可能なものだけを指標として約束します。フィードバックサイクルで実際に測定可能なのは、結果の満足度(NPS)ではなくプロセス指標です — 未分類フィードバックの残量(目標0)、S1事故の発見→ホットフィックスのリードタイム、返信の応答時間、「その他」トピックの比率(許可ラベルが現実を捉えきれないと「その他」が膨れ上がります)。この4つは、会議で「感覚」ではなく数字で語れます。
一人ならこれだけ:CSシステムもデータセットも必要ありません。自分のゲーム(または好きなゲーム)のストアレビュー・コミュニティの書き込みを手作業で20〜30件だけコピーしてjsonlにし(
{"id":..., "src":..., "text":...})、§15.3.2のプロンプトをそのまま貼り付けて一度回してみましょう。出てきた表Bで「件数1位のトピック」と「あなたが先に直したいトピック」が異なる1件を探し、なぜ違うのかを1行で書いてみると — 優先順位がなぜLLMの仕事ではなく人の仕事なのかが、体で入ってきます。
チームなら、次の一歩から始めましょう。4チャネルのフィードバックを1行1レコードのjsonlに集める抽出スクリプトと、§15.3.2の許可トピックラベルのリストを先に固定します。ラベルが固定されていてこそ、LLMの分類でも人の分類でも同じ軸で測れ、四半期の推移を比較できます。自動返信はその次です — 返信は不可逆なので、CSのレビューゲートなしには絶対に自動送信につなぎません。
木曜日の午後4時。戦闘TFの会議が終わり、7人がそれぞれの席へ散っていきました。ホワイトボードには、グローバルクールタイム(GCD)を0.8秒から0.5秒に下げるかどうかをめぐる議論の跡が残っています。バランス担当のシニアは「私のシミュレーションでは0.5が正しい」と言い、コードリードは「0.5ではサーバーティックが追いつかない」と言いました。UIデザイナーは「どちらが正しいかは分からないが、クールタイムゲージの幅が狭くなりすぎる」と言いました。
3人とも正しいことを言っています。そして3人がそれぞれ自分の分野のドキュメントに自分の結論を書き始めると、来週にはこの3つのドキュメントが互いに衝突します。バランス側のシートには0.5、コード側の仕様書には0.8、UIガイドには0.6と書かれている状態。誰が見ても、どれが正本なのか分かりません。
戦闘TFの存在理由は、まさにこの衝突を1つの場で吸収することです。そして、その吸収の成果物 — ただ1つの決定 — だけが正本ドキュメントに上がるべきです。残りの議論の残骸は、隔離されたワークスペースの中で終わらせなければなりません。本章では、その隔離と吸収のメカニズムを扱います。
戦闘システムの大改修は、1つの職種では終わりません。GCDを1つ触るだけで、バランス(数値)、コード(サーバーティック)、UI(ゲージ表現)、アニメーション(モーションの長さ)、サウンド(打撃感)が同時に揺らぎます。こうした案件を分野ごとに別々に回すと、決定が2〜4週間ずつ延び、決定が出ても分野間で食い違います。
TF(タスクフォース)は、この食い違いを防ぐために、複数の職種を1つのワークスペースに一時的に集める単位です。核心は「一時的」と「隔離」です。会社の正本ドキュメント体系の中にTFの議論をそのまま流し込むと、未検証の議論・却下された案・実験中の数値が正本を汚染します。そこで私たちは、SVNの中に95_という番号で始まる隔離ワークスペースを作ります。
95_BattleTF。95番台の番号は、短期TFワークスペースを意味する取り決めです。通常の正本docsは10番台・20番台の番号を使い、90番台は「一時的・隔離・終了予定」のシグナルです。フォルダ番号を見るだけで、「ここは正本ではない、ここで見た数値を引用するな」が即座に伝わります。
隔離のルールは単純です。
95_BattleTFの中だけで生きる。TF_결정사항_요약.md(=TF決定事項要約.md)ただ1つだけを正本docsへ昇格する。95_BattleTF/archive/へ下ろし、保管のみとする。恒久部署として固まったTFが危険な理由はここにあります。隔離が解けると、TFワークスペースの未検証数値が正本のように引用され始め、四半期ごとに同じ決定が別の場で再びひっくり返ります。
戦闘TFの1サイクルは、隔離されたスペースを開き、その中で議論・実験・決定を積み上げ、終了時に決定だけを正本へ吸収させる構造です。
flowchart TD
A["戦闘案件の発生
(GCD 0.8→0.5?)"] --> B["95_BattleTF 隔離スペース開設
(SVN 95_番台)"]
B --> C["隔離内部の成果物を蓄積
議事録・実験シート・却下案・生メモ"]
C --> D{"決定に到達?"}
D -->|まだ| C
D -->|確定| E["TF_결정사항_요약.md 更新
(隔離スペース内部)"]
E --> F{"TF終了?"}
F -->|存続| C
F -->|終了| G["TF_결정사항_요약.md
1件だけ正本docsへ昇格"]
G --> H["残り全部
95_BattleTF/archive/ へ降格"]
G --> I["アートチームへはhtmlのみ共有
(md原本は非共有)"]
H --> J["隔離スペース閉鎖"]
style B fill:#fff3cd,stroke:#d39e00
style G fill:#d4edda,stroke:#28a745
style H fill:#f8d7da,stroke:#dc3545
左上から案件が入り、黄色の隔離スペースの中ですべてのノイズが処理され、緑色の1マス — 決定要約 — だけが正本へ抜けていきます。赤色は降格です。この図1枚が、95番台ワークスペース運営のすべてです。
TF終了の時点で最も手間がかかる仕事は、四半期分の議事録・実験シートから「正本に上げる決定だけ」を選り分けることです。議論は長く、却下された案と確定した案が混ざっていて、同じ数値が会議のたびに少しずつ違う形で書かれています。これを人が手で整理すると、終了作業だけで丸1日かかります。
以下は、実際に回したプロンプトとClaudeの生の出力、そして私がそれをどう検証・拒否・再依頼したかの全過程です。要約せず、そのまま載せます。
以下の95_BattleTFの議事録6件から、正本に上げる確定決定だけを抜き出して
TF_결정사항_요약.md のドラフトを作って。TFはもうすぐ終了する。
確定したものだけ(却下・実験中・「次回また見よう」は除く)、各決定は
決定ID・テーマ・確定値・根拠(データ出典)・決定者・確定日の形式で。
同じテーマで会議ごとに値が変わっていたら、最終値だけ残して以前のものは捨てて。
確定かどうか曖昧なら捨てずに[要検討]として別に分け、根拠の出典がなければ[根拠未記載]と表示して。
議事録:
--- 議事録 2026-W14-mon ---
案件1: グローバルクールタイム 0.8→0.5の検討。バランスシミュレーション(sim_gcd_0407.xlsx)上、
0.5でDPS分散-12%。コードリード: 0.5はサーバーティック(33ms)の限界により保留を要請。
決定: 0.6で1次合意(暫定)。次回会議までにコード側で0.6の負荷テスト。
案件2: 回避i-frame 12f→10f。アニメーションディレクターのモーションが8fのため、10fだと視覚的断絶。
決定: 12f維持で確定。決定者 企画リード。
... (中略はなし、実際には6件の議事録の全文が入る) ...
--- 議事録 2026-W16-thu ---
案件1: グローバルクールタイム。0.6の負荷テスト通過(サーバーティック余裕18%)。
決定: 0.6確定。決定者 企画リード。根拠 sim_gcd_0407.xlsx + load_0415.log。
案件3: 討伐報酬コイン1.2倍。バランス側の提案、根拠なし。保留。
# TF_결정사항_요약.md (ドラフト)
## 確定決定
### D-GCD-01 | グローバルクールタイム
- 確定値: 0.6秒 (従来0.8秒)
- 根拠: sim_gcd_0407.xlsx (DPS分散への影響), load_0415.log (サーバーティック負荷18%の余裕)
- 決定者: 企画リード
- 確定日: 2026-W16-thu
- 備考: W14で0.5の提案があったが、サーバーティックの限界により0.6に収束。0.5案は廃棄。
### D-IFRAME-01 | 回避i-frame
- 確定値: 12フレーム維持
- 根拠: [根拠未記載] — アニメモーション8fで視覚的断絶という定性判断のみ記録あり
- 決定者: 企画リード
- 確定日: 2026-W14-mon
## [要検討]
### R-COIN-01 | 討伐報酬コイン1.2倍
- 状態: 保留 (バランス側の提案、根拠データなし)
- 判断: 根拠未提出のため確定不可。TF終了後、バランスチーム単独案件としての移管を勧告。
3つの項目を1つずつ見ていきました。
D-GCD-01はそのまま通過です。 W14の0.5/0.6の暫定案を捨て、W16の0.6確定だけを残したのは正確です。ルール3をきちんと守っています。根拠の2つの出典をまとめたのも正しい処理です。
D-IFRAME-01は拒否しました。 Claudeはこれを「確定」に分類しましたが、根拠が[根拠未記載]です。定性判断(視覚的断絶)だけで確定決定に上げると、正本を見た別の人が「なぜ12fなのか」の根拠を見つけられません。これはルール1・5の衝突ケースです — Claudeは決定者が確定したのだから「確定」と見なしましたが、私は「根拠のない確定は正本に上げられない」という私たちのdocsポリシーを適用しなければなりませんでした。このポリシーは議事録に書かれていないので、Claudeが知るはずもありません。
R-COIN-01は分類は正しいものの、処方が過剰でした。 「バランスチーム単独案件としての移管勧告」は、Claudeがでっち上げた手続きです。私たちの会社に、そんな移管トラックはありません。分類(要検討)は受け入れ、処方の文は捨てました。
検証を反映してもう一度作って。D-GCD-01はそのままにして、
D-IFRAME-01は根拠が定性判断だけなので、確定から外して[根拠補強後に昇格]へ移して。
R-COIN-01は分類は残しつつ、「移管勧告」のような処方の文は外して — うちの会社の手続きを君は知らないんだから、状態と事実だけ書いて。
あと、議事録にない手続き・トラック・チーム名をでっち上げず、分からなければ[企画リード判断要]として空けておいて。
// (意図: D-IFRAME-01の正本昇格条件 = アニメーションディレクターのモーションフレーム計測データの議事録添付)
# TF_결정사항_요약.md
## 確定決定 (正本昇格対象)
### D-GCD-01 | グローバルクールタイム
- 確定値: 0.6秒 (従来0.8秒)
- 根拠: sim_gcd_0407.xlsx, load_0415.log (サーバーティック負荷18%の余裕)
- 決定者: 企画リード / 確定日: 2026-W16-thu
- 備考: 0.5案はサーバーティックの限界により廃棄。
## [根拠補強後に昇格]
### D-IFRAME-01 | 回避i-frame 12f維持
- 定性判断(アニメモーション8fに対する視覚的断絶)のみ記録あり。
- 昇格条件: モーションフレーム計測データの議事録添付。
## [企画リード判断要]
### R-COIN-01 | 討伐報酬コイン1.2倍
- 状態: 保留。バランス側の提案、根拠データなし。確定値なし。
この確定版のうち、「確定決定」セクション — D-GCD-01の1件 — だけが正本docsへ昇格されました。残りの2セクションはarchive/へ下りました。四半期分の6件の議事録が、正本の1行に吸収されたわけです。
ここで、AIがやった仕事とできなかった仕事が分かれます。AIは6件の議事録を横断して同じテーマの値の変化を追跡し、却下案を分離し、根拠の欠落を表示しました — 議事録6件を1行ずつ突き合わせるこの単純反復こそ、人の手では抜け落ちやすいところです。しかし、「根拠のない確定は正本に上げられない」というポリシーの適用、「移管トラックは存在しない」という会社の事実、「確定/保留」の最終判断は、すべて人がやりました。この流れからAIの段落を消すと、消えるのは抽出・整列の労働だけで、正本に何を上げるかの決定はどのみち人の手に残ります。
TFに入ってくる案件が、すべて内部で生まれるわけではありません。パブリッシャー・アート外注先・事業チームから、「戦闘関連でこれをやってほしい」という要請が入ってきます。これを無分別にTFの案件として受けると、TFは外部の陳情窓口になってしまいます。
そこで外部要請は、受け取った瞬間に3つの道筋に分類します。戦闘の決定が必要なものだけを95_BattleTFへ投入し、1つの分野で終わる仕事は担当者が単独で処理し、範囲外・根拠不足は理由を書いて返信・保留します。TFに入ってくるのは最初の道筋だけ — これが、TFの陳情窓口化を防ぐ第一防衛線です。分類そのものは人の判断ですが、入ってきた要請テキストを読んで「これは何分野にまたがるか」を1次タグ付けしておく程度なら、AIに先にざっと目を通させても構いません。
この三角分類(request-triangulate)の判定順序・ワークド・トラック別の後続処理は、次章16.2が専任で扱います。ここでは、「TFは最初の道筋だけを受ける」という入口ルールだけを押さえておきます。
TFの決定が正本へ昇格されたら、それを関連チームに共有します。ここで1つの非対称があります。アートチームにはMarkdownの原本(.md)を渡さず、レンダリングされたhtmlだけを渡します。
理由は単純です。アートチームは、決定の結果だけを知っていればよいからです。「クールタイムゲージは0.6秒基準で幅を取り直してほしい」 — この1行が、彼らに必要なすべてです。mdの原本には、決定IDの体系、atom参照、却下された0.5案の痕跡、根拠データのファイル名が入っています。これは企画とコードが共有する作業言語であって、アートが学習すべきものではありません。
mdをそのまま渡すと、アートチームは2つのコストを払うことになります。第一に、自分と無関係な表記体系を解釈するのに時間を使います。第二に、未検証・却下の情報を決定と誤解しかねません。htmlはこの2つを防ぎます — きれいにレンダリングされた決定結果だけが見え、内部表記はビルド過程でふるい落とされます。
原則の形に書くなら、作業言語(md)はその言語を使う職種の中だけを回り、その外へは成果物(html)だけが出ていく、です。TFワークスペースの隔離(95番台)と同じ哲学です。中で使う生のものは中に置き、外へは吸収された結果だけを送り出します。
隔離・吸収のメカニズムが回るには、その下に5つの運営原則が敷かれていなければなりません。1つでも欠けると、TFは議論の場へと崩れていきます。
[根拠未記載]を自動表示させたのも、この原則の延長です。5つの原則が束になって機能するとき、隔離された95番台のスペースは、議論の場ではなく決定の工場になります。
TF運営の中期以降に繰り返される落とし穴と処方をまとめます。
| 落とし穴 | 症状 | 処方 |
|---|---|---|
| 会議の場への変質 | 意見交換のみで決定なし | 毎会議に決定スロットN個を強制 |
| 権限の侵犯 | TFが他分野の決定に介入 | 決定権テーブルの明確化 |
| メンバーの負担過多 | TF5〜6個の重複参加で本業を侵食 | TF参加は合計で週8時間まで |
| 恒久化 | 解散なしに同じ会議を反復 | 四半期再評価 |
| 隔離の漏れ | 95番台の未検証数値が正本のように引用される | 正本昇格は決定要約1件のみ |
| 外部との断絶 | 決定を外部に未共有 | 正本昇格+htmlの共有 |
隔離の漏れが、最も静かで最も危険です。フォルダ番号の取り決めが崩れると、すべてが崩れます。
著者のプロジェクトA運営記録から、方向と比率だけを移します。以下の数値は絶対値ではなく、TF不在時と比べた運営時の変化の方向です — 絶対的な周期はチーム規模・ビルド周期によって異なります(著者環境基準の観察です)。
| 項目 | TF不在 | TF運営 | 方向 |
|---|---|---|---|
| 戦闘決定1件のサイクル | 分野別にばらばら、数週間 | 数日単位 | 短縮 |
| 決定後の分野間衝突 | 四半期に多数 | 四半期に少数 | 減少 |
| ゲームディレクターへのエスカレーション | 週に多数 | 週1〜2件 | 減少 |
| 分野間の情報共有 | 散発的 | 議事録・正本昇格で固定 | 体系化 |
最も大きく回収されるのは、ゲームディレクターの時間です。分野間の決定をTFが隔離スペースの中で吸収してしまうので、ディレクターの席まで上がってくる衝突が減ります。TFとは結局、「ディレクターがいちいち仲裁していた分野間の合意」を1つのワークスペースに引き下ろして処理する装置なのです。
ゲーム外への応用。 隔離されたワークスペースから決定だけを正本へ吸収するという原理は、ゲームと無関係なあらゆる部署横断プロジェクトにそのまま適用できます。たとえば、マーケティング・法務・営業が一緒に新しい利用規約の改定を議論するTFを思い浮かべてみてください。議事録・レビュー意見・却下された文言のドラフトは共有ドライブの一時フォルダ(
95_약관TF〔=95_規約TF〕のような隔離スペース)に置き、TFが終わったら최종_확정문구.docx〔=最終確定文言.docx〕の1件だけを社内の正本文書庫へ上げ、残りはアーカイブへ下ろします。こうしておけば、6か月後に「この条項はなぜこう決めたんだっけ」と問うとき、未確定のドラフトが正本のふりをして紛れ込む事故を防げます。
setup
- SVN(またはフォルダ)に95_BattleTF/の隔離スペースを作り、四半期分の議事録をその中に集めましょう。
- 95_BattleTF/archive/をあらかじめ作っておきましょう(降格対象の行き先です)。
prompt - 本章の「1回目のプロンプト」を、議事録の全文と一緒に貼り付けましょう。核心ルール:①確定決定のみ ②同じテーマは最終値のみ ③曖昧なら捨てずに分離表記 ④根拠がなければ明示 ⑤会社の手続き・チーム名をでっち上げないこと。
verify
- 出力の「確定」分類を1件ずつ見ましょう。根拠が定性判断だけの項目は、「確定」から引きずり下ろします(正本昇格ポリシーの適用です)。
- AIが作った処方の文(移管・トラック・勧告)に実在しない手続きが紛れていないか確認し、消しましょう。
- 「確定決定」セクションだけを正本docsへコピーし、残りはarchive/へ下ろします。
一人で作業する個人開発者にとっても、隔離・吸収はそのまま有効です。「TF」を「自分の頭の中の複数の役割」に置き換えればよいのです。
95_temp_결정/〔=95_temp_決定/〕のような一時フォルダを掘り、そこにシミュレーション・メモ・却下案を全部吐き出しましょう。결정요약.md〔=決定要約.md〕の1枚だけを本来の作業フォルダへ移し、一時フォルダは丸ごとarchive/へ下ろしましょう。隔離スペースがあれば、「この数値は確定なのか実験中なのか」をフォルダの位置だけで区別できます。一人であっても、未来の自分に同じ混乱を引き継がせない、いちばん安上がりな方法です。
火曜日の午前、メッセンジャーがほぼ同時に3回鳴りました。
アートリード:「戦闘エフェクトのカラー、今のトーンがくすみすぎているのですが、もっと華やかにしてもいいですか?」
QAリード:「ギルド出席報酬が2回付与されるケースがあります。再現動画を添付します。」
パブリッシャー担当:「東南アジアビルドへのイスラム文化圏ガイドラインの反映をお願いします。次の四半期審査の前までに。」
3通のメッセージの文字数は似たようなものでした。ところが、1つは30分で終わる仕事で、1つは今すぐコードリードを捕まえなければならない事故で、1つは四半期単位の計画に組み込むべき外部日程でした。同じ受信トレイに落ちてきたという理由で同じ重さで扱えば、30分の案件に半日を使い、肝心の事故は夕方まで放置されることになります。
プランナーのもとに届くリクエストは、職種の数と同じくらい性質がばらばらです。問題は、それらがすべて「一行のメッセージ」という同じ形で届くという点です。本章では、その一行を受け取った瞬間に3つのトラックへ振り分ける作業を扱います。トラックが分かれた瞬間に、何を今止め、何を後回しにするかが決まります。
プランナーはコードも、アートも、サウンドも自分の手では作りません。仕様を書き、意図を伝え、結果を検証するだけです。すべての成果物は他職種の手を経て生まれます。だからこそ、連携の質が企画成果物の質をそのまま決めます。
著者がディレクターを務めるプロジェクトA(モバイルファーストのMMORPG、中規模(10〜50人)チーム)で、プランナーが日常的に連携する職種を広げてみると、次のようになります。
7つの職種と、毎日から四半期単位までの頻度でかみ合っています。プランナーがデスクで過ごす時間の40〜60%は、この連携に費やされます。本業(設計)に使う時間は残りの半分ということです。だとすれば、連携の時間を減らすことは、そのまま本業の時間を増やすことです。そして連携の時間を食いつぶす最大の原因は、届いたリクエストを分類できず、見当違いのところにエネルギーを注いでしまうことにあります。
先ほどの3通のメッセージに戻りましょう。表面上はどれも「〜してください」です。しかしその中には、3つの異なる性格が隠れています。
この3つの性質を、著者はそれぞれ一語で呼んでいます。合意(align)、欠陥(defect)、日程(schedule)です。届いたリクエストをまずこの3つのどれかに押し込む作業、これをプロジェクトAではrequest-triangulateという名前のワークフローとして固定化してあります。三角測量(triangulate)という名前は、1つの点(リクエスト)を3つの基準点(職種の性格・緊急度・外部依存性)で囲んで位置を特定する、という意味で付けました。
分類の流れは次のとおりです。
flowchart TD
A[外部リクエスト1件到着] --> B{外部の締め切り/契約に
縛られているか?}
B -- はい --> S[Track-S: 日程 schedule]
B -- いいえ --> C{ユーザー影響のある
バグ/欠陥か?}
C -- はい --> D[Track-D: 欠陥 defect]
C -- いいえ --> E{好み・意図の
合意の問題か?}
E -- はい --> F[Track-A: 合意 align]
E -- いいえ --> G[保留ボックス:
追加情報を要請]
S --> S1[四半期ロードマップに編入
十分なリードタイムを確保]
D --> D1[優先度P0~P2を判定
コードリードに即時連結]
F --> F1[意図までを伝達
表現は当該職種に委任]
style S fill:#fde2c4,stroke:#c98a3a
style D fill:#f6c6c6,stroke:#c25151
style F fill:#c9e4d0,stroke:#4f9d6a
style G fill:#e0e0e0,stroke:#888
質問の順序が核心です。日程の依存性を最初に問う理由は、外部の締め切りがかかった仕事では、内部の判断よりリードタイムが優先されるからです。四半期審査まで3週間の案件を「あとで合意すればいい」と分類してしまうと、合意が終わった頃には締め切りが目前です。欠陥を2番目に置く理由は、ユーザーにすでに影響を与えている事柄は、好みの議論より常に先に来るからです。合意は最後です。急ぎではなく、外部に縛られず、ユーザーを害さない案件であってはじめて、「ゆっくり合意しよう」が成立します。
3つの質問がすべて「いいえ」なら、それは分類の失敗ではなく情報不足です。そのときはトラックを無理に決めず、保留ボックスに入れて問い返します。「これは次のビルドに必ず入れる必要がありますか、それとも検討だけしておけばよいですか?」のような一文が、たいていトラックを決めてくれます。
この分類を頭の中だけでやっていると、忙しい日には崩れます。そこで著者は、届いたリクエストの束をまるごとAIに投げて1次分類を受け取り、その結果を検証・修正する方式で運用しています。以下は、実際に回した1回分をほぼそのまま書き写したものです。要約はせず、AIが間違えた部分と著者が拒否した部分まで残します。
以下の外部リクエストを3つのトラックに分類してください — Track-S(日程: 外部の締め切り・契約・審査に縛られたもの)、
Track-D(欠陥: ユーザー影響バグ、P0~P2判定)、Track-A(合意: 好み・意図、合意で完結)。
外部依存性 → ユーザー影響 → 好み/意図の順に見て、最初に該当したトラックで確定し、
3つともあてはまらなければ保留にして、問い返す質問を1つください。
出力はリクエストごとに | トラック | 根拠(1行) | 次のアクション(1行) |。
[リクエスト一覧]
1. アートリード: 戦闘エフェクトのカラーがくすんでいる。もっと華やかにしてもよいか?
2. QAリード: ギルド出席報酬の重複付与バグ。再現動画あり。
3. パブリッシャー: 東南アジアビルドに文化圏ガイドラインを反映。次の四半期審査の前まで。
4. サウンドディレクター: ボスBGMのイントロが戦闘開始タイミングより0.5秒遅い。
5. 運営チーム: ユーザーから「ギルドチャットのフォントが小さすぎる」との問い合わせが多数。
| # | トラック | 根拠 | 次のアクション |
|---|------|------|-----------|
| 1 | Track-A | カラートーンは好み・意図の領域、外部/バグと無関係 | 意図(戦闘の緊張感)のみ伝達、彩度の決定はアートに委任 |
| 2 | Track-D | 報酬重複 = ユーザー資産への直接影響、再現可能 | P0判定、コードリードに即時連結、ホットフィックス検討 |
| 3 | Track-S | 四半期審査という外部締め切りに拘束 | 四半期ロードマップに編入、ガイドライン項目を分解しリードタイム算定 |
| 4 | Track-A | BGMタイミングは演出の好みの問題に見える | サウンドディレクターと合意 |
| 5 | Track-D | フォントの可読性はUI欠陥 | P2バグとして登録 |
4番と5番で、著者は手を止めました。
4番は拒否。 AIはBGMイントロの遅れを「演出の好み(Track-A)」と見ました。しかし「戦闘開始タイミングより0.5秒遅い」は好みではなく同期の欠陥です。音源そのものを変えるのではなく、BT(ビヘイビアツリー)上のトリガー時点とオーディオキューがずれている問題である可能性が高いのです。これは合意では終わらず、アニメーション・コード側のタイミングデータに手を入れる必要があります。→ Track-Dに訂正。 ただし、ユーザーの資産には影響しないためP2です。
5番は保留に格下げ。 AIは「フォントが小さい」を即座にUI欠陥(Track-D)と断定しました。しかし、これが欠陥なのか好みなのかは、メッセージだけでは判別できません。フォントがデザイン仕様どおりにレンダリングされているのに「小さく感じられる」のであれば合意(Track-A)に近く、仕様より小さく崩れて表示されているのであれば欠陥(Track-D)です。→ 保留。運営チームへ問い返し:「仕様上のフォントサイズに対して実際に小さく表示されているのでしょうか、それとも仕様自体を大きくしてほしいというご意見でしょうか?」
拒否した2件を反映して再度投げたプロンプトの追加指示は、短いものでした。
4番は「戦闘開始に対する0.5秒の遅延」を同期の欠陥として再分類すること(Track-D, P2)。
タイミングがBTトリガー/オーディオキューのどちらでずれたのかを確認する質問を1つ付け加えること。
5番は保留として処理し、「仕様に対する実際のレンダリング」かどうかを尋ねる質問を明記すること。
再出力は、4番をTrack-D / P2 / "BT戦闘開始ノードのオーディオキューのオフセットが0なのか、それともBGMクリップ自体に0.5秒の無音が含まれているのかを確認"に、5番を保留 / "仕様に対して小さくレンダリングされているのか vs 仕様引き上げの要望なのか、運営チームに再確認"に正して返してきました。この時点で分類が完結しました。
ここで、AIがやった仕事と人がやった仕事がはっきり分かれます。AIは5件を1次で素早く振り分け、空欄のない表を作ってくれました。人はその中から、トラックの境界が微妙な2件(好みに見えて同期の欠陥であるBGM、欠陥に見えて好みかもしれないフォント)を捕まえました。5つの欄を漏れなく埋める仕事と、そのうち2つの欄が間違って埋められていることに気づく仕事は別々の能力であり、このワークド・トランスクリプトはその2つを、それぞれ得意な側に任せたものです。
分類が終わると、各トラックはまったく別の後続作業に入ります。同じ表から始まっても、行き先が違います。
Track-A(合意)に分類されたリクエストは、「意図までを伝え、表現は委任する」という原則で処理します。アートのカラーのリクエストに著者が返した答えは、彩度の数値ではなく意図でした。「この戦闘はボスの第1フェーズなので、緊張感が核心です。華やかさより圧迫感を優先してもらえるとうれしいです。その範囲内で、彩度はアートの判断にお任せします。」プランナーが彩度の値を直接指定した瞬間、アートの自律性が削られ、成果物の責任の所在も曖昧になります。意図と表現の境界を守ることが、合意トラックのすべてです。
Track-D(欠陥)に分類されたリクエストは、優先度の判定とコードへの連結につながります。ギルド報酬の重複(P0)はその場でコードリードに渡し、BGMの同期(P2)はバックログに登録しつつ、原因推定の質問を添えました。欠陥トラックでのプランナーの仕事は「直すこと」ではなく、優先度を付けて正確なインプットを渡すことです。P0かP2かを分ける基準は「今、ユーザーの資産・進行に影響を与えているか」です。報酬の重複は資産に直結するためP0、BGMの0.5秒の遅れは不快ではあるものの進行を妨げないためP2です。
Track-S(日程)に分類されたリクエストは、四半期ロードマップへ入ります。パブリッシャーの文化圏ガイドラインは一行のリクエストですが、実際には複数の項目に分解されます — 宗教的シンボルの表現、色のタブー、テキストの方向性、キャラクターの服飾。これを受け取った瞬間に「検討します」と答え、四半期計画にまるごと載せることが核心です。外部の締め切りがかかった仕事は、小さく見えてもリードタイムが命であり、開始が遅れれば必ず事故になります。
この3つの分岐をひと目で比較すると、次のようになります。
分類が正確であれば、同じ受信トレイの5行が、3つの別々の処理ラインへきれいに散っていきます。分類を誤れば、欠陥が合意の会議に引きずり込まれて時間を食いつぶしたり、日程案件の開始が遅れて締め切り直前に爆発したりします。
リクエストが1〜2件ではなく、ひとかたまりになって押し寄せてくるときがあります。パブリッシャー審査を控えた数週間や、戦闘システムの全面改修のような局面です。そういうときは、作業そのものを95_BattleTFのような一時作業スペースへ隔離し、終わったら決定だけを正本に昇格させます。その隔離・吸収のメカニズムと、「アートチームにはhtmlだけを渡す(mdの学習0)」という運用は、前の章16.1ですべて扱いました。
分類(3-track)の観点から一行だけ付け加えると、こうなります。ひとかたまりに膨らんだ連携は、たいていTrack-S(日程)の案件が複数の職種にまたがって分解されていく局面であり、個別のトラック処理では受け止めきれなくなったとき、隔離作業スペースという一段上の器に移し替えるのです。つまり、3-track分類が入口だとすれば、TF隔離はその入口を通過した大きなかたまりを収める部屋です。
| 失敗パターン | 処方 |
|---|---|
| すべてのリクエストを同じ重さで処理する | 受け取った瞬間に3-track分類、外部依存性から質問する |
| 日程案件を合意に誤分類する | 判定順序の1番目に外部締め切りの質問を固定する |
| 好みに見える同期の欠陥を合意として処理する | 「タイミング/数値のずれ」はまず欠陥を疑う |
| 欠陥に見える好みを欠陥と断定する | 「仕様に対する実際のレンダリングか」を問い返して保留にする |
| 合意トラックでプランナーが表現まで決める | 意図までにとどめ、表現は職種に委任する |
| 日程案件の開始が遅れる | 四半期ロードマップに即時編入し、リードタイムを確保する |
この表の半分は分類段階のミスで、残りの半分は分類後の処理のミスです。分類が正確でも、トラックごとの手の動かし方を誤れば効果は消えます。(TF隔離・昇格・媒体まわりの落とし穴は、16.1の落とし穴の表を参照してください。)
ゲーム外への応用。 一行のリクエストが同じ受信トレイに届いたという理由で同じ重さで扱われてしまうという問題は、ゲームに限らず、あらゆるサービス企画者・PMの日常そのものです。届くリクエストを「合意(好み・方向性)・欠陥(ユーザー影響バグ)・日程(外部締め切り)」の3トラックに振り分ける分類は、ドメインを変えてもそのまま機能します。たとえばWebサービスのPMのメッセンジャーに同時に「ボタンの色をもう少し明るく(合意)」「決済の領収書が重複送信されている(欠陥)」「個人情報保護法改正への対応、締め切り3週間前(日程)」が届いたなら、外部締め切り→ユーザー影響→好みの順に最初に該当したトラックへ入れ、決済バグにただちに人を付け、法改正はまずリードタイムを確保すればよいのです。
Webチャットボット最小ルート(ターミナルなし) — 本章の核心はワークフローのスクリプトではなく、「一行のリクエストを合意・欠陥・日程の3トラックに振り分ける」という発想です。その発想は、CLI・hook・atomのインフラがなくても、Webチャットボット(ChatGPTまたはClaudeのWeb版)だけでそのまま再現できます。本筋は次の2ステップです。
1. その日に届いたリクエストを、形式にこだわらず1行ずつ集めておきましょう。メッセンジャー・メール・メモ、どこから拾ってきてもかまいません。
2. Webチャットボットの入力欄に下のプロンプトを貼り、その下に集めたリクエスト一覧を貼り付けます。これが、request-triangulateがやっていた1次分類を手で1回行うことに当たります。
以下のリクエストをTrack-A(合意)/Track-D(欠陥)/Track-S(日程)に分類してください。
外部締め切り → ユーザー影響バグ → 好み・意図の順に見て、最初に該当したトラックで確定し、
3つともあてはまらなければ保留にして、問い返す質問を1つください。出力は | トラック | 根拠1行 | 次のアクション1行 |。
[リクエスト一覧を貼り付け]
そのあとは、出力された表の2か所だけを人が検証すれば十分です — 「タイミング・数値のずれ」が合意に分類されていたら同期の欠陥を疑い、「〜が小さい/遅い」のような体感の不満が欠陥と断定されていたら、「仕様に対する実際のレンダリングか」を問い返して保留に下げます。スクリプトやワークフローは、この分類が手になじみ、毎日の束をさばくのが大変になってきたときに、はじめて導入すれば十分です。
setup. 届いてくる外部リクエストを1か所(チャンネルやドキュメント)に集めましょう。3つのトラックの定義を1行ずつ書いておきましょう — 合意(好み・意図)、欠陥(ユーザー影響バグ)、日程(外部締め切り)。
prompt. 集めたリクエストの束をAIに投げ、判定の順序を固定しましょう。
以下のリクエストをTrack-A(合意)/Track-D(欠陥)/Track-S(日程)に分類してください。
外部締め切り → ユーザー影響バグ → 好み・意図の順に見て、最初に該当したトラックで確定し、
3つともあてはまらなければ保留にして、問い返す質問を1つください。出力は | トラック | 根拠1行 | 次のアクション1行 |。
[リクエスト一覧を貼り付け]
verify. 出力された表の2か所を直接検証しましょう。(1)「タイミング・数値のずれ」が合意に分類されていたら、同期の欠陥ではないかと疑ってください。(2)「〜が小さい/遅い」のような体感の不満が欠陥と断定されていたら、「仕様に対する実際のレンダリングか」を問い返して保留に下げてください。境界の2件だけ人が押さえれば、残りは信頼してかまいません。
チームもTFもない一人開発者なら、トラックはそのままに、対象だけを変えましょう。ストアレビュー、Discordでの報告、ベータテスターのメモを1つのドキュメントに集めておき、週1回、上のプロンプトで束ごと分類しましょう。合意(好み)は「自分のビジョンと衝突しなければ受け入れる」、欠陥(バグ)はその週のうちに処理し、日程(ストア審査・イベントの締め切り)はリードタイムとあわせてカレンダーに入力しましょう。集中整備期間の隔離フォルダー運用は、16.1の一人ミニ版に従えばよいでしょう。
95_BattleTFの会議室。ギルド出席報酬を資源+5で確定したその日の午後、私は同じ一つの決定を三つの場所へ流しました。企画チームのチャンネルには仕様のmarkdownを、プログラムチームにはデータカラム1行を、アートチームには画面1枚のhtmlを。三つの場所からほぼ同時に返事が来ました。プログラムリードは「トリガーの時点はどこか」と尋ね、アートディレクターは「出席ボタンの位置は06_UIガイドと合っているか」と尋ね、アニメーターは何も言いませんでした。同じ決定だったのに、3人が見たものは全部違っていました。
本章は、その「違って見えること」を事故ではなく設計に変えた記録です。一つの決定を職種別に違う形でパッケージングすること — それがframingです。
ギルド出席報酬という一つの決定に張り付くオーディエンスは五つです。彼らは同じ文章を読んでも、自分の領域だけを選んで読み、残りは読み流します。読み流した場所で事故が起きます。
| オーディエンス | 集中して読むもの | 本能的に読み飛ばすもの |
|---|---|---|
| コードリード | データカラム・インターフェース・トリガーの時点 | 色味・ナラティブ・演出 |
| アートディレクター | 画面レイアウト・コンポーネント・スタイルガイド | データ整合性・トリガー |
| サウンドディレクター | 行動トリガー・雰囲気・長さ | データの詳細 |
| アニメーター | 動作・タイミング・状態遷移 | ビジュアルのトーン・数値 |
| QA | 受け入れ基準・リスク・エッジケースシナリオ | 実装方式の内部 |
問題は情報の量ではなく、露出のさせ方です。分厚い仕様書を1部、5人の机に同じように置いておくと、5人はそれぞれ違うページを開き、違うページを閉じます。framingはこの「開き方」を偶然に任せず、意図的に配置します。
以下は、同じ一つの決定が職種の境界を越えるたびにどんな形に着替えるのかを示すframingマトリクスです。
パッケージはオーディエンスごとに違っても、中心に敷かれた不変の事実(数値・時点・範囲)は、どのパッケージでも揺らいではいけません。framingの技術は「違うように見せること」ではなく、「違うように見せながら同じものを保存すること」です。
framingを毎回手でこね直すと負担が大きく、結局やらなくなります。そこで私は、決定を一つ投げて職種別パッケージの草案を一度に受け取る方式を使っています。以下は、ギルド出席報酬の決定をめぐって実際にやり取りしたトランスクリプトです。手を加えずそのまま載せます。
[決定事実 — 95_BattleTFで確定]
- ギルド出席報酬 = ギルド資源 +5
- トリガー: ギルドメンバーがその日初めてログインしたとき1回
- 範囲: 当該ギルドの全ギルドメンバーそれぞれ
- UI: ギルド画面の上部に資源総量を表示 + "出席"ボタン
この決定一つを3職種向けにパッケージングして。
企画はmarkdownで意図・ルール・データへの影響を、アートはhtmlに入れる画面仕様を
レイアウト・コンポーネント・スタイルガイド参照中心で (データ・トリガーへの言及は禁止)、
プログラムはデータ1行・インターフェース・トリガー・検証項目で。
三つのパッケージすべてに不変値(+5、初回ログイン1回、ギルドメンバー全員)を同じように入れて、
わからない値はでっち上げず[要確認]と表示して。
1) 企画 → markdown
## ギルド出席報酬
- 意図: 毎日1回の接続をギルド単位の協力資源に換算し、継続率を引き上げる。
- ルール: ギルドメンバーが当日初めてログインしたときギルド資源 +5。1日1回。ギルド単位で累積。
- データへの影響: guild_resource_total が増加。資源インフレのモニタリングが必要 [要確認: 上限未定]。
2) アート → 画面仕様 (htmlに入れる内容)
- ギルド画面の上部領域に資源総量を表示 (progress_bar + 数字badge)。
- 右側に"出席"ボタン。押したときの状態変化は06_UIガイドのボタン状態ルールに従う。
- スタイル: 06_UIガイドに準拠。色は [要確認: ギルドテーマのカラートークン未指定]。
- (データ数値・トリガーはこのパッケージから意図的に除外)
3) プログラム → データ/インターフェース
- データ: guild_table にカラム追加 — guild_resource_total (int, default 0)
- インターフェース: GuildResourceManager.add(guild_id, amount)
- トリガー: 1日の初回ログインのイベントフックで add(guild_id, 5) を1回呼び出し
- 検証: integrity_check_guild_resource (負数不可 / 1日1回を超える呼び出しは遮断)
- 同時実行性: 同じギルドメンバーがマルチデバイスで同時ログインした場合の重複加算 [要確認]
生の出力をそのまま使ってはいません。3か所に手を入れました。
[要確認]2件(資源の上限、マルチデバイスでの重複加算)は、決定事実になかった抜けです。AIがでっち上げずに表示したのは正しい判断ですが、これはframing以前に決定そのものの穴なので、95_BattleTFへ差し戻すべきものです。アートパッケージを修正して:
- 「06_UIガイドに従う」のような外部文書参照の文言をすべて削除。
- ボタンの押下/待機/完了の3状態の視覚的な違いを、画面仕様の中で直接記述。
- アートチームはこの1枚だけを見て作業する前提で、他の文書にジャンプしなくて済むよう自己完結的に。
プログラムパッケージの[要確認]2件は成果物から外し、
代わりに先頭へ「95_BattleTF再確定が必要な項目」ブロックとして分離して。
この一度の拒否・再リクエストで、成果物は3職種がそれぞれ自分の持ち場ですぐ手に取って使える形になりました。AIはパッケージの草案を3セットこしらえ、抜けに印まで付けてくれましたが、どのパッケージから何を削るか — アートパッケージから外部参照を抜き、プログラムパッケージから未確定項目を取り除くという — そのハサミ入れは最後まで私の手に残りました。framingの核心となる判断は、含める側ではなく外す側にあります。
パッケージをどこに置くかによって方式が分かれます。三つのうちどれを使うかは、仕様書のサイズと運用体力で決めます。
(1) 一つの文書内のオーディエンス別サマリー。 本文の後ろに職種別サマリーのセクションを付けます。5人が一つのファイルを共有しつつ、それぞれ自分の節だけを読みます。
## オーディエンス別サマリー
### コード(実装)
- データ: guild_table.guild_resource_total (int)
- インターフェース: GuildResourceManager.add(guild_id, amount)
- トリガー: 1日の初回ログイン1回
- 検証: integrity_check_guild_resource
### アート(ビジュアル)
- 画面: ギルド上部の資源総量 + 出席ボタン
- コンポーネント: progress_bar, badge, button(3状態)
- 優先順位: 今回のマイルストーン
### QA(検証)
- 受け入れ基準: 出席後にギルド資源 +5 が反映、1日1回超過は遮断
- リスク: 資源インフレ、マルチデバイスでの重複加算
(2) オーディエンス別の個別成果物。 本文1個に対して、職種別ファイルを別々に置きます。95_BattleTFでアートチームにhtmlだけを送り、mdを送らない運用がこの方式の実戦形です — 同じ決定でも、職種ごとに媒体そのものが違います。
spec_guild_attendance.md — 企画本文(全体コンテキスト)
guild_screen_v3.html — アート (htmlのみ、md学習0)
guild_table 1 row + add() — プログラム (データ/インターフェース)
qa_guild_attendance.md — QA (受け入れ基準・リスク)
分量の大きい仕様書に向いていて、媒体が職種のツールへ直接入っていきます。代わりに、一つの決定が変わると複数の成果物をまとめて直す必要があり、運用負担が大きくなります。
(3) Wikilinkグラフ。 本文には職種別の開始点だけをリンクで入れ、各自が自分の枝をたどって探索します。
[[spec_guild_attendance]]
├── [[code_guild_table]]
├── [[ui_guild_screen_v3]]
└── [[qa_guild_attendance]]
三つの方式のコストと回収は次のとおりです。以下の数値のうち「効果」は著者の推定(未検証)であり、信頼してよいのは方向と相対比率だけです。
| 方式 | コスト | 回収タイミング |
|---|---|---|
| (1) オーディエンス別サマリー | 本文の分量+30%前後 | ほぼすべての仕様書ですぐ回収 |
| (2) 個別成果物 | 成果物Nセットの運用 | 分量が大きく媒体が職種別に違うときだけ回収 |
| (3) Wikilinkグラフ | グラフインフラへの先行投資 | 仕様書が蓄積されグラフ自体が資産になったとき回収 |
ほとんどの仕様書には(1)が合います。コストが最も小さく、回収が最も速いからです。(2)はアートのhtmlのように媒体がすでに分かれている場所だけで使い、(3)は仕様書が十分に蓄積され、リンクグラフが探索価値を生むようになったら有効化します。
オーディエンスを仕様書ごとに定義し直すと、framingは毎回ゼロからこね直しになります。そこでコードを固定します。
| オーディエンスコード | 領域 |
|---|---|
| code | コード・システム・データ |
| art | アート・ビジュアル・UI |
| sound | サウンド・音響 |
| anim | アニメーション・モーション |
| qa | QA・検証 |
この五つが内部運用の標準です。外注・法務のような外部オーディエンスは、この標準の外で別途扱います。五つに固定すれば、LLMにframingを任せるときにオーディエンス定義を毎回書き直さなくて済み、抜けたオーディエンスをチェックリストで捕まえられます。
仕様書のたびに5職種のサマリーを手で書いていると、結局書かなくなります。そこで流れを次のように束ねました。
flowchart LR
A[プランナー: 決定事実を作成] --> B[LLM: 5オーディエンスのパッケージ草案]
B --> C[プランナー: 拒否/補強/維持を判断]
C --> D{抜けを発見?}
D -- はい --> E[95_BattleTF再確定へ差し戻し]
D -- いいえ --> F[最終仕様書 + 職種別framing]
E --> A
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class A,C,D human;
class B ai;
class F pass;
プランナーが決定事実だけを書けば、LLMが五つのパッケージ草案を作り、プランナーは拒否・補強・維持を判断します。抜け([要確認])が出てきたらframingの中で処理せず、決定の段階へ差し戻します — framingは決定の穴を埋める道具ではなく、決まった決定を運ぶ道具だからです。
このサイクルで繰り返し踏む落とし穴を四つ、処方箋と一緒に置いておきます。
| 落とし穴 | 症状 | 処方箋 |
|---|---|---|
| 情報の重複 | 同じ内容が本文・サマリーで繰り返され運用負担に | 本文に1回、サマリーは差分項目だけ |
| 情報の欠落 | ある職種に重要な値が丸ごと抜ける | 5オーディエンス固定のチェックリストで欠落を点検 |
| 本文の無視 | サマリーだけを見て本文のコンテキストを読み流す | サマリーの末尾に「根拠は本文」と明記 |
| 媒体の混線 | アートにmdを送って学習負担を与える | 職種別媒体の原則(アート=html)を固定 |
自動化は作成負担を仕様書1件あたり5分前後まで下げますが、拒否・補強・維持の判断までは自動化されません。その判断こそが人の持ち場です。
次は、著者が運用するプロジェクトAでframing導入の前後を比較した値です。絶対数値は著者の推定(未検証)であり、信頼してよいのは変化の方向と相対比率です。
| 項目 | framingなし | framing運用 | 方向 |
|---|---|---|---|
| 職種別の解釈事故 | 四半期あたり15〜20件 | 四半期あたり3〜5件 | 大幅減 |
| オーディエンスが仕様書を読む時間 | 15〜30分 | 5〜10分(自分の節だけ) | 減少 |
| 決定 → 作業開始 | 1〜2日 | 4〜8時間 | 短縮 |
| 職種間の解釈衝突 | 四半期あたり8〜12件 | 四半期あたり2〜3件 | 減少 |
| 仕様書の作成時間 | 1〜2時間 | 1.5〜2.5時間(LLM補助) | 小幅増 |
仕様書の作成自体は少し長くなります。職種別のパッケージを載せるからです。しかしその後の職種ごとの作業サイクルが短くなり、決定から作業開始までの全体時間は減ります。このトレードオフがframing導入の核心的な根拠です。LLM補助のレビューが負担になるチームなら、方式(1)の手書き5オーディエンスサマリーを先に定着させ、その上に自動化を載せる順序が安全です。
ゲーム外への応用。 一つの決定をオーディエンスごとに違う形でパッケージングしつつ、不変の事実(数値・時点・範囲)はどこでも保存するというframingは、ゲームに限らず、あらゆる組織のお知らせ・リリースコミュニケーションにそのまま使えます。たとえば「サブスクリプション料金を9,900ウォンに7月1日から引き上げる」という1件を決定したなら、開発チームには決済テーブルのカラム・適用時点といったデータとして、デザインチームには案内バナーの画面1枚として、カスタマーサポートチームには想定問い合わせの応対スクリプトとして、パッケージが分かれます。パッケージは三つとも違いますが、「9,900ウォン・7月1日・新規および既存加入者全員」という三つの数字がどのパッケージでもずれた瞬間、顧客とのトラブルが噴き出します。
setup
prompt
[決定事実]
(数値・時点・範囲を1行ずつ)
この決定をcode・art・sound・anim・qaのうち該当する職種向けにパッケージングして。
パッケージごとにその職種が関心を持たない情報は外しつつ、不変値(数値・時点・範囲)はどのパッケージにも同じように入れて、
アートのパッケージは他の文書を参照せずその1枚だけで自己完結的に、わからない値はでっち上げず[要確認]と表示して。
verify
[要確認]があれば、framingではなく決定の段階(95_BattleTFのようなTF)へ差し戻しましょう。一人ミニ版
一人で作業しているなら、オーディエンスを二つに減らしましょう — 「後日の自分」(実装)と「レビュアー」(QA)です。決定事実を1行書き、LLMに「これを実装メモとレビューチェックリストの2セットに分けて」と頼んだあと、2セットで核心の数値が一致するかだけを突き合わせれば十分です。オーディエンスが二つだけでも、「同じ決定を違う形でパッケージングしつつ不変値は保存する」というframingの骨格はそのまま機能します。
会議が終わって5分が経ちました。会議室のホワイトボードには、まだ文字が残っています。「戦闘の被弾判定、クライアント先行反映で行く。ただしサーバー検証の優先度は次のスプリント」。5人が30分かけてたどり着いた結論です。全員がうなずき、誰かが写真を撮りました。
3週間後、同じ5人が同じ会議室に再び集まりました。議題リストの1行目にこう書いてあります。「戦闘の被弾判定 — クライアント先行反映 vs サーバー検証、要決定」。誰も3週間前の結論を覚えていません。ホワイトボードの写真は誰かのカメラロールのどこかにあり、その人は今日休暇です。また30分を使います。今度は逆の結論が出ます。
これが、議事録が最大の痛みである理由のすべてです。会議では決定が下されます。ところが、その決定が次の会議、次のドキュメント、次のビルドへと流れていかない(propagateしない)のです。決定は下されたのに、伝播しません。本章は、その切れた輪をデータでつなぎ直す話です。
著者が運営する個人RnDシステムは、17個のドキュメントに分かれています。atom命名標準、関係図の自動化、Layerマッピングガイド、JIT注入インフラなど。その中で最も多くの時間を吸い込んだ単一のドキュメントが、議事録改善計画です。ほかの16個を合わせたものに匹敵するほど、比重が大きいのです。
最初は不思議でした。議事録なんて、ただ書き留めればいいのではないか。ところが測ってみると、痛みの位置は議事録の作成ではなく、議事録のあとにありました。会議で決定は確かに下されました。問題は、その決定が誰の責任で、どんな根拠で、いつまでに、何につながるのかが、会議室のドアを出た瞬間に蒸発することでした。
この断絶をひと場面で描くと、こうなります。
flowchart LR
M[会議
30分の議論] --> D[決定発生
全員同意]
D -.断絶.-> X[次の会議で
再上程]
D -.断絶.-> Y[ドキュメントに反映されない]
D -.断絶.-> Z[ビルドから漏れる]
X --> M
style D fill:#ffe6cc,stroke:#d79b00
style X fill:#f8cecc,stroke:#b85450
style Y fill:#f8cecc,stroke:#b85450
style Z fill:#f8cecc,stroke:#b85450
点線が、切れた伝播です。決定(オレンジ)は下されたのに、3つの分岐(赤)のどこへも流れていきません。流れていけなかった決定は、3週間後の会議に戻ってきます。矢印が上に曲がって会議へ戻っていくあの循環こそ、痛みの本体です。
伝播が切れると、4つのことが同時に起きます。
決定の履歴を失います。「なぜそう決めたんだっけ?」に「覚えていないから、また会議を入れよう」と答えることになります。繰り返し会議が増えます。同じ議題が四半期ごとに再上程されます。新しく合流したメンバーがコンテキストをつかめません。意思決定の積み重ねが見えないので、毎回1対1で説明しなければなりません。そしてAIによる補助が無力になります。コンテキストがないので、答えが一般論にとどまります。議事録が散らばっていると「うちのチームがこの議題を以前どう決めたか」をAIに渡せず、AIはインターネットの平均値を返してきます。
4つはすべて、同じ根から生まれます。決定がデータではなく、メモとして扱われているからです。メモは揮発し、データは流れます。
ここで視点を一度ひっくり返す必要があります。議事録を「会議の成果物」と見るなら、書き留めて保管すれば任務完了です。保管された議事録は、机の上のメモ用紙と同じです。その日は見えていても、翌週にはどこへ行ったか分かりません。
議事録を「意思決定データベース」と見ると、まったく別の作業になります。議事録そのものではなく、議事録から抽出した決定が資産であり、その決定が検索可能で、参照可能で、伝播可能でなければなりません。議事録は、決定を汲み上げるための鉱脈にすぎません。
この転換をコードで強制するのが、第17部全体の骨子です。著者のシステムには、この転換を支えるatomがひとつ入っています。名前はdecision_summary_not_clickup_mirrorです。かみくだいて言えば「議事録の決定サマリーはClickUp(タスクトラッカー)のミラーではない」という原則です。
このatomがなぜ必要だったのかが、痛みの核心を突きます。議事録の決定スロットをそのままタスクボードに書き写すチームは多いものです。すると「やること」は残るのに、「なぜそう決めたのか(根拠)」が消えます。タスクトラッカーは何をやるかを収めますが、なぜそう決めたかは収めません。3週間後に会議が繰り返される理由は、まさにこれです。やることはクローズされたのに根拠がないので、誰かが「ところでこれ、なぜこうすることにしたんだっけ」と聞いても、答えられる人がいません。だから決定サマリーはトラッカーのミラーになってはならず、根拠(rationale)を抱えた独立の資産でなければなりません。atomの名前そのものが、この禁止線なのです。
伝播する決定と揮発する決定の違いは、構造にあります。揮発する決定は「クライアント先行反映で行く」というひとつの文です。伝播する決定は、4つのフィールドに分解されます。
4つのフィールドのうち、最も重要なのがownerです。決定に責任者がいなければ、その決定は誰の仕事でもなく、誰の仕事でもない決定は実行へと伝播しません。だから著者の抽出パイプラインは、ownerが空のときに素通りさせず、[MISSING]として明示的に報告します。責任線が空白であるという事実そのものを、表面に引き上げるのです。
rationaleは、先ほど述べたdecision_summary_not_clickup_mirror原則が住む場所です。根拠がなければ、3週間後に会議が繰り返されます。follow_upは、決定が実際の実行へつながる橋です。このフィールドが空なら、決定は決定のまま残り、ビルドに届きません。
この4つのフィールドを人が毎回手で埋めることも可能ですが、それでは強制力が弱いのです。著者のシステムは、議事録から決定を自動で抽出し、欠けたフィールドを報告するパイプラインを使います。3つのスクリプトが直列につながります。
flowchart TD
R[議事録 .md
標準フォーマット] --> L[meeting_lint.py
フォーマット検証]
L -->|通過| P[decision_parser.py
4フィールド抽出]
L -->|差し戻し| R
P -->|ownerなし| MISS["[MISSING] 報告"]
P -->|4フィールド完備| PEND[pending atom
1週間の検証待機]
PEND --> PROM[promote.py
週1回の昇格]
PROM --> ATOM[正式atom
+ JIT manifest登録]
style L fill:#dae8fc,stroke:#6c8ebf
style P fill:#d5e8d4,stroke:#82b366
style PROM fill:#e1d5e7,stroke:#9673a6
style MISS fill:#f8cecc,stroke:#b85450
第1段階のmeeting_lint.pyは、議事録が標準フォーマットに従っているかを検査します。frontmatterがあるか、議題/決定/アクション/次回会議の4スロットが埋まっているか。フォーマットが壊れた議事録はここで差し戻され、作成者に返ります。自動パーサーはフォーマットが強制された入力しか処理できないため、このlintがパイプライン全体の入口ゲートの役割を果たします。
第2段階のdecision_parser.pyが核心です。決定スロットを読み、4つのフィールド(decision/owner/rationale/follow_up)に分解します。ここでownerが見つからなければ、その決定を捨てるのではなく[MISSING]として報告します。責任者のいない決定を黙って通過させることが、最も危険だからです。
第3段階は、抽出された決定がすぐには正式な資産にならず、pending状態で1週間待機することです。この検証期間が可逆ゲートです。1週間以内に「これは決定ではなく議論だった」「根拠が間違っていた」と判明すれば破棄します。そしてpromote.pyが、週1回のレビューを生き残った決定だけを正式なatomフォルダーへ移し、JIT manifestに登録します。登録された決定は、次のセッションから関連作業に自動で注入されます。ここでようやく、決定が流れはじめます。
可逆と不可逆の境界が、ここにあります。pendingでの破棄までは可逆です。ところがpromoteが終わり、決定がほかのドキュメント・マスターデータ・ビルドへ伝播すると、そこからは不可逆です。チームメンバーの認識が変わり、従属する決定がその上に積み上がるからです。だから人によるチェックはすべて、promoteの直前、つまりpendingの可逆区間で終わらせなければなりません。
抽象論だけでは腑に落ちないので、実際に壊れた議事録を1枚パイプラインに入れてみた記録を、そのまま載せます。入力は、フォーマットが半分ほど崩れた議事録です。
入力 — 2026-06-02-battle.md(フォーマット不良)
---
type: meeting_note
date: 2026-06-02
category: battle
---
## 議題
1. 被弾判定の位置(クライアント vs サーバー)
## 決定
- 被弾判定はクラで先行反映することにした。体感の反応速度が
サーバー往復より重要だという意見が優勢。チートのリスクは許容。
- サーバー検証は次のスプリントで別タスクに。
## アクションアイテム
- [ ] サーバー検証の設計
この議事録は決定を書いてはいるものの、ownerがどこにもありません。誰がサーバー検証を担当するのかが空白です。次回会議の日程スロットも丸ごと抜けています。これをパーサーに入れて、何が出てくるかを見ました。
プロンプト(decision_parser.pyに付けたLLM補助プロンプト全文)
以下の議事録の「## 決定」スロットを読み、決定ごとに4つのフィールドを
抽出せよ: decision、owner、rationale、follow_up。
- ownerを本文から特定できない場合は必ず "[MISSING]" と表記。
推測で埋めないこと。
- rationaleは本文に書かれた根拠のみ引用。なければ "[MISSING]"。
- follow_upはアクションアイテムまたは次のスプリントへの言及と接続。
JSON配列のみで出力。
[議事録本文を貼り付け]
Claudeの生の出力(要約せず、そのまま)
[
{
"decision": "被弾判定をクライアントで先行反映する",
"owner": "[MISSING]",
"rationale": "体感の反応速度がサーバー往復より重要。チートのリスクは許容",
"follow_up": "サーバー検証を次のスプリントの別タスクとして進める"
},
{
"decision": "サーバー検証を次のスプリントで別タスクに分離",
"owner": "[MISSING]",
"rationale": "[MISSING]",
"follow_up": "アクションアイテム「サーバー検証の設計」と接続(担当者未指定)"
}
]
ここで止めて、出力を検討しました。2件の決定はどちらもownerが[MISSING]と判定されました。正確です — 議事録のどこにも責任者がいなかったのですから、推測で埋めなかったのが正しいのです。2件目の決定のrationaleも[MISSING]ですが、実際に本文には2件目の決定の根拠がありませんでした。パーサーがでっち上げなかったという意味なので、信頼できます。
ただ、1か所だけ曖昧でした。1件目の決定のfollow_upが「サーバー検証を次のスプリントへ」なのですが、これは2件目の決定と内容が重なります。パーサーが2つの決定の境界を、やや曖昧に見たのです。人が介入すべきポイントです。
人による拒否 + 再依頼
1件目の決定のfollow_upが2件目の決定本文と重複している。
1件目のfollow_upは「サーバー検証タスクの作成」に絞り、
サーバー検証の具体的な内容は2件目の決定に渡せ。
また、2件の決定ともownerが[MISSING]なので、
この議事録をpendingに上げる前にownerの確定が必要だという
警告行を出力の先頭に付けよ。
Claudeの再出力(生のまま)
⚠️ 警告: 決定2件ともowner [MISSING]。pending昇格前に
会議の進行役へownerの確定依頼が必要。
[
{
"decision": "被弾判定をクライアントで先行反映する",
"owner": "[MISSING]",
"rationale": "体感の反応速度を優先、チートのリスクは許容",
"follow_up": "サーバー検証タスクの作成(詳細は決定 #2)"
},
{
"decision": "サーバー検証を次のスプリントで別タスクに分離",
"owner": "[MISSING]",
"rationale": "[MISSING]",
"follow_up": "「サーバー検証の設計」アクションアイテムと接続"
}
]
このトランスクリプトが見せているものこそ、第17部の核心です。パーサーは決定を汲み上げながら、ownerという責任線が空白だという事実を隠しませんでした。[MISSING]が2回刻まれ、それが会議の進行役への「ownerを確定せよ」という信号として返っていきました。決定が実行へ伝播するには責任者が必要で、責任者がいなければシステムがそれを表面へ押し上げます。壊れた議事録1枚がこのゲートを通過するには、人がownerを埋めるしかありません。伝播の最初の結び目が、ここで結ばれます。
ちなみに、上の出力の⚠️はコンソールの警告行にすぎず、本文フォーマットの一部ではありません。議事録自体は、引き続ききれいな4スロットのマークダウンのまま残ります。
第17部はもともと6つの章(動機・抽出・カテゴリー・キャプション・同期・AI補助)で設計していたのを、4つに統廃合しました。画像キャプションと同期は議事録の枝葉であり、最大の痛みである「決定の伝播」をひとつの章にまとめて先頭に立てるのが正しかったからです。最大の痛み(会議 → 決定 → 実行の伝播)を§17.1に引き上げ、その痛みを解くパイプライン(meeting_lint → decision_parser → promote)を、そのあとに配置しました。
議事録をデータベースとして扱う視点、決定を4フィールドに分解する構造、ownerが空なら[MISSING]として報告するゲート、pending 1週間の可逆検証 — この4つが、切れた伝播をつなぎ直します。決定は下されたのに流れないという痛みは、決定を流れられる形にしておけば解けるのです。
ゲーム外への応用。 「会議では決定が下されたのに、その決定が次の会議へ流れていかない」という痛みは、ゲーム開発に限らず、あらゆる職場の会議室で毎週繰り返されます。決定を一文のメモではなく4つのフィールド(何を・誰が・なぜ・次の行動)に分解して書き、責任者が空白なら
[MISSING]として表面に引き上げるやり方は、どんな会議にもそのまま持ち込めます。たとえばマーケティングの週次会議で「次のキャンペーンはインスタ中心で行く」と合意したなら、そこにowner(誰が実行するか)、rationale(なぜインスタなのか — 前四半期のコンバージョン率という根拠)、follow_up(予算案の作成)を付けて書いてみましょう。3週間後に「あれ、誰がやることになったんだっけ」という質問は、二度と出てきません。
Webチャットボット最小ルート(ターミナルなし) — 本章の核心はスクリプトではなく、「決定を4フィールドに分解して流れるようにする」という発想です。その発想は、CLI・フック・atomのインフラなしに、Webチャットボット(ChatGPTまたはClaudeのWeb版)だけでもそのまま再現できます。以下の3ステップが本流です。
1. 会議が終わったら、議事録(または会議メモ)をそのままコピーします。フォーマットがなくても構いません。
2. Webチャットボットの入力欄に以下のプロンプトを貼り、その下に議事録を貼り付けます([議事録本文]の位置)。これが、decision_parser.pyがやっていたことを手で1回やるということです。
以下の議事録から、決定ごとに4つのフィールドを表として抜き出せ:
decision(何を)、owner(誰が責任)、rationale(なぜ)、follow_up(次の行動)。
- ownerを特定できない場合は必ず "[MISSING]"。推測禁止。
- rationaleは本文に書かれた根拠のみ。なければ "[MISSING]"。
[議事録本文]
3. 出力された表で[MISSING]が付いたセルを、会議の進行役に確認して埋めます。完成した表をdecisions.mdのような1枚のドキュメントに日付順に貼って積み上げれば、そのドキュメントがそのまま意思決定DBです。検索はドキュメント内検索(Ctrl+F)で十分です。スクリプト・atom・JITは、この習慣が積み重なって検索が苦しくなったときに、初めて導入すればいいのです。
setup(インフラ版 — 上の最小ルートが手になじんだあと)
- 議事録の標準フォーマットを1枚決めましょう。frontmatter(type/date/category)+ 4スロット(議題/決定/アクション/次回会議)。
- フォーマット検証用のmeeting_lint.py、決定抽出用のdecision_parser.py、昇格用のpromote.pyの3スクリプトを置きましょう(最初はlintとparserだけで十分です)。
- 決定の4フィールド(decision、owner、rationale、follow_up)を明示しましょう。ownerが空なら[MISSING]を強制するルールをparserに入れます。
prompt(decision_parserに付けるLLM補助プロンプト)
以下の議事録の「## 決定」スロットを読み、決定ごとに4つのフィールドを抽出せよ:
decision、owner、rationale、follow_up。
- ownerを特定できない場合は必ず "[MISSING]"。推測禁止。
- rationaleは本文に書かれた根拠のみ引用。なければ "[MISSING]"。
- follow_upはアクションアイテム・次のスプリントへの言及と接続。
JSON配列のみで出力。
[議事録本文]
verify
- 出力のすべての決定にownerが埋まっているかを確認しましょう。[MISSING]がひとつでもあれば、会議の進行役にownerの確定を依頼してから、pendingに上げます。
- rationaleが本文にない根拠をでっち上げていないかを見ましょう(なければ[MISSING]であるのが正常です)。
- pendingに1週間置いたあと、週1回のレビューを生き残った決定だけをpromote.pyで正式なatomに昇格させましょう。
スクリプト3つが負担なら、ここまで減らしましょう。議事録のフォーマットだけ4スロットに統一し、会議が終わったら決定スロットだけを切り出して、LLMに上のプロンプトで1回かけます。ownerが[MISSING]と出た決定だけ、その場で責任者を書き込みましょう。自動化なしでこれだけやっても、「決定に責任者がいない」という最もありがちな伝播の断絶ひとつは防げます。lint・promoteは、議事録が積み重なって検索が必要になったときに追加すればいいのです。
水曜日の朝、出社するなり社内メッセンジャーに通知が届きました。「先週、インベントリの枠を30に増やすと決めましたよね? マスターデータを直すのは誰の担当でしたっけ?」スレッドには誰も答えられません。議事録は確かにあります。どこかのフォルダーに。開いてみると議題と議論はびっしり書かれているのに、「結局何を決めて、誰が責任を持つのか」は文章の間に溶け込んでいます。結局、次の会議で同じ議題を最初から蒸し返すことになります。
この章は、その3日間の空白を埋める機械の話です。議事録が1件入ると、書式チェックを通過し、決定の4フィールドが抽出され、持ち主のいない決定には[MISSING]のラベルが貼られ、候補ファイルが作られ、1週間後に人のレビューを経て、自動注入される資産になります。人の手が触れるのは、両端の2か所だけです。議事録を書く入口と、週1回レビューする出口です。
まず全体を1枚で見ます。四角の一つひとつが、小さなスクリプトか、人の判断です。手作業のマスは2つだけで、残りは自動で流れます。
flowchart TD
A["会議の実施"] --> B["標準書式で議事録を作成
(人)"]
B --> C{"meeting_lint.py
書式チェック"}
C -->|違反| B
C -->|通過| D["decision_parser.py
決定4フィールド抽出"]
D --> E{"ownerはあるか?"}
E -->|なし| F["[MISSING]報告
持ち主の指定を要請"]
F --> B
E -->|あり| G["pending atom候補ファイル
生成"]
G --> H{"週1回レビュー
(人)"}
H -->|昇格| I["promote.py
正式atom + JIT登録"]
H -->|破棄| J["破棄履歴を保存"]
H -->|保留| G
I --> K["次のセッションから自動注入"]
style B fill:#e8f0ff,stroke:#3366cc
style H fill:#e8f0ff,stroke:#3366cc
style F fill:#fff0e8,stroke:#cc6633
style J fill:#f0f0f0,stroke:#999999
青いマス2つ(議事録の作成・週1回レビュー)だけが人で、残りはスクリプトです。オレンジのマス([MISSING]報告)は、自動チェックが人を呼び戻す場所です。決定に持ち主がいなければ、パイプラインはただ止まるのではなく、誰が責任を持つのか決まるまで、その決定を議事録作成の段階へ送り返します。これがこのパイプラインの中核となる設計です。空欄を黙って見過ごさず、騒がしく報告します。
資産フォルダー全体の構造は、このようになっています。
抽出を可能にするには、議事録が機械に読める形になっていなければなりません。「## 決定」セクションがなかったり、決定が地の文の段落に溶け込んでいたりすると、パーサーは何も抽出できません。そこで、いちばん先に書式チェックを入れます。meeting_lint.pyがやることは単純です。必須のfrontmatterがあるか、必須セクションがあるか、決定スロットがD1、D2の形式で埋まっているか。
# meeting_lint.py の骨格
REQUIRED_FRONTMATTER = ["type", "date", "category", "attendees"]
REQUIRED_SECTIONS = ["## 議題", "## 決定", "## アクションアイテム", "## 次回会議"]
ALLOWED_CATEGORIES = ["art", "battle", "daily", "issue", "review"]
def lint(meeting_note_path):
fm, body = parse_markdown(meeting_note_path)
errors = []
for key in REQUIRED_FRONTMATTER:
if key not in fm:
errors.append(f"frontmatter欠落: {key}")
if fm.get("category") not in ALLOWED_CATEGORIES:
errors.append(f"category値が不適切: {fm.get('category')}")
for section in REQUIRED_SECTIONS:
if section not in body:
errors.append(f"セクション欠落: {section}")
if "## 決定" in body:
block = extract_section(body, "## 決定")
if not any(l.strip().startswith("- D") for l in block.split("\n")):
errors.append("決定スロットが空 (D1, D2...の形式が必要)")
return errors
このチェックを、議事録のコミット前フックに掛けます。書式に違反するとコミット自体がブロックされます。推奨にとどめると忙しい日にこっそりスキップされ、一度スキップされた書式は翌週には崩れます。1〜2週間ブロックされてみれば、書式は手になじみます。ただし、厳しすぎると議事録の作成自体を先送りするようになるため、適応期間の後にfalse positiveを一度整理してあげるのが現実的な運用です。
書式を通過した議事録から、decision_parser.pyが決定スロットを読み取ります。決定1件から抽出すべきものは、正確に4つです。何を決めたのか(decision)、誰が責任を持つのか(owner)、なぜそう決めたのか(rationale)、次に何をすべきか(follow_up)。この4フィールドが決定を資産にします。特にowner。持ち主のいない決定は、決定ではなく希望事項です。そのためパーサーは、ownerが空のとき黙って空欄のままにせず、[MISSING]を記入して報告します。
ここから最後まで、議事録1件が資産になる過程を、1行も飛ばさずに追いかけてみます。入力からatom昇格まで、単一の連続した例です。
================ 入力: meetings/2026-05-18_battle_tf.md ================
---
type: meeting
date: 2026-05-18
category: battle
attendees: [イ・ミンス, teammate_a, teammate_b]
related_atoms: [combat_global_cooldown_constant]
---
## 議題
- 戦闘のグローバルクールダウン(GCD)値の統一
- 回復スキルのGCD例外の可否
## 決定
- D1: 戦闘のグローバルクールダウンを0.5秒に統一する。(所有者: teammate_a) [根拠: refgameと比較した入力レスポンス体感テストで0.5秒が最も安定]
- D2: 回復スキルはグローバルクールダウンの適用から除外する。[根拠: 回復サイクルが途切れる懸念]
## アクションアイテム
- @teammate_a: 戦闘マスターデータのcooldownカラムへ一括で0.5を適用 (~MM-DD)
## 次回会議
- MM-DD 14:00、回復サイクル1週間テストの結果レビュー
================ $ python meeting_lint.py meetings/2026-05-18_battle_tf.md ================
[OK] frontmatter 4/4、セクション4/4、決定スロット2件を検出。コミット許可。
================ $ python decision_parser.py meetings/2026-05-18_battle_tf.md ================
[
{
"id": "D1",
"decision": "戦闘のグローバルクールダウンを0.5秒に統一する。",
"owner": "teammate_a",
"rationale": "refgameと比較した入力レスポンス体感テストで0.5秒が最も安定",
"follow_up": "戦闘マスターデータのcooldownカラムへ一括で0.5を適用 (~MM-DD)",
"source_meeting": "2026-05-18_battle_tf.md",
"category": "battle",
"related_atoms": ["combat_global_cooldown_constant"]
},
{
"id": "D2",
"decision": "回復スキルはグローバルクールダウンの適用から除外する。",
"owner": "[MISSING]", # ← 所有者の記載なし。パーサーが報告
"rationale": "回復サイクルが途切れる懸念",
"follow_up": null, # ← 後続アクションもなし
"source_meeting": "2026-05-18_battle_tf.md",
"category": "battle",
"related_atoms": ["combat_global_cooldown_constant"]
}
]
[WARN] D2: owner=[MISSING] — 持ち主のいない決定。pending生成を保留し、議事録の作成者へ差し戻し。
================ pending生成: D1のみ通過 ================
$ cat atoms/pending/meeting_decision_2026-05-18_D1.md
---
name: meeting_decision_2026-05-18_D1
description: 戦闘グローバルクールダウン0.5秒統一の決定
status: pending
type: decision
source_meeting: 2026-05-18_battle_tf.md
owner: teammate_a
category: battle
related_atoms: [combat_global_cooldown_constant]
created: 2026-05-18
---
## 決定
戦闘のグローバルクールダウンを0.5秒に統一する。
## 根拠
refgameと比較した入力レスポンス体感テストで0.5秒が最も安定。
## 後続アクション
- [ ] @teammate_a: cooldownカラムへ一括で0.5を適用 (~MM-DD)
================ 1週間後の週次レビュー ================
$ python promote.py atoms/pending/meeting_decision_2026-05-18_D1.md
[PROMOTE] → atoms/combat_global_cooldown_constant_decisions/meeting_decision_2026-05-18_D1.md
[JIT] manifest登録: trigger=(전투|쿨다운|GCD|cooldown)、atom 18個 → 19個
[OK] 次のセッションから「글로벌 쿨다운」と入力すると、この決定を自動注入。
この1つのボックスが、パイプラインのすべてです。注目すべきはD2です。決定の内容はまともで根拠もあるのに、ownerが空です。パーサーはこれをそのまま通しません。[MISSING]を記入し、pending生成を保留したまま、作成者へ差し戻します。D2は数日後、「回復サイクル1週間テストの結果レビュー」の会議で持ち主を得て、再び入ってきます。空欄を堰き止めるこの一度の差し戻しが、3日後の社内メッセンジャーで「あれ、誰がやることになってましたっけ?」が二度と出ないようにします。
ownerがなければ報告するというルール自体は、atom1件として固定してあります(decision_summary_not_clickup_mirror、§17.1.2)。タスク管理ツールには「マスターデータの修正」というToDoが載っているかもしれませんが、そのToDoがなぜ・何を決定した結果なのかは、議事録のatomにしか残りません。
パーサーを通過した決定は、すぐに正式なatomになるのではなく、pending/で1週間待ちます。会議で自信を持って決めたことが、1週間運用してみるとひっくり返ることは珍しくないからです。上の例のD2が、まさにその危険地帯にありました。「回復はGCD(グローバルクールダウン)除外」という決定は、1週間テストで回復サイクルが壊れれば、再びひっくり返るかもしれません。pendingは、インクが乾く時間を強制的に確保するマスです。
そして、破棄も資産として残します。もしD2のような決定が1週間テストで崩れたなら、ただ消すのではなく、破棄履歴のatomを作ります。
---
name: meeting_decision_2026-05-18_D2_DISCARDED
status: discarded
discarded_reason: 1週間テストの結果、回復サイクルのDPS曲線が崩壊
---
## 元の決定
回復スキルにもグローバルクールダウン0.5秒を適用する。
## 破棄の理由
1週間テストで回復サイクルのDPSが落ち、全体のバランスが崩壊。除外の決定へ差し戻す。
## 教訓
「回復はGCD除外が標準」→ combat_healing_skill_cooldown_exception atomへ昇格。
破棄履歴が、次の会議での「この議題、前に試さなかったっけ?」の答えになります。同じ失敗を二度しないように防ぐ、いちばん安い道具です。ただし、破棄の記録が積もると検索ノイズになるため、四半期ごとに重複を整理して教訓だけを残す手入れが必要です。
毎週決まった時間に、pendingの候補を一括で見ます。結果は3つのうちのいずれかです。
| 結果 | 処理 |
|---|---|
| 昇格 | pending → 正式atomフォルダーへ移動、JIT manifestに登録 |
| 破棄 | 決定がひっくり返った → pendingから外し、破棄履歴atomを保存 |
| 保留 | 情報不足 → pendingを1週間延長 |
レビューはatom10個あたり15分前後です。昇格が決まれば、promote.pyがファイル移動とmanifest更新を一度に処理します。
# promote.py の骨格
def promote(pending_path):
fm, body = parse_markdown(pending_path)
target = ATOM_BASE / f"{fm['related_atoms'][0]}_decisions" / f"{fm['name']}.md"
move(pending_path, target)
manifest = json.load(open(JIT_MANIFEST))
manifest['atoms'].append({
"name": fm['name'],
"path": str(target),
"trigger_regex": build_trigger(fm), # related_atoms + categoryのキーワード
"description": fm['description'],
"added": today(),
})
json.dump(manifest, open(JIT_MANIFEST, "w"), indent=2)
log_promotion(fm['name'])
trigger_regexが次のセッションでユーザー入力とマッチすると、この決定が自動的に注入されます。上の例なら「글로벌 쿨다운(グローバルクールダウン)」と入力すると、D1の決定とその根拠が一緒に入ってきます。手で運んでいた決定が、必要な瞬間にひとりでに浮かび上がる資産になる地点です。
著者のプロジェクトA運用経験から、標準書式だけを整えていた段階と、パイプラインを稼働させた段階を比較した印象です。以下の数値は精密な計測ではなく、運用中に体感した方向性とおおよその比率で、著者の推定(未検証)が混ざっています。
| 項目 | 書式のみ(手動抽出) | パイプライン稼働 |
|---|---|---|
| 議事録 → 決定抽出の時間 | 会議1件あたり20〜30分 | 1分未満 |
| 決定のatom昇格率 | 5〜10%(整理の時間が不足) | 60〜80%(全数レビュー) |
| 「前に決めてなかったっけ?」の再会議 | 四半期あたり5〜10件 | 四半期あたり0〜2件 |
| 持ち主不明の決定の発生 | 追跡できず | [MISSING]報告で即時に可視化 |
いちばん大きく変わったのは昇格率です。手作業で整理していたころは時間がなく、決定の90%以上が揮発していました。自動化すると全数レビューが可能になり、価値のある決定が漏れなく残ります。方向性は明らかです。比率の正確な値は、チームの規模と会議の頻度によって変わります。
| パターン | 処方箋 |
|---|---|
| lintを推奨どまりで運用する | コミットフックで強制する |
| 決定スロットに議論まで書く | 決定は一文、根拠は別フィールド |
| ownerの空欄をそのまま通す | [MISSING]報告 + pending保留で差し戻す |
| pendingレビューが先送りされる | 週次の振り返りに固定スロット、5分でも毎週 |
| 破棄履歴を残さない | 破棄も別のatomとして保存する |
この5行がほぼすべてです。人が意志の力で守らなければならない場所を最大限減らし、書式とownerのチェックを機械に任せるのが、このシステムの安定点です。
ゲーム外への応用。 議事録1件を、書式チェック→決定抽出→1週間の検証→正式登録というコンベヤーに流し、人は入口(作成)と出口(週1回のレビュー)の2か所にだけ手を触れるこの構造は、ゲームに限らず、どんな知識労働チームの文書運営にも移植できます。たとえばコンサルティングチームが顧客ミーティングのノートを扱うとき、ノートの書式だけ統一しておき、「決定・担当・根拠・次の行動」のスロットをLLMで一次抽出し、担当が空なら
[MISSING]を立てて差し戻し、1週間寝かせた決定だけを正式なアクショントラッカーへ昇格させればよいのです。手作業で整理すると90%以上揮発していたミーティングの決定が、コンベヤーに載せれば全数レビューの対象になり、漏れなく残ります。
setup. 議事録フォルダーに標準書式のテンプレートを置き、meeting_lint.pyをコミット前フックに掛けましょう。frontmatterの4フィールドとセクション4つを必須にします。
prompt. 議事録1件をパーサーに入れ、次のように指示しましょう。
この議事録の
## 決定セクションから、決定ごとにdecision / owner / rationale / follow_upの4フィールドをJSONで抽出してください。ownerが明示されていない決定は、ownerを[MISSING]と表記し、別途警告行にまとめてください。推測で埋めないでください。
verify. 出力されたJSONに[MISSING]が記入された決定があれば、その決定はpendingを作らず、議事録の作成者へ差し戻しましょう。ownerがすべて埋まった決定だけをpending候補ファイルとして生成し、1週間後の週次レビューで昇格・破棄・保留を決めます。
一人で作業しているなら、スクリプト3本とコミットフックまでは過剰です。議事録の## 決定セクションだけを標準化し、決定ごとに1行でD1: 何を / 持ち主: 自分 / 根拠: なぜと書きましょう。週1回、その週の議事録から決定の行だけをかき集めて1つのファイル(decisions.md)にまとめ、持ち主が空の行には自分で[MISSING]の印を残して翌週に埋めます。スクリプトは、後で手が回らなくなったときに足しても遅くありません。核心は「決定を1行・持ち主の明記・週1回まとめる」という3つの習慣です。
議事録は積み上げること自体が目的ではありません。6か月後にも検索でき、決定につながり、2台のPCで同じ状態に見えることが目的です。
火曜日の午後のことです。1年前の会議で、キャラクター衣装の彩度を1段階下げると確かに合意した記憶がありました。ところが、その議事録が見つからないのです。フォルダを開くと、meeting_0413.md、회의_수정본_final.md、IMG_2034.pngのようなファイルが200個、日付順にただ積まれているだけでした。カテゴリーもキャプションも、一貫した名前もありません。決定はどこかにあるのに、その決定にたどり着く道が消えてしまった状態でした。
議事録が資産になるには、3つの要素が同時に機能しなければなりません。分類が検索の一次入口を作り、キャプションが画像という半分を検索可能なまま生かしておき、同期が1,000件を超えても処理コストを変更分だけに留めます。この3つのうち1つでも欠けると、議事録は積み上がるほど重くなるだけの死んだ山になります。
§17.1・§17.2では、議事録を抽出パイプラインに変換する流れ — meeting_lint.pyでフォーマットを検査し、decision_parser.pyが決定の4フィールド(decision / owner / rationale / follow_up)を抜き出し、ownerがなければ[MISSING]として申告し、pending atomとして集めてからpromote.pyで昇格させる — を組み立てました。本章では、そのパイプラインが長期的に壊れないよう支える3つの運用標準を扱います。
議事録は時間が経つと数百件、数千件になります。検索できない資料は資産ではありません。カテゴリーはその検索の最初の分かれ道です。オフィスのキャビネットにラベルを貼る作業と同じで、ラベルのないキャビネットは結局誰も開けません。
著者が運営するプロジェクトA(MMORPG開発)では、カテゴリーを5つにまとめました。要点は小さく直交に保つことです。
5つがすべてのチームの正解というわけではありません。非戦闘システムが中心のプロジェクトなら、battleをsystemに置き換えるといった調整が必要です。要点は数ではなく、分類の決定が会議そのものを止めない程度に小さく保つという原則です。
会議が2つの枠にまたがることはよくあります。キャラクターコンセプトをレビューするうちに戦闘モーションまで合意したなら、artでしょうか、battleでしょうか。原則は主要成果物を基準に1つだけです。コンセプトが主要成果物ならartに分類し、戦闘モーションはsub_topicフィールドで補助的に記録します。
---
type: meeting_note
category: art
sub_topic: [character, battle_motion]
date: 2026-05-18
attendees: [teammate_a, teammate_b, teammate_c, イ・ミンス]
related_atoms: [character_concept_kim, battle_motion_kim]
confidential: internal
---
sub_topicは検索の二次フィルターにすぎず、ルーティングの決定には使いません。ルーティングは常にcategoryの単一値だけで動作します。この単一値の原則が崩れると、§17.2のpromote.pyがatomをどのフォルダへ送るか分岐できなくなり、カテゴリー別統計の合計もずれます。直交性は見た目の問題ではなく、パイプラインの整合性の前提です。
5つの枠に分けた本当の理由は、検索ラベルのためではありません。枠ごとに運用方式が違うため、分離してこそ差をつけた運用が自然に設計できるからです。
artは添付画像が多いため、次節のキャプション標準が必須です。決定が視覚中心なので、決定スロットにのような画像参照が入ります。battleは決定が数値・ルールであるためatomの自動昇格率が最も高く、決定の1行がマスターデータの一括変更につながるため、影響範囲の可視化(第11部の関係図)が重要です。dailyは決定がほとんどないのが正常で、蓄積が速いため週単位の自動フォルダ(daily/2026-W21/)に分離します。issueは議事録が散漫になりがちなので、事後24時間以内の整備を義務とし、再発防止atomをissue_postmortem/へ抽出します。reviewは分量が長いため、5〜10行の要約atomを別途作成し、次の四半期振り返りで自動引用されるようにします。
新規カテゴリーの追加は、きわめて慎重に行います。四半期に5回以上発生し、運用方式が既存の5つと明確に違い、別途のルーティングフォルダが必要で、1か月後にも5回以上を維持している — この4条件をすべて通過して初めて検討します。著者の運用経験では5つのまま1年以上維持されており、tech_reviewやexternalのような候補が浮かんだときも、結局sub_topicに吸収されました。
カテゴリーは、人が作成時に直接入力するのが一次です。外部から受け取った資料のように欠落している議事録だけをAI分類器で補助します。キーワード辞書で90%ほどが拾え、残りのuncertainだけをLLMか人が判定します。
LLMに委任するときは、制約を強くかけるプロンプトが安定します。次は実際に使っているプロンプトの全文です。
次は議事録です。5つのカテゴリーのうち1つに分類してください。
カテゴリー:
- art: ビジュアル・アート方向
- battle: 戦闘システム・バランス
- daily: 定例の進捗共有
- issue: 緊急イシュー対応
- review: マイルストーン・QAレビュー
議事録:
[全文または最初の500字]
応答形式: カテゴリーの単語1つのみ。説明・根拠・不確実性の表明は一切禁止。
応答が5つのカテゴリーのいずれでもない場合、システム失敗とみなします。
同じ議事録(下記はart会議の冒頭)を入れたとき、Claudeの生の出力はこうでした。
入力議事録:
キャラクターK_007(学者)のコンセプトv3レビュー。衣装の色調の彩度が高すぎるという意見。1段階下げることで合意。次回会議で戦闘モーションのトーンも一緒に点検することに。
Claude出力:
art
きれいに1単語だけが返ってきました。ところが、同じプロンプトにdailyの議事録を入れると、こんなこともありました。
入力:
今日のビルドが未明に壊れた。原因はマスターデータのマージコンフリクトと見られる。まずホットフィックス、その後正式修正の予定。
Claude出力:
issue
表面上はdailyのスタンドアップで出た発言ですが、Claudeは内容を見てissueに分類しました。これこそが、分類器を一次に使ってはいけない理由です。人は「これはデイリーの途中で突発したビルド事故だから、別途のissue会議に分離すべきだ」という運用判断をします。AIはテキストだけを見てラベルを貼ります。ラベルは合っていても、会議を分離するかどうかは決定できません。だから人が一次で、LLMは欠落分の補助にとどめます。
四半期振り返りでは、カテゴリー別の会議数を集計して「どこに時間を使っているか」を見ます。下記の分布は著者の推定(未検証)で、絶対件数は例示であり、比率の大小関係だけが実際の運用感覚と一致します。
| カテゴリー | 比重(推定) | 備考 |
|---|---|---|
daily |
約1/3 | 毎日の定例、決定はほぼなし |
battle |
約1/5 | 戦闘TFが週2回 |
art |
約1/7 | アートレビュー + 外部会議 |
issue |
低い | ビルド事故など |
review |
最も低い | マイルストーン・四半期振り返り |
| その他 | 約1/5 | 1on1、外部など非カテゴリー |
issueがある四半期に突出して多ければ、ビルド・CIの安定性改善が次の優先順位として浮上します。カテゴリーは検索のためだけでなく、組織の時間配分を映す鏡でもあります。
artの議事録は本文の半分が画像です。そしてキャプションのない画像は、机の上に積まれた写真の山と同じです。その日は全部覚えていても、1か月後には裏面に1行メモを書いておいた写真だけが生き残ります。
flowchart LR
A["会議直後
参加者だけが理解"] --> B["1週間後
作成者も一部だけ記憶"]
B --> C["1か月後
どの決定に関連するか不明"]
C --> D["6か月後
事実上廃棄・検索不能"]
A -.キャプション1行.-> E["6か月後にも
決定IDで逆参照可能"]
style D fill:#fcd6d6,stroke:#d94a4a
style E fill:#d6fce0,stroke:#4ad97a
画像が議事録の半分なのに検索できなければ、議事録という資産の半分が消えたのと同じです。その半分を生かしておくのが、キャプションの1行です。
プロジェクトAのキャプション標準は3行で終わります。

**[図1]** キャラクターK_007(学者)コンセプトv3 — 衣装の色調を1段階彩度ダウン
*決定: D2(衣装彩度 -10%) | 次のアクション: v4作業(~MM-DD)*
3つの要素がそれぞれ別の検索経路を開きます。番号 + 1行説明は本文から「図1参照」として引用する道を、決定ID参照(D2)は「この決定に紐づく画像」という逆参照を、次のアクションは後続作業の手がかりを残します。3行とも1分以内に書けます。「即時添付」は「会議中に書く」という意味ではありません。会議中は決定の整理だけを行い、終わった直後の10分以内にキャプションを埋めるのが現実的です。
キャプションと同じくらい重要なのがファイル名です。フォルダとファイル名そのものが、検索の最初の入口だからです。
議事録フォルダ/
├── 2026-05-18_art_review.md
└── images/
└── 2026-05-18_art_review/
├── character_kim_concept_v3.png
├── env_palette_comparison.png
└── reference_external_game_a.png
規則は<主題>_<項目>_<バージョン or 備考>.<ext>で、韓国語(ハングル)・空白・特殊文字は禁止します(パスのエンコーディング事故防止)。IMG_2034.png(意味0)、김캐릭터 v3.png(韓国語・空白)、final_final_v3_real.png(バージョン無意味)、untitled.png(廃棄候補)は全部アンチパターンです。こうした名前は人の意志に頼らず、meeting_lint.pyに検査ルールを追加して強制するほうが良いです — §17.2でフォーマット検査を自動化したあのlintに、ファイル名検査を1行載せるだけで十分です。
会議では、外部のゲームやアートを参考として引用することがよくあります。出典がなければ著作権事故に直結します。

**[図3]** 参考画像 — refgame (Developer Y, 2024)
*引用理由: 似たコンセプトの彩度処理の比較。直接の借用なし。*
出典(ゲーム名・開発会社・年)・引用理由・直接借用の有無をすべて明記します。そして画像はテキストより流出リスクが大きいため、等級をfrontmatterに付けます。
confidential: internal # internal / restricted / external_ok
images:
- file: character_kim_concept_v3.png
confidential: restricted
reason: 未公開キャラクターデザイン
internalは社内共有、restrictedは該当TF・担当者のみ、external_okはマーケティング・外部共有の承認済みを意味します。議事録のビルド時に等級別へ出力を分離し、external_okでない画像は外部共有版で自動的にぼかし処理します。この自動分離が、外部共有のマスキング事故を事実上0にする直接の効果を生みます。
画像50枚にキャプション50個を手で書くのは負担です。AIに本文とファイル名を渡して、一括で下書きを受け取ります。
次は議事録の本文 + 画像ファイルの一覧です。
[議事録本文]
[画像ファイル名10個]
各画像についてcaptionの下書きを作成してください。
形式:
- [図N] <説明> — <核心となる決定または変化>
- *決定: D? | 次のアクション: ?*
本文に根拠が見つからない画像は「内容不明 — 作成者の確認が必要」と表示。
ここでは最後の行が核心です。同じ議事録を入れたとき、Claudeは本文に根拠のある画像にはキャプションを付けましたが、reference_external_game_a.pngにはこう答えました。
Claude出力(抜粋):
[図3] reference_external_game_a.png — 内容不明、作成者の確認が必要。本文にこの外部参考画像の引用理由が明記されていません。
AIが、わからないものをわからないと申告したのです。これを受けて作成者が引用理由を埋めます。本文のコンテキストだけで足りなければ、核心の5〜10枚だけを選んでVisionモデルに送ります(画像のトークンコストが大きいため、全部は回しません)。
# 核心の画像5〜10枚だけに選択適用 — 画像1枚あたりのトークンコストが大きい
response = client.messages.create(
model="claude-opus-4-8",
messages=[{
"role": "user",
"content": [
{"type": "image", "source": {"type": "base64", "data": img_b64}},
{"type": "text", "text": "この画像を日本語1行で説明。推測禁止、見えるものだけ。"},
],
}],
)
作成者はこの1行をキャプション形式に整えます。すべての画像にVisionを回す必要はありません。核心の5〜10枚だけでも検索可能性は十分に上がります。
キャプションがきちんと書かれた1年分の議事録は、それ自体がビジュアルデベロップメントドキュメントになります。character_kimのv1 → v2 → v3の視覚的変化を決定IDで追跡でき、external_ok等級だけをフィルタすれば外部報告資料が自動でキュレーションされ、分野別の核心画像 + キャプションを集めれば新規メンバーのオンボーディング資料になります。キャプション導入前後の変化を著者の推定(未検証)として表現すると、方向はこうです — 6か月前の議事録の検索成功率は大きく上がり、「この画像どこで見たっけ?」という再質問は大きく減り、外部共有のマスキング事故は0に収束します。絶対数値はチームごとに違うでしょうが、ステップ1・2(ファイル名標準 + キャプション様式)だけでもその方向ははっきり現れました。
議事録そのものはテキストファイルなので、gitで十分です。同期の本当の対象は、議事録から派生したデータのほうです — §17.2のpending atom候補、JIT manifest、カテゴリー統計、決定インデックス(decision_index.json)、キャプションインデックス、confidential等級別のビルド出力、そしてベクトル検索用のLLM埋め込み。これらのデータが、議事録の変更すべてに反応しなければなりません。
問題は、議事録が1,000件を超えると、毎回全体を再処理するコストが運用の半分を占めるようになることです。作業ライン全体を止めて、すべての部品を作り直すようなものです — 部品が1つしか変わっていないのに。
flowchart TB
subgraph Full["Full Sync · 100件未満で有効"]
F1["全議事録1,000件"] --> F2["すべての派生データを
ゼロから再生成"]
F2 --> F3["インデックス・埋め込みを全部入れ替え"]
end
subgraph Inc["Incremental · 200件を超えたら必須"]
I1["git diffで
変更N件だけを検知"] --> I2["該当N件の
派生データだけ再生成"]
I2 --> I3["インデックスを部分更新
追加・修正・削除で分岐"]
end
Full -.議事録200件突破.-> Inc
style Full fill:#fce7d6,stroke:#d98a4a
style Inc fill:#d6fce0,stroke:#4ad97a
Full Syncは実装が単純で状態不一致のリスクが0なので、導入初期(100件未満)にはむしろ安全です。Fullが悪い方式だという意味ではありません。ただ、議事録の数に線形に比例するコストが、200件を超えるあたりからボトルネックになります。そこでIncrementalに切り替えます。
Incrementalの最初のステップは、「どのファイルが変わったか」を正確に判定することです。ファイルのmtimeは速いものの、touchしただけでも変更として拾われ、正確度が低いです。ファイルハッシュは内容基準なので正確ですが、追加・削除の区別が弱いです。著者の推奨はgit diffベースです。最後のsync時点のコミットハッシュを記録しておき、それ以降に変更されたファイルだけを処理します。追加・修正・削除をすべて正確に拾いながら、別途の状態管理の負担が最も小さい方法です。
# incremental_sync.py の骨格
def get_changed_files(last_sync_commit):
result = subprocess.run(
["git", "diff", "--name-only", last_sync_commit, "HEAD", "--", "meetings/"],
capture_output=True, text=True
)
return result.stdout.strip().split("\n")
def sync():
last_commit = read_state("last_sync_commit")
for path in get_changed_files(last_commit):
if not os.path.exists(path):
handle_deletion(path) # atom・インデックス・埋め込みを一括削除
elif is_new(path, last_commit):
handle_creation(path) # lint → 決定抽出 → pending atom → インデックス → 埋め込み
else:
handle_modification(path) # 既存の派生を無効化して再処理
write_state("last_sync_commit", get_current_commit())
ここで、コストを最も大きく分けるもう1つの分岐があります。議事録が本文まで変わったのか、frontmatterだけが変わったのかです。
def detect_change_scope(file_path, last_commit):
diff = subprocess.run(
["git", "diff", last_commit, "HEAD", "--", file_path],
capture_output=True, text=True
).stdout
fm_lines, body_lines = split_diff_by_section(diff)
return {"frontmatter_changed": bool(fm_lines), "body_changed": bool(body_lines)}
scope = detect_change_scope(path, last_commit)
if scope["body_changed"]:
full_reprocess(path) # 埋め込みの再生成を含む
elif scope["frontmatter_changed"]:
metadata_only_update(path) # 埋め込みの再生成0
categoryやconfidentialのようなメタ情報だけが変わったのなら、LLM埋め込みを作り直す必要はありません。埋め込みは通常、同期コストの中で最大の塊なので、この1回の分岐がコストを大きく減らします。埋め込みはcontent_hash基準でキャッシュします — 本文ハッシュが同じならキャッシュ済みの埋め込みをそのまま再利用し、frontmatterだけの修正では埋め込み呼び出しが0になります。
コスト差の方向は明確です(下記は著者の推定で、絶対値ではありません)。週あたり50件ほど変更される運用では、毎週のFull re-embedに比べて、Incrementalの埋め込みコストは数十分の一の水準まで下がりました。議事録が増えるほどFullのコストは蓄積に比例して大きくなる一方、Incrementalのコストは週あたりの変更件数だけに縛られ、蓄積と無関係にほぼ平坦でした。この「蓄積と無関係」という性質がIncrementalの本質的な価値です。
Incrementalは速い代わりに、累積する不一致のリスクを抱えています。小さなバグでatomが1件漏れると、その漏れは次のIncrementalで勝手に直りません。そこでガードレールを挟みます — 毎日Incremental、毎週直近1週間分のPartial Full(検証)、毎月全体のFull re-syncでインデックス・埋め込みの一貫性を点検します。毎月の点検で不一致が見つかれば、変更検知ロジックを補強します。この毎月1回が、長期運用の最後のセーフティネットです。
ここにPC分離運用がもう一段重なります。著者は会社PCと自宅PCの2か所で議事録を扱います。原則は、sync作業は1台のPCだけで行うことです。
| 流れ | 処理 |
|---|---|
| 会社PC → git push | 会社PCがsync作業(派生データ再生成)を担当 |
| 自宅PC → git pull | last_sync_commitだけ更新、再処理は不要 |
| 両方で変更後にmerge | mergeの結果を基準にchangedファイルを再算定 |
両方で同時にsyncするとlast_sync_commitの状態が衝突し、その衝突は静かにインデックスをずらします。1台のPCをsync主体として固定するという単純なルールが、最も確実な防御です。
分類・キャプション・同期は、ばらばらに動く標準ではありません。3つは§17.2の抽出パイプラインの上で1つの流れにまとまります。
議事録が作成されると、categoryがpromote.pyのルーティングを決め、キャプションの決定IDがdecision_parser.pyの抜き出した決定4フィールドとつながり、そうして作られたすべての派生データを、Incremental syncが変更分だけ選んで更新します。本章の運用の出発点はdecision_summary_not_clickup_mirror(§17.1.2)です。分類が決定を見つける道を開き、キャプションが決定の視覚的証拠を残し、同期がその決定資産を2台のPCで同じ状態に保ちます。
火曜日の午後のあの途方に暮れた状態 — 確かに合意したのにたどり着く道がなかったあの状態 — は、この3つの軸が機能した瞬間に消えます。category: artでフォルダが絞られ、キャプションの決定: D2で正確な決定にたどり着き、同期がその議事録を自宅でも同じ姿で見せてくれます。
ゲーム外への応用。 資料は検索・参照・同期されて初めて資産になるという原則は、ゲームの議事録だけの話ではなく、文書を扱うすべての社会人に共通する課題です。分類(小さく直交したカテゴリー)・キャプション(添付画像への1行説明)・同期(全体ではなく変更分だけ)という3つの軸は、ドメインを差し替えてもそのままです。たとえば営業チームが1年分の顧客ミーティング資料を蓄積するなら、カテゴリーを「新規提案・契約交渉・アフターサポート」など5枠以下に固定し、添付した見積書のキャプチャごとに「[図1] A社2次見積もり — 単価5%引き下げ」のような1行を付け、クラウド同期は変わったファイルだけを選んで処理すればよいのです。そうして初めて、半年後に「あのとき単価をなぜ下げたんだっけ」をキャプション1行で即座に見つけられます。
setup
1. 会議カテゴリーを5つ以下で定義しましょう(art / battle / daily / issue / reviewを出発点に、チームに合わせて1〜2個を入れ替え)。
2. meeting_lint.pyに2つの検査を追加しましょう — categoryが定義済みの値のいずれかであるか、画像ファイル名が<主題>_<項目>_<バージョン>パターン(韓国語・空白なし)であるか。
3. frontmatterにconfidentialフィールドを用意し、last_sync_commitを記録する状態ファイルを準備しましょう。
prompt(欠落した議事録の分類補助)
次は議事録です。5つのカテゴリーのうち1つに分類してください。
[カテゴリー定義5行] / [議事録の最初の500字]
応答形式: カテゴリーの単語1つのみ。説明・根拠・不確実性の表明は一切禁止。
応答が5つのカテゴリーのいずれでもない場合、システム失敗とみなします。
verify
1. 任意の6か月前の議事録を、カテゴリー + キャプションの決定IDだけで見つけられるか、実際に検索してみましょう。
2. git diff --name-only <last_sync_commit> HEADで拾われた変更ファイル数と、実際に修正した議事録の数が一致するか確認しましょう。
3. AIの分類結果を無批判に受け入れず、uncertainと「デイリー中に発生した決定」のケースは人がもう一度見ましょう。
一人で働くプランナーなら、こう縮小しましょう。
decision(決定のある会議)とlog(進行記録)。決定のある会議だけキャプション・atomを揃え、残りは日付フォルダに積んでおきます。decision_index.json(決定ID → 議事録パスのマッピング)というファイルを1つだけ置き、議事録の保存時にその行だけ更新しましょう。gitがそのまま同期であり、Full/Incrementalを区別するほど量が積もるまでは、毎回の全体再生成で十分です。一人の規模でも変わらない核心は1つです — 決定にたどり着く道を残すこと。分類・キャプション・同期はその道を支える3本の柱にすぎず、規模に合わせていくらでも細く立ててかまいません。
マイルストーンデモを3日後に控えた昼休み、プランナーの一人が食堂のトレイを置きながら尋ねます。「クエスト報酬のゴールドを1.5倍に上げることにした件、あれは会議で決めたものですよね?マスターデータに入れてもいいですか?」隣の席が答えます。「あれは誰かが、とりあえず試してみたらどうかと言っただけじゃなかったかな」。90分の録音ファイルと、誰かがキーボードで打ち込んだ2ページのメモは確かにあります。しかしその記録は「何を話したか」は収めていても、「何を決定し、誰が責任を持ち、なぜそうしたのか」は収めていませんでした。
社内のR&D文書17件を痛みの大きい順に並べたとき、最も大きな割合を占めたのは議事録改善計画書でした。意外でした。戦闘バランスでも、コンテンツ量産パイプラインでもありませんでした。いちばん痛いところは、会議で下した決定が実行へと伝わらないこと、そのただ一つだったのです。
そこで議事録システムを決定追跡データベースとして設計し直し、その流れのどこにAIを入れ、どこに入れてはいけないのかを、6か月間自ら運用しながら検証しました。本章はその5つのポイントの地図です。
録音ファイルから意思決定グラフの更新まで、議事録パイプライン全体でAIアシスタントを置ける場所はちょうど5つです。5つすべてに同時に入れることはしません。場所ごとに成熟度と事故リスクが異なるからです。
flowchart TD
A[会議の録音音声] -->|"ポイント1
STT"| B[元のテキスト]
B -->|"ポイント2
ドラフト生成"| C[議事録ドラフト]
C -->|"meeting_lint.py"| D[標準様式の議事録]
D -->|"ポイント3
決定スロット補強"| E[決定4フィールド完成]
E -->|"decision_parser.py
ポイント4 ルーティング"| F[pending atom]
F -->|"promote.py
ポイント5 関係抽出"| G[正式atom + グラフ]
style C fill:#ffd9d9,stroke:#c0392b
style E fill:#d9f0ff,stroke:#2980b9
style F fill:#d9f0ff,stroke:#2980b9
赤(ポイント2)が最も魅力的でありながら最も危険な場所、青(ポイント3・4)が最初に導入した安全な場所です。パイプライン中間のmeeting_lint.py → decision_parser.py → promote.pyはAIではなく決定論的なスクリプトです。AIはこの決定論的な骨格のあいだにある「判断が必要な隙間」にだけ入ります。
各ポイントの性格を一行で要約するとこうなります。
AI自動化を語る前に、AIではないスクリプトの骨格を先に見る必要があります。議事録が決定データベースになる核心は、LLMではなく3つの小さなPythonスクリプトにあるからです。
標準様式の議事録は、最後に決定ブロックを持ちます。ブロックの各決定は4つのフィールドを強制します。
## Decisions
D1:
decision: 戦闘のグローバルクールダウンを0.5秒に統一する
owner: teammate_a
rationale: スキル連携テストで0.3秒は入力抜けが頻発した(本文14:22)
follow_up: コンボ設計シートにGCD 0.5を反映、6/13まで
このブロックをdecision_parser.pyが読みます。核心の動作は単純です — 4フィールドのうち一つでも空であれば[MISSING]を出力して報告します。とくにownerがなければ、その決定は「誰も責任を持たない決定」、つまり実行されない決定なので、最も強くブロックします。
$ python decision_parser.py 2026-06-06_combat-sync.md
D1: OK (owner=teammate_a)
D2: [MISSING owner] 「回復スキルはGCD除外を検討」 — ownerなし、昇格をブロック
D3: [MISSING rationale] 根拠フィールドが空、警告
[MISSING owner]が付いたD2は、pendingフォルダにすら進めません。人がownerを埋めるまで、決定として扱われないのです。これが「会議はしたのに何も動かなかった」を構造的に防ぐ仕掛けです。
通過した決定はpromote.pyがpending atomにし、週1回のレビューゲートで人が承認すると正式なatomに昇格します。このとき適用される原則がdecision_summary_not_clickup_mirror atomです(§17.1.2)。タスクボードは「何をするか」を、決定データベースは「なぜそう決めたのか」を追跡します。二つを混ぜると両方が壊れます。
この3つのスクリプトが骨格であり、AIはこの骨格の空欄を埋めるアシスタントです。順序が逆になると — AIが骨格を作ると — ハルシネーション(幻覚)が決定データベースの信頼そのものを崩します。
ポイント2(STTテキスト → 議事録ドラフトの自動生成)は、どのチームも最初にやりたがる場所です。「録音さえ放り込めば議事録が出てくる」という絵があまりに魅力的だからです。そして、まさにその魅力のせいで、最も高くつく失敗をします。
失敗のパターンは4つです。
flowchart TD
R[STTテキスト → AIドラフト] --> R1["ハルシネーション決定
合意していないことを決定として記録"]
R --> R2["決定の取りこぼし
実際の決定を見逃す"]
R --> R3["話者の取り違え
提案者を入れ替える"]
R --> R4["トーンの平準化
反対意見・ニュアンスの消失"]
R1 --> X["決定追跡という
議事録の存在理由が崩壊"]
R2 --> X
R3 --> X
R4 --> X
style X fill:#ffd9d9,stroke:#c0392b
このうち最も致命的なのがハルシネーション決定です。会議で誰かが「グローバルクールダウンは0.5秒のほうがよくないですか?」と意見を投げただけなのに、AIドラフトが「グローバルクールダウン0.5秒で合意」と書いてしまいます。3週間後、この一行はマスターデータに反映され、その上にコンボ設計が積み上がり、QAケースが作成されます。合意されたことのない決定が、不可逆的に伝播するのです。
そこでポイント2には絶対原則を適用します。
ポイント2を「永遠にやらない」という意味ではありません。ポイント3・4・1が安定し、進行役がAI出力の限界を体で知った後であれば、ポイント2の導入価値は十分に大きいのです。ただ、順序が最後だということです。
ここが6か月の運用で最も大きな効果を上げた場所です。人が決定の存在を宣言し、AIがその決定の付属フィールドを埋めます。ポイント2と決定的に違うのは、決定があるという事実そのものを、人が先に確定させる点です。
人が手で埋めるには時間がかかりすぎる3つを、AIがドラフトします。
核心は、プロンプトが根拠の引用を強制し、ハルシネーションを明示的に禁止するところにあります。次に示すのは、実際に運用していた補強プロンプトの全文です。
プロンプト(人が入力):
以下の会議本文を見て、進行役が宣言した決定D1(「戦闘のグローバルクールダウンを0.5秒に統一」)の
owner / rationale / affected_atoms / follow_up の4つの欄だけを埋めてください。
決定を新しく作らないでください。rationaleは本文の発言をタイムスタンプごとそのまま引用し、
根拠のない欄は推測せず「根拠なし」と書いてください。
// (意図: affected_atomsは本文で言及されたシステム・シートのみ、最大3つ)
[会議本文]
(14:18) teammate_a: 今GCDが0.3秒なんですが、スキルを連打すると入力がよく抜けます。
(14:22) teammate_b: 昨日のコンボテストでも、0.3秒だと2つ目のスキルが出ないケースが
30%ほどありました。ログは残してあります。
(14:25) teammate_a: では0.5秒に上げましょう。私がコンボシートに反映します。
(14:26) 進行役: 0.5秒でいきます。決定。
(14:31) teammate_c: 回復スキルはGCDを適用すると、いざというとき使えないんですが、それは…
(14:33) 進行役: それは次回、別途見ましょう。
Claude出力(生の出力そのまま):
1. owner: teammate_a
根拠: (14:25)「私がコンボシートに反映します。」 — teammate_aが
本人の責任として明示。
2. rationale:
- (14:18) teammate_a:「GCD 0.3秒でスキル連打時に入力抜け」
- (14:22) teammate_b:「コンボテストで0.3秒は2つ目のスキル不発動
ケース約30%、ログ保有」
→ 0.3秒の入力抜け問題が、0.5秒決定の直接の根拠。
3. affected_atoms(本文言及ベースの候補):
- atoms/combat/global_cooldown
- コンボ設計シート(本文14:25「コンボシート」に言及)
- atoms/combat/skill_recovery ← ただし(14:31)で回復スキルの
GCD例外の議論は「次回に持ち越し」(14:33)。この決定の影響範囲に
含めるかは不確実。根拠弱い、人の確認が必要。
4. follow_up:
- teammate_a: コンボ設計シートにGCD 0.5秒を反映
- [別途議題] 回復スキルのGCD例外の可否は本決定の範囲外、
次回会議の議題として分離(14:33 進行役の発言)
人による検証/拒否:
進行役が出力をチェックしました。ownerとrationaleは引用が正確だったので、そのまま採択しました。affected_atomsの3つ目の候補skill_recoveryは、AI自身が「根拠弱い、人の確認が必要」と申告しており、進行役の判断でこの決定の影響範囲から除外しました — 回復スキルの例外は別の決定になるべき事案であって、今回のD1の影響ではないからです。follow_upの「別途議題として分離」という提案は採択し、次回会議の議題として登録しました。
ここで重要なのは、AIが不確実な項目をハルシネーションで押し切らず、自ら不確実性を申告した点です。「推測・ハルシネーション禁止、根拠がなければ『根拠なし』と明記」というプロンプトの制約が、この正直な出力を生みました。制約を外すと、AIはskill_recoveryを自信満々にaffected_atomsに入れ、そのハルシネーションがグラフに伝播します。
チェックを終えた決定ブロックはdecision_parser.pyを通過し — 4フィールドがすべて埋まっているので[MISSING]なしで — pending atomへと進みます。
ポイント3を通過したpending atomが正式なフォルダへ昇格するとき、どのフォルダに送るかをAIが推薦します(ポイント4)。
このatom(「戦闘のグローバルクールダウン0.5秒統一 / owner teammate_a」)を、以下のフォルダのうち
どこに入れるのがよいか、優先順位つきで最大3つ選んでください。新しいフォルダを作る提案はせず、
このリストの中だけで。
- atoms/combat/ atoms/character/ atoms/operations/ atoms/visual/
「新規フォルダ作成の禁止」が核心の制約です。これを外すと、AIはatoms/combat_timing/やatoms/gcd_rules/のようなフォルダを際限なく提案し、カテゴリが無限に増殖して検索と自動注入が崩壊します。カテゴリは小さく直交に保ち、1年以上変動なしで運用するのが原則です。AIはそのクローズドリストの中からだけ選びます。
ポイント5(atom間の関係抽出)は、最も遅く、最も慎重に導入します。昇格したatom同士の依存関係を推論する場所です。
新規atom A:「回復スキルはグローバルクールダウンの適用から除外」
既存atom B:「グローバルクールダウン0.5秒に統一」
推論された関係:
A.exception_of: [B]
A.derives_from: [B]
B.affects: [A] ← 逆方向を自動付与
問題は、この推論がLLMの非決定性に正面からさらされることです。同じ入力でも、昨日と今日で違う関係が出てきます。緩和装置は3つです — temperature=0と可能なモデルでのseed固定、候補を提示して人が承認するレビューゲート、そして一方向だけを抽出し逆方向はスクリプトが決定論的に補正する方式です。双方向の両方をLLMに任せると、片方が抜け落ちるからです。
5つのポイントを同時にオンにするのが、最もよくある、最も高くつく失敗です。運用負担が効果より先に到達し、チームがシステムを丸ごと捨ててしまいます。次に示すのは、実際にたどった順序です。
ポイント2を最も遅く置くことが、この順序の核心です。最もやりたい場所を最も後に回す — 直感には反しますが、最も危険な検査台には最も熟練した手が育ってから就かせるのが、作業場の安全原則です。
コスト面でもこの順序は合理的です。会議100件/月の基準で、ポイント3は約$5〜10、ポイント4は$1〜2程度なので(著者の運用環境基準の推定、未検証)、二つだけオンにしても月$10未満です。最も大きな効果を出す二つの場所が、最も安いのです。
同じ会議を二つの方式で記録したときの違いが、本章全体の要約です。
Before — 自由記述の議事録(AIなし、またはポイント2に決定スロットまで任せた場合):
## 2026-06-06 戦闘同期会議
GCD関連を議論。0.3秒は短すぎるという意見が出た。
コンボテストで問題があったらしい。0.5秒という話が出た。
回復スキルの例外も少し言及された。
おおむね0.5秒の方向でまとまる雰囲気だった。
3週間後にこの議事録を開き直しても、「0.5秒でまとまる雰囲気」が決定なのか意見なのか、誰がマスターデータに反映することになったのか、回復スキルの例外は決定されたのか持ち越されたのかを、誰も復元できません。話者もなく、ownerもなく、根拠も「本文のどこかにある」という程度なので、もう一度録音を聞き直すしかありません。
After — 決定スロット + ポイント3補強の議事録:
## 2026-06-06 戦闘同期会議
### 議題サマリー(AI補助)
- 戦闘グローバルクールダウン(GCD)0.3秒の入力抜け問題
- 回復スキルのGCD例外の可否(別途議題として分離)
### Decisions (人の宣言 + AI補強)
D1:
decision: 戦闘のグローバルクールダウンを0.5秒に統一する
owner: teammate_a
rationale: |
- (14:18) teammate_a: 0.3秒でスキル連打時に入力抜け
- (14:22) teammate_b: コンボテスト0.3秒で2つ目のスキル不発動 ~30%、ログ保有
follow_up: teammate_a — コンボ設計シートにGCD 0.5秒を反映(6/13まで)
affected_atoms: [atoms/combat/global_cooldown, コンボ設計シート]
### 分離された議題
- 回復スキルのGCD例外 → 次回会議(14:33 進行役の決定)
3週間後、この議事録はdecision_parser.pyが読み取ってグラフにつながっており、「なぜ0.5秒なのか」と問う誰に対しても、rationaleの2行の引用で即答できます。ownerが明示されているのでfollow_upが実行されたかどうかが追跡され、回復スキルの例外が決定ではなく持ち越された議題だという事実まで保存されます。
違いを生んだのはAIの分量ではなく、人が決定を宣言する場所を保ったまま、AIには根拠埋めだけを任せた構造です。上のAfter議事録でAIが埋めた段落(議題サマリー、rationaleの引用、affected_atomsの候補)をすべて消すと、残るのは決定一行とownerだけです — 議事録の情報量の半分以上がAI補強から生まれましたが、その半分がすべて人のチェックを通過した根拠引用だという点が核心です。
ゲーム外への応用。 「決定の存在は人が宣言し、AIは根拠・責任者・影響だけを埋める」という原則は、ゲームに限らず、録音をAIで整理するすべての働く人にそのまま適用される安全線です。最も魅力的な場所(録音を丸ごと議事録に自動生成)が最も危険な理由は、AIが「0.5秒のほうがよくないですか」という意見を「0.5秒で合意」という決定にすり替えるハルシネーションにあります。たとえば人事チームが評価会議の録音を整理するときは、「B等級で確定する」という決定だけは進行役が直接確定し、AIには「この等級の根拠となる発言を録音から引用して、なければないと言って」だけを任せましょう。決定をAIに作らせると、合意されたことのない評価が人事記録に不可逆的に残ります。
setup
1. 議事録の標準様式に## Decisionsブロックを作り、各決定にdecision / owner / rationale / follow_upの4フィールドを強制しましょう。
2. decision_parser.pyを書きましょう — 4フィールドのうち一つでも空なら[MISSING <フィールド>]を出力し、とくにownerが空なら昇格をブロックします。
3. 決定サマリーはタスクボードの鏡ではなく「なぜ」を収める独立した資産だというルール(decision_summary_not_clickup_mirror)を明文化しましょう。
prompt 4. ポイント3の補強プロンプトを使いましょう。必ず含める制約は「決定を新しく作らない / 本文の根拠をタイムスタンプつきで引用 / 根拠がなければ『根拠なし』と明記 / 推測・ハルシネーション禁止」。rationale・owner・affected_atoms・follow_upの4スロットを要求します。 5. ポイント4のルーティングプロンプトには「リストにない新規フォルダの作成禁止 + クローズドなフォルダリスト」を入れましょう。
verify
6. 補強された決定ブロックをdecision_parser.pyにかけ、[MISSING]がないことを確認しましょう。
7. AIが埋めたaffected_atomsのうち「根拠弱い」と申告された項目は、人が直接チェックして除外/採択しましょう。自動コミットはいかなる場合も行いません。
一人ミニ版
一人で作業していたり、ツールを入れる時間がなければ、スクリプトなしで議事録の最後に手書きの決定ブロック4行(決定 / 担当 / 根拠 / 次のアクション)だけを書きましょう。ownerが自分自身でも名前を書きます。AIには「この決定の根拠を会議メモから引用して、なければないと言って」とだけ指示しましょう。パイプラインがなくても、決定を宣言する場所と、根拠引用を強制するプロンプトの二つだけで、議事録は決定データベースになり始めます。
四半期会議の真っ只中でした。バトルプランナーが「グローバルクールダウン(GCD)を0.5秒に統一しよう」と提案し、全員がうなずきました。ところが、隣の席のシニアが手を挙げたのです。「これ、昨年の第4四半期に0.3秒で決めたものと衝突しませんか? あのとき、なぜ0.3秒にしたんでしたっけ?」会議室が一瞬、静まり返りました。誰もその決定の根拠を覚えていませんでした。議事録を漁りましたが、「戦闘TFで議論した」の一行だけ。結局、昨年の決定を再構成するのに30分を費やし、それでも「なぜ0.3だったのか」は最後まで見つかりませんでした。
決定は、下すことより追跡するほうが難しいものです。1年で数百件も積み重なると、どの決定が生きていてどれが廃止されたのか、どの決定が別の決定を前提に敷かれているのか、人間の頭では追いきれません。本章では、決定をatomとして固定化し、追跡可能な資産に変えるシステムを扱います。核心はシンプルです。決定1件をdecision_id・owner・rationaleを備えたカードとして記録し、カード同士をwikilinkでつないでグラフを作り、影響がどこまで波及するのかをgrepで逆追跡します。
決定追跡の最小単位は、決定カードです。著者が運営するプロジェクトA(MMORPG開発)で実際に使っているカードを、1枚そのまま持ってきました。冒頭の会議で衝突した、まさにあの0.5秒統一の決定です。
---
decision_id: D2026_Q2_017
title: 戦闘グローバルクールダウン0.5秒統一
type: system_change
status: active # active / superseded / deprecated
created: 2026-04-18
owner: teammate_a # バトルプランナー、決定の発議・所有者
approved_by: イ・ミンス # Design Director
approval_meeting: 95_BattleTF_2026-04-18
scope:
- combat_system
- all_active_skills
content: |
すべての戦闘アクティブスキルにグローバルクールダウン0.5秒を適用。
回復スキルは例外(別途決定D2026_Q2_018)。
rationale:
- コンボ入力の視認性問題(ユーザーフィードバックの累積)
- シミュレーション上、戦闘の平均時間が増える方向
- 新規ユーザーの学習曲線の緩和
affected_atoms:
- combat_global_cooldown_constant
- combat_skill_cooldown_rule
affected_files:
- CombatBalance.xlsx
- CombatFormula_v3.md
- UI/skill_cooldown_indicator
implementation:
target_build: 2026-05-09
impl_owner: teammate_b # コードリード
qa_owner: teammate_c # QAシニア
related_decisions:
- supersedes: D2025_Q4_034 # 旧0.3秒決定
- relates_to: D2026_Q2_018 # 回復の例外
---
背骨となるのは3つの項目です。decision_idは決定に恒久的なアドレスを与えます。ownerは「誰がこの決定に責任を持つのか」を確定させます。rationaleは、6か月後の「なぜああしたんだっけ?」に答えます。会議で見つからなかったあの「なぜ0.3だったのか」こそ、本来D2025_Q4_034のrationale欄にあるべきだった内容です。残りの項目(scope・affected_atoms・related_decisions)は、影響追跡とグラフ接続のための配線です。
ここで1つ、設計上の判断が入ります。12項目をすべて強制すると、人はカードを書くこと自体を避けるようになります。そこで、必須5項目(decision_id・title・owner・status・rationale)と任意7項目に分けます。会議での決定直後に5項目だけ埋めてもカードは生きており、残りは実装段階で埋めれば済みます。
カード1枚が発生から廃止までどんな経路を回るのかが、追跡システムの骨格です。不可逆ゲートがどこにあるかに注目してください。
flowchart TD
A[会議・社内メッセンジャーで決定が発生] --> B[決定カードの草案作成
必須5項目]
B --> C[decision_id付与・インデックス登録]
C --> D{影響範囲分析
impact}
D --> E[affected_atoms・affected_filesを記入]
E --> F[wikilinkでグラフ接続]
F --> G{owner・approved_byレビューゲート}
G -->|差し戻し| B
G -->|承認| H[ビルド反映]
H -.不可逆.-> I[他の文書・決定へ伝播]
I -.不可逆.-> J[事後測定・検証]
J --> K{進化の判断}
K -->|代替済み| L[status: superseded
supersedesリンク]
K -->|有効| M[status: activeを維持]
style H fill:#ffe0e0
style I fill:#ffe0e0
草案作成(B)からレビューゲート(G)までは、すべて可逆の段階です。カードを直すにも廃棄するにも、コストはほとんどかかりません。しかし、ビルド反映(H)以降は実質的に不可逆です。ユーザーがすでに体感した変更は、ホットフィックスで巻き戻してもコミュニティの認識に痕跡を残しますし、後続の決定がこの決定を前提に積み重なり始めると、巻き戻しのコストは指数関数的に膨らみます。だからこそ、決定者によるすべてのレビューはゲートGで終わっていなければなりません。これは第5部で扱った「収録・キャスティングは不可逆の段階」という原則と、まったく同じ構造です。
カードをatomにすると、カード同士をつなげられます。related_decisionsのsupersedes・relates_toが、グラフのエッジになります。冒頭の会議での衝突は、実はこのグラフの一片だったのです。
このグラフがあれば、会議は30秒で終わっていたはずです。D2026_Q2_017を開けばsupersedes: D2025_Q4_034が見え、そのカードのrationaleをワンクリックすれば「なぜ0.3だったのか」がそのまま出てきます。グラフは決定の進化の履歴であり、決定の進化の履歴は、そのままゲームの歴史です。PvP変種(D2026_Q2_025)のように、本決定から派生した分岐まで一目で追跡できます。
決定カードのaffected_atoms・affected_filesを人が逐一埋めると、漏れが出ます。プロジェクトAにはimpactという影響範囲抽出の手順があります。決定atomを受け取り、3つの方向でグラフを走査します。
affectsリンク:明示的に「影響を与える」と宣言された関係[[combat_global_cooldown_constant]]を本文で引用しているすべての文書3つの経路の和集合が、決定の本当の影響範囲です。さらにportal_layer_change_impact_check atomが、「ポータルレイヤー(外部公開文書・APIスペック)に触れるか」を別途検査します。ポータルレイヤーに掛かるとグレードが1段階上がります。外部への伝播は、巻き戻しがいっそう高くつくからです。
理論はここまでです。実際にLLMに議事録の塊を1つ投げて決定カードを受け取る全過程を、プロンプト全文と生の出力まで、そのまま載せます。要約はしません。Claudeが混乱した箇所、人が拒否した箇所、再依頼まで、すべて見えます。
以下の議事録を決定カードのYAMLに変換して。スキーマは
decision_id / title / type / status / owner / approved_by / scope /
content / rationale / affected_atoms / affected_files / related_decisions。
ownerは発議者、approved_byは承認者。affected_atomsとrelated_decisionsは
議事録に実際に出てきたものだけを書き、推測はしないこと。
// (意図: decision_idはD{YYYY}_Q{n}_{seq}、seqが不明ならXXX)
[議事録]
- 戦闘のグローバルクールダウンを0.5秒に統一することにした(teammate_a発議)。
現在はスキルごとにバラバラで、コンボの視認性が悪いというフィードバックが累積。
- 昨年0.3秒にしたものは、速すぎてコンボが潰れるという指摘。
- 回復スキルは0.5秒を適用すると緊急時の対応ができない。例外処理が必要。
- ディレクター(イ・ミンス)承認。teammate_bが実装、teammate_cがQA。
- target buildは5月9日。
---
decision_id: D2026_Q2_XXX
title: 戦闘グローバルクールダウン0.5秒統一
type: system_change
status: active
owner: teammate_a
approved_by: イ・ミンス
approval_meeting: 95_BattleTF
scope:
- combat_system
- all_skills # ← 回復スキルの例外があるのにall_skillsにしている
content: |
すべての戦闘スキルにグローバルクールダウン0.5秒を適用。
rationale:
- コンボ視認性のフィードバック累積
- 昨年の0.3秒は速すぎてコンボが潰れる
affected_atoms:
- combat_global_cooldown # ← 実際のatom名と異なる。推測したもの
affected_files: [] # ← 空のまま。議事録に明記なし
implementation:
target_build: 2026-05-09
impl_owner: teammate_b
qa_owner: teammate_c
related_decisions:
- supersedes: D2025_Q4_XXX # 昨年の0.3秒決定。seq不明
- relates_to: 回復スキル例外の決定(ID未定) # ← リンクではなく記述で埋めている
---
正直に見れば、この出力は60点です。良い点と間違いが混ざっています。
owner/approved_byの区別が正確、target_build・実装者・QAが正確、supersedes関係を捕捉。scopeにall_skillsを入れました。回復スキルの例外が明記されているのに、全体として捉えています。affected_atomsにcombat_global_cooldownを推測で入れました。プロジェクトの実際のatom名はcombat_global_cooldown_constantです。「推測するな」という指示に違反しています。related_decisionsの回復例外を、リンクではなく自然言語の記述で埋めました。affected_files: []と空にしました。指示には忠実ですが、影響ファイルがないはずがありません。著者はこのカードをそのまま受け取りませんでした。①②③を拒否理由として書き、再依頼しました。拒否の核心はatom名の推測です。atom IDが1文字でも違えば、後に続くgrep・逆参照がすべて外れます。追跡システムの正確さは、atom IDの正確さに縛られています。
3か所直して。scopeは回復スキルが例外だからall_skillsではなく
all_active_skillsにして、回復の例外はcontentに書いて。affected_atomsは
推測するなと言ったのに推測している — atomの実名が分からなければ[要確認]として、
何を確認すべきかコメントだけ付けて。インデックスはこちらで見る。
related_decisionsは回復の例外をD2026_Q2_018へのrelates_toに、supersedesは
D2025_Q4_034に。affected_filesも[要確認]の候補だけにして。
affected_atoms:
# [要確認] 以下は候補。インデックスで正確なatom IDの確定が必要。
- combat_global_cooldown_constant # グローバルクールダウンの定数値
- combat_skill_cooldown_rule # スキル別クールタイム規則(例外処理を含む)
affected_files:
# [要確認] 議事録にはない。type=system_change基準の通常の影響候補:
- CombatBalance.xlsx # クールタイム数値シート
- CombatFormula_v3.md # 戦闘計算式ドキュメント
- UI/skill_cooldown_indicator # クールタイムUIインジケーター
related_decisions:
- supersedes: D2025_Q4_034
- relates_to: D2026_Q2_018
scope:
- combat_system
- all_active_skills
content: |
すべての戦闘アクティブスキルにグローバルクールダウン0.5秒を適用。
回復スキルは例外(別途決定D2026_Q2_018)。
2次出力は正直になりました。atomを推測して断定する代わりに、[要確認]フラグを付け、根拠コメントを添えています。著者はatomインデックスを開き、combat_global_cooldown_constant・combat_skill_cooldown_ruleの2つの名前が実在することを確認して、フラグを外しました。affected_filesの3つの候補もインデックスと照合したうえで確定しました。本章の冒頭に載せた最終カードが、その成果物です。
このトランスクリプトの教訓は1つです。LLMは決定カードの草案作成者として強力ですが、atom IDと決定IDの最終確定は、人がインデックスと照合しなければなりません。 AIは候補を探索し、人が採択します。2つの役割が混ざると、間違ったatom名がグラフ全体を汚染します。
カードとグラフがatom IDで結ばれていれば、「この決定はどこに影響するのか」はgrep1行で答えが出ます。決定D2026_Q2_017の核心atomであるcombat_global_cooldown_constantを、原稿・シート・決定カードの全体から逆参照で走査します。
rg "combat_global_cooldown_constant" --type md --type yaml -l
# → D2026_Q2_017.yaml (決定カード本体)
# D2026_Q2_025.yaml (PvP変種 — この定数を再引用)
# CombatFormula_v3.md (計算式ドキュメント)
# 95_BattleTF_2026-04-18.md (議事録の原本)
この結果がそのまま、「この定数を変えると4か所が揺れる」という影響マップです。PvP変種のカードが同じ定数を引用しているという事実は、人の記憶では見落としがちですが、grepは見落としません。atom IDが正確だったからこそ可能なことです — 1次出力のcombat_global_cooldownでgrepしていたら、この4行は1つも引っかからなかったはずです。グレード分類(§18.2)と全サイクルワークフロー(§18.3)、grepワークフローの精緻化(§18.4)は、すべてこのatom IDの正確性の上に立っています。
著者のプロジェクトAで、追跡システムの導入前後を比較します。以下の数値は著者の推定(未検証)であり、絶対値より方向と比率で読むことをおすすめします。
| 項目 | システムなし | システム運用 | 方向 |
|---|---|---|---|
| 「前に決めていなかったか?」の再議論 | 四半期あたり8〜12件 | 四半期あたり0〜2件 | 大幅減少 |
| 決定の影響範囲の把握 | 1〜2日 | grepで数分 | 大幅短縮 |
| 決定の進化履歴の追跡 | シニアの記憶頼み | グラフで自動 | 人への依存を除去 |
| 新規メンバーの決定履歴の学習 | 1〜2か月 | 1〜2週間 | 最大の効果 |
最大の効果は最後の行です。新規メンバーが「なぜこのゲームはいま、この形なのか」をシニアをつかまえて尋ねる代わりに、決定グラフをたどって自分で読み下っていきます。決定追跡は、そのまま会社の意思決定の学習資産になります。ただし、システムを導入した最初の四半期は、カード作成の負担が確実にあります。必須5項目から定着させて漸進的に広げていく道が安全です。
ここまでの運用は、保守的な適用です。人が会議で決定し、カードを書き、影響atomを識別し、自動化はインデックス・検索・grep・グラフ可視化だけを受け持ちます。人が核心の判断を、自動化が保管と検索を担当する形です。
次の段階は、先のトランスクリプトが示した方向です。議事録の自然言語を入力に、LLMが決定カード12項目の草案を埋め、グラフをたどって影響atomの候補を探索し、グレードまで推薦します。人の手に残る仕事は、「AIが埋めたカードとatom名がインデックスと合っているかのレビュー」と「最終承認」の2つに絞られます。0から12項目を埋める負担と、LLM草案のatom名をインデックスで確認する負担とでは、質が違います。
この進歩的な適用が根付くには、3つの骨格が必要です。第一に、すべての決定がatomとして登録され、wikilinkでつながった決定グラフ。議事録の塊は自動化の入力になりません — 決定単位に分解されている必要があります。第二に、グラフ上で影響分野の数・巻き戻しコスト・ユーザー影響範囲を計算してグレードを推薦するインパクトグレード自動判定器(§18.2)。第三に、atom IDとwikilinkで精密に動作するgrep・LLM影響追跡(§18.4)。
ここで、本書全体を貫くメッセージがもう一度現れます。決定をatom・グラフ・グレードに分解する作業は、「検索と逆参照の利便性」が表面であり、本質は、分解されていない議事録の塊からは、自動影響分析には何が決定の単位なのかすら分からないという点にあります。分解は協業言語の統一を表面の目的とし、プロシージャルな自動化の前提を本質の目的とするという一般論(§6.6)が、意思決定の領域で決定グラフ・atom・グレードとして現れたものです。第5部のワールドBT(BehaviorTree、ビヘイビアツリー)・クエストクラウド、第8部の進歩的バランシングと同じ骨格です。2010年代にも理論は可能でしたが、議事録を決定atomへ自動分解する作業が塞がっており、2023年以降、LLMがその分解の草案を受け持つようになって、紙の上にしかなかったビジョンのかなりの部分が実現の領域に入ってきました。
decision_id・owner・rationaleを備えたカードとして固定化してこそ、6か月後の「なぜああしたんだっけ」に答えられます。ゲーム外への応用。 決定カードはゲームに限らず、あらゆる組織の「なぜあのとき、ああ決めたんだっけ」に6か月後でも答えられるようにする装置です。マーケティングチームが「先四半期にこのチャネルは畳むと決めたはずだが、なぜだったか」を議事録の1行から見つけられず30分を浪費する事態は、
decision_id・owner・rationaleの3項目だけのカード1枚で消えます。たとえば人事チームが「在宅勤務は週2日に統一」といった方針を決めるとき、その1枚に発議者・承認者・根拠(生産性データ・従業員アンケート)と、置き換えた旧方針のIDを書いておけば、1年後の方針見直しの場でも、過去の判断根拠がそのまま生きています。
Webチャットボット最小経路(ターミナルなし) — 本章の核心は、決定カードのディレクトリやgrepではなく、「決定に恒久アドレス(decision_id)・責任者(owner)・根拠(rationale)を固定化し、新しい決定の前に過去の決定をまず探す」という発想です。この発想は、CLIやatomインデックスがなくても、Webチャットボット(ChatGPTまたはClaudeのWeb版)だけで再現できます。以下の3ステップが本流です。
1. 決定1件を1行で書きます。decisions.mdという普通の文書1枚で十分です。YAMLもスクリプトも要りません。
- [D17] グローバルクールダウン0.5秒統一 (owner: 私, 根拠: コンボ視認性, 代替: D08)
2. 議事録をカードに変換するときは、Webチャットボットに以下を貼り付けます。1次プロンプトの4つの制約をそのまま移したものです。
以下の議事録の決定を表に変換して。項目は
decision_id / title / owner / rationale / 置き換えられた過去の決定。
ownerを特定できなければ[MISSING]、atom・ファイル名が分からなければ[要確認]として、
推測はしないこと。
// (意図: decision_idはD{年}_{連番}、連番が不明ならXXX)
[議事録本文]
3. 新しい決定をする前に、decisions.mdを文書内検索(Ctrl+F)でまず検索します — 「前に決めていなかったか」という問いの1つは、これで解決します。これがgrep逆追跡の手動版です。atomインデックス・YAMLカード・rgワークフローは、決定が数百件積み重なって1つの文書では検索が苦しくなったときに、はじめて導入すれば十分です。
setup(インフラ版 — 上の最小経路が手になじんだ後で)— 決定カードのディレクトリとインデックスファイルを作りましょう。
decisions/
D2026_Q2_017.yaml
_index.json # by_status / by_scope / by_quarterの集計
prompt — 議事録の決定案件をLLMに投げるときは、上の1次プロンプトの4つの制約を必ず含めましょう。特に「atom名を推測せず[要確認]としておくこと」を明示します。
verify — 出力されたカードのaffected_atoms項目をatomインデックスと照合し、実名を確認したうえでフラグを外しましょう。その後、核心atomでrg "<atom_id>" -lを回し、影響ファイルがカードのaffected_filesと一致するかをクロス検証します。
チームのインフラなしで一人で使うなら、YAMLカードは捨ててください。決定1件をMarkdownの1行で書きます。
- [D17] グローバルクールダウン0.5秒統一 (owner: 私, 根拠: コンボ視認性, 代替: D08の0.3秒)
decisions.mdファイル1つにこの1行を積み重ね、新しい決定をする前にrg "クールダウン" decisions.mdで過去の決定をまず検索しましょう。カードもグラフもツールもありませんが、「前に決めていなかったか」という問いの1つは解決します。追跡システムの90%は、この1行の習慣から始まります。
会議が終わり、議事録を整理していたときのことです。1行だけの決定が書かれていました。「グローバルクールタイム(GCD)を0.5秒に統一」。会議では30秒もかからずに合意されました。全員がうなずき、次の議題に移りました。
その1行が、続く2か月を食いつぶしました。戦闘データのスキル277件がすべて影響を受け、UIのクールタイムゲージ演出は描き直しになり、バランス調整シートは2回作り直されました。同じ議事録に書かれていたもう一つの決定「チュートリアル案内文の誤字修正」は、5分で終わりました。
2つの決定は、議事録の上では同じく1行でした。文字数も似たり寄ったりでした。それなのに、一方は5分、もう一方は2か月でした。この差を、議事録を書くその瞬間に見えるようにすること — それがインパクト等級分類です。等級が見えなければ、2か月かかる決定が5分で済む決定と同じ行に埋もれてしまいます。
本章では、決定の波及を5つの等級に自動分類し、その波及がどこまで広がるのかを決定atomグラフの上で追跡する方法を扱います。道具は、前章で積み上げた決定atomとimpact抽出、そしてportal_layer_change_impact_check atomです。
まず、等級分類がない状態がどんな姿なのかを押さえます。決定がすべて同じ行に並ぶと、2種類の事故が交互に起こります。
一つは過小処理です。グローバルクールタイムの決定のように四半期を揺るがす決定が「5分で済むもの」として扱われ、検証なしでビルドに入ります。2か月後になってようやく波及が表面化し、そのときには巻き戻しのコストがすでに山のように積み上がっています。
もう一つは過剰処理です。誤字を1つ直すのにTFを招集し、ゲームディレクターの決裁を取ります。決定サイクルが急増し、本来ディレクターが見るべきT0の決定に使う時間が、誤字の会議に吸い込まれていきます。
2つの事故は正反対に見えますが、根は同じです。決定の重さが見えていないのです。重さが見えないから、軽いものに力を注ぎ、重いものを素通りさせてしまいます。等級分類は決定に重さのラベルを貼る作業であり、ラベルが貼られた瞬間に処理方式が自動的に枝分かれします。
著者が運営するMMORPG開発会社のプロジェクトAでは、決定のインパクトを5つの等級に分けています。上に行くほど重く、処理により多くの人と時間がかかります。
| 等級 | 定義 | 例 | 決定者 | サイクル |
|---|---|---|---|---|
| T0 | ゲームビジョン・コアシステム | モバイル優先の決定、コアメカニクスの変更 | ゲームディレクター + CEO | 四半期 |
| T1 | システム・複数分野 | グローバルクールタイムの統一、新規職業の追加 | TF議長 + ディレクター | 1〜2週間 |
| T2 | 分野・中規模 | 特定スキルの数値調整、UIコンポーネントの追加 | 分野ディレクター | 3〜5日 |
| T3 | 単発・小規模 | 単一NPCのセリフ修正、色の微調整 | シニア1名 | 1〜2日 |
| T4 | 即時・ホットフィックス | バグ修正、テキストの誤字 | 担当者 | 時間単位 |
表だけを見れば教科書のようにきれいです。しかし実務での難所は、表を覚えることではなく、目の前の決定1件をどのマスに入れるかを判断することです。「グローバルクールタイムの統一」がT1だということを、会議が終わった後ではなく、議事録を書くその瞬間に分かっていなければなりません。だからこそ、次節の3基準が核心になります。
等級は勘で決めません。3つの基準を評価し、そのうち最も高い等級を採用します。
3基準のうち影響分野数は、決定atomグラフから機械的に数えられます。決定が触れるatomがどの分野(戦闘・UI・データ・ナラティブなど)に属するかというタグを集めれば終わりです。
問題は残りの2つです。巻き戻しコストとユーザー影響範囲は、グラフ上の数字には換算できません。「この決定を2か月後に巻き戻すにはどれだけかかるか」は自然言語の判断です。まさにこの地点が、2023年以前のインパクト自動分類における最後の壁でした。影響分野数は自動化されましたが、自然言語判断の2マスが空いたままで、結局は人が最初から付け直していました。LLMが決定atomの本文を読んでこの2マスの草案を埋めるようになり、壁は低くなりました。
ここで正直に言っておきます。LLMが埋めるのは草案であって、確定ではありません。巻き戻しコストをLLMが「大」と推定しても、分野ディレクターが「うちのシート構造ならこれは普通」と判断を下すことがあります。自動分類は人の判断を置き換えるものではなく、人が空欄から始めなくて済むようにするものです。
前章で作った決定atom 1件をそのままLLMに入れ、等級を付けさせた実際の過程です。要約せずに全過程を載せます。拒否と再依頼まで含めます。
# decisions/D2026_Q2_017.md (前章18.1で登録したatom)
id: D2026_Q2_017
title: グローバルクールタイム0.5秒統一
date: 2026-04-18
proposer: teammate_a
decider: イ・ミンス # Design Director (18.1のapproved_byと同一)
context: 職業ごとにクールタイム基準がばらばらで、新規職業を追加するたびに
バランス再算定コストが累積。0.5秒のグローバル基準への統一を提案。
affected_atoms: [combat_cooldown_rule, skill_table, ui_cooldown_gauge]
reversible: ?
status: proposed
以下の決定atomのインパクト等級を、T0〜T4のいずれかで付けてください。
影響分野数 / 巻き戻しコスト / ユーザー影響範囲の3基準を別々に見て、
そのうち最も高い等級を採用すること。確信が持てない欄は「不確実」と書き、
人が確認すべきだと伝えてください。
// (意図: T0=分野5+・コスト非常に大・全体、T1=2〜4・大・大、T2=1・普通、T3=1・小、T4=1・非常に小)
[atom原文を添付]
3基準の評価:
1. 影響分野数
affected_atomsを見ると:
- combat_cooldown_rule → 戦闘分野
- skill_table → データ分野
- ui_cooldown_gauge → UI分野
明示された分野は3つです。
ただし「新規職業を追加するたびにバランスを再算定」というcontextを
見ると、バランス分野も間接的に影響を受ける可能性が高いです。
直接3 + 間接1 = 実質2〜4個の範囲と見ます。 → T1シグナル
2. 巻き戻しコスト
グローバルクールタイムを統一してから巻き戻すには、skill_tableの
すべてのスキルのクールタイムを職業別基準に再分岐させる必要があります。
skill_tableの行数が不明なため正確なコストは不確実ですが、
「統一 → 分岐の復元」は一般にコストが大きいです。 → 大 (T1シグナル)
※ この欄はskill_tableの規模が分からなければ確定不可。不確実マーク。
3. ユーザー影響範囲
クールタイムはすべての戦闘状況に適用されるため、ほぼすべてのユーザーが
体感します。 → 大 (T1シグナル)
採用等級: T1
根拠: 3基準がすべてT1を指しています。T0ではありません —
「コアメカニクスの変更」とまでは言えず、既存メカニクスのパラメーター
統一だからです。
人による確認が必要:
- 巻き戻しコスト欄: skill_tableの実際の行数の確認が必要
- reversibleスロットが「?」のまま空欄 → この分類はreversibleを
「困難」と埋めることを前提とします。
ここで、そのまま受け取りはしませんでした。出力の1番の項目に手が入りました。LLMはバランス分野を「間接影響」と見ましたが、プロジェクトAの実際の構造では、クールタイムの統一はバランス調整シートを直接作り直させます — 新規職業のバランス算定公式そのものが、クールタイム値を入力として受け取るからです。したがってバランスは間接ではなく、直接の4番目の分野です。影響分野数は4つ。それでも採用等級T1は変わりません。
もう一つ、巻き戻しコスト欄の「不確実」マークは正確でした。skill_tableは実際にスキル277行で、統一を巻き戻すと277行すべてのクールタイムを再分岐しなければなりません。コストは「大」ではなく「非常に大」に近いものでした。それでも、1マスが上に上がったからといって採用等級がT0になるわけではありません — 影響分野数とユーザー影響がT1の範囲なので、最も高い等級は依然としてT1です。
訂正。affected_atomsにbalance_sheetを追加してください (バランス公式がクールタイム値を
入力として受け取るので直接影響です)。これで影響分野は4つ、skill_tableは277行です。
これを反映してreversibleを埋め、最終等級を確定してください。
反映しました。
影響分野: 戦闘・データ・UI・バランス = 4つ
巻き戻しコスト: 非常に大 (skill_table 277行すべてを再分岐)
ユーザー影響: 大
reversible: 困難 (実質的に不可逆に近い — 277行の再分岐は
ビルド1〜2回 + 四半期にわたるバランス再検証を要する)
最終等級: T1
注意: 巻き戻しコスト欄だけを見ればT0シグナルですが、
影響分野4つ・ユーザー影響「大」はT1の範囲です。
3基準中2基準がT1を指すため、T1が妥当です。
ただし、この決定は「T1の中でも重い側」と表示しておくことを
推奨します — 巻き戻しコストがT0級だからです。
最後の文は、人より慎重でした。等級はT1だが巻き戻しコストはT0級だという点を、「T1の中でも重い側」とラベリングしようという提案 — これは最初のプロンプトにはなかった判断です。受け入れました。この決定にはweight: heavyタグを追加で付け、事後追跡の優先順位を上げました。
このトランスクリプトが示す核心は1つです。LLMは分類の草案と理由を作り、人はドメイン事実(バランスが直接影響、277行)で補正する。 どちらか片方だけでは成り立ちません。人だけでやれば空欄から始めるので遅く、LLMだけでやれば277行を知らないまま「大」と書きます。
等級が決まったら、次は「どこまで広がるか」です。前章のimpact抽出 — インバウンドエッジ、オントロジーのaffects関係、wikilinkの逆参照 — を決定atomに適用します。
# impact_propagation.py — 決定atomの伝播範囲を追跡
def trace_impact(decision):
# 1次: 決定が直接触れるatom・ファイル
direct = decision.affected_atoms + decision.affected_files
# 2次: 1次atomをwikilinkで逆参照するatom (impactインバウンドエッジ)
secondary = []
for atom in direct:
secondary.extend(find_inbound_refs(atom)) # [[atom]] 逆参照
secondary.extend(find_affects_edges(atom)) # オントロジー affects
secondary = dedup(secondary) - set(direct)
return {
"direct": direct,
"secondary": secondary,
"affected_fields": determine_fields(direct + secondary),
"estimated_hours": estimate_hours(direct, secondary),
}
核心はfind_inbound_refs — atomグラフの中で、該当atomを[[...]]で指している入ってくる矢印を集める関数です。決定自身が何に触れるか(出ていく矢印)はatomに書かれていますが、そのatomに誰が依存しているか(入ってくる矢印)は、グラフ全体を逆方向にスキャンしなければ見えません。2か月級の波及は、ほぼ常にこのインバウンドエッジ側に隠れています。
D2026_Q2_017にこの追跡を回した結果を正直に書きます。directは先ほど確定した4つのatom。secondaryはskill_tableを逆参照するatom群 — スキル説明テキスト、スキルアイコンのマッピング、職業別スキルツリーなど — が芋づる式に出てきました。数は時点ごとに異なるため断定しません。 追跡がつかんでくれた事実は「secondaryがdirectの数十倍」という方向であり、正確なatom数はグラフの状態によって変わります。方向だけで十分です — secondaryがdirectより1桁大きければ、それはT1のシグナルであり、事後追跡の対象だという意味です。
分類は独立した段階ではなく、決定フローの真ん中にゲートとして固定されます。決定候補が登録されると自動分析が等級を推薦し、人がレビュー・調整した後に初めて決定会議へ進みます。
flowchart TD
A[決定候補の登録
議事録 → 決定atom草案] --> B[自動インパクト分析
impact抽出]
B --> C{3基準の自動評価}
C -->|影響分野数| D1[グラフから計算]
C -->|巻き戻しコスト| D2[LLM草案 → 不確実マーク]
C -->|ユーザー影響| D3[LLM草案 → 不確実マーク]
D1 --> E[最も高い等級を採択
T0〜T4を推薦]
D2 --> E
D3 --> E
E --> F{人によるレビュー}
F -->|ドメイン事実で補正| G[等級確定 + weightタグ]
F -->|差し戻し・再分類| C
G --> H{等級分岐}
H -->|T0| T0[ディレクター+CEO / 四半期サイクル]
H -->|T1| T1[TF / 1〜2週間]
H -->|T2| T2[分野ディレクター / 3〜5日]
H -->|T3| T3[シニア1名 / 1〜2日]
H -->|T4| T4[担当者が即時処理]
T0 --> I[ビルド反映 — 不可逆]
T1 --> I
T2 --> I
T3 --> I
T4 --> I
I --> J[事後追跡
推定と実際 → 次の推定に学習]
J -.可逆吸収.-> B
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class B,C,D1,E,I code;
class D2,D3 ai;
class F,G,T0,T1,T2,T3,T4 human;
class A data;
このフローで不可逆の段階はただ1つ、ビルド反映(I)です。その手前はすべて可逆です — 等級の推薦を間違えても人が差し戻せばよく、weightタグも外せば済みます。ビルドに入り、他のドキュメントへ伝播した後に初めて不可逆になります。だからこそ、ゲート(Fの人によるレビュー)がビルドの手前にあるのです。不可逆の線を越える前に、人が一度せき止めます。
最後の点線 — 事後追跡(J)が次の決定の自動分析(B)へ戻る矢印 — が、このシステムを学習サイクルにします。不可逆の段階から出てきた実測データ(たとえば、QA時間が推定より長かった)が、次の決定の可逆の段階へ吸収されます。
決定がビルドに入って1週間〜1か月後、推定と実際を突き合わせます。D2026_Q2_017の事後追跡の様式です。
決定D2026_Q2_017 事後追跡 (様式例 · 数値は仮の入力)
─────────────────────────────────
作業時間 (推定 → 実際)
コード: 16h → 22h (+38%)
データ: 8h → 6h (-25%)
UI: 4h → 4h (=)
QA: 8h → 12h (+50%)
total: 36h → 44h (+22%)
影響atom (推定 → 実際)
direct: 4 → 4 (正確)
secondary: 推定数十 → 実際数十 (方向一致、正確な数値は非公開)
事故発生: 0件
誤差パターン: QAが毎回推定を超過 (今回+50%)
次の決定への適用: QA推定に+20%マージンを標準適用
上のブロックは、事後追跡がどんな形かを見せる様式の例です。時間・パーセントの値は実際のプロジェクトデータではなく、様式を埋めた仮の入力なので、自分のプロジェクトでは自分の数字に置き換えて埋めれば大丈夫です — 本書の約束のとおり、私たちは構造を見せ、数字はあなた自身が測ります。様式とは無関係に本物なのは1つだけです。「QAが推定を毎回超過する」という誤差の方向、そしてその方向を次の決定にフィードバックする手続きです。だからこそ、次の推定にはQAマージン(たとえば+20%)を最初から付けるという処方が出てきます。
事後追跡の価値は、正確な数字を当てることではなく、誤差の方向をフィードバックすることにあります。推定が正確になるほど等級分類への信頼が上がり、信頼が上がれば委任が可能になります。
各等級で繰り返される事故は異なります。処方も異なります。
| 等級 | 事故パターン | 処方 |
|---|---|---|
| T0 | ビジョンが曖昧 → 四半期の間ずっと混乱 | 決定文へのビジョン1行の明記を強制 |
| T1 | 分野間の衝突 → スケジュール遅延 | TFに全影響分野の代表が参加 |
| T2 | 隣接システムへの影響の見落とし → 後続決定の急増 | secondary追跡を必須に |
| T3 | 小さな決定の累積 → 一貫性の毀損 | 四半期の振り返りでT3をまとめて点検 |
| T4 | 検証不足 → ホットフィックスのやり直し | ホットフィックスにも最低1名のレビュー |
この表の処方は、すべて前の節までに出てきた道具で実行されます。T2の「secondary追跡を必須に」は§18.2.4のfind_inbound_refsであり、T1の「全影響分野の代表が参加」は、§18.2.4がつかんだaffected_fieldsによって誰が入るべきかが決まります。
最も高くつく事故は、表のどこにもありません。等級そのものを間違えることです。T0をシニアが1人で決めればビジョンが毀損され、T4をディレクターが直接処理すればボトルネックが生まれます。等級を間違えると、その下のすべての処方が見当違いの場所で作動します。だからこそ、§18.2.3の人によるレビューゲートは単なる形式ではないのです。
プロジェクトAで、等級分類の導入前後を比較します。以下の数値のうち絶対値は加工した例であり、方向(不等号)は実際の傾向です。
| 項目 | 等級なし | 等級運用 |
|---|---|---|
| 決定サイクル | すべて1〜2週間で均一 | T0四半期〜T4時間単位に分化 |
| 誤って処理された決定 | 四半期あたり多数 | 四半期あたり少数 |
| ディレクターの週間決定負担 | 大きい(すべての決定がディレクターへ) | 小さい(T2〜T4を委任) |
| ホットフィックスサイクル | 1〜2日 | 4〜24時間 |
| 四半期振り返りでの決定分析 | まとめて見るのが難しい | 等級別の統計で集計 |
表を断定的な数字ではなく方向で書いた理由は、3番目の項目1つで説明できます。ディレクターの時間の回収が、等級分類の最大の効果です。 等級がなければ、誤字からビジョンまですべての決定がディレクター1人に集中します。等級ができるとT2以下が分野ディレクター・シニア・担当者に分かれ、ディレクターはT0・T1に集中します。委任が可能になるということは、ディレクターが本当に重い決定に使う時間を取り戻すということです。
前章で決定atomグラフを作り、本章ではそのグラフの上に等級の自動分類器を載せました。2つは別々に動く道具ではなく、1つの骨格の中で連続する持ち場です。
flowchart LR
A["① 決定atomグラフ
(前章18.1)
議事録 → 12スロットatom"] --> B["② インパクト等級の自動分類器
(本章18.2)
グラフ → T0〜T4 + 推定"]
B --> C["③ wikilink影響追跡
(18.4 grepワークフロー)
atom ID → 伝播範囲"]
C --> D["人によるレビュー・承認
不可逆ゲート"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class C code;
class B ai;
class D human;
class A data;
3つの要素が直列です。グラフが入力を作り(①)、分類器が重さを付け(②)、影響追跡が伝播範囲を広げて見せます(③)。本章は真ん中の持ち場です。
3つの要素はいずれも、LLMの発展以降になって初めて実現の領域に入ってきました。最後まで解けなかった壁が②の自然言語評価の2マス — 巻き戻しコストとユーザー影響範囲(§18.2.2の「最後の壁」)— であり、LLMがその草案を埋めるようになって初めて、①→②→③が直列で回り始めました。
可逆・不可逆の整列も、この骨格にかみ合います。§18.2.5のフロー図のとおり、不可逆の線はビルド反映ただ1つであり、ビルド反映そのものは取り消せませんが、その実測結果は次の決定をより正確にする可逆的な学習として戻ってきます。
| パターン | 処方 |
|---|---|
| すべての決定を同じサイクルで処理 | 等級別にサイクルを分化 |
| 等級分類なしで各自が自律判断 | 3基準の分類ゲート |
| 等級の無視(T0をシニアが決定) | 決定者表の強制 |
| 事前の影響度評価の省略 | 自動分析を決定会議前のゲートに |
| 自然言語の欄をLLM出力のまま確定 | 人がドメイン事実で補正 |
| 事後追跡なしで決定を終了 | 1週間〜1か月で推定と実際を比較 |
| 推定誤差を次の決定に未反映 | 誤差の方向を次の推定マージンに適用 |
ゲーム外への応用。 インパクト等級は「1行の依頼が5分で済むものか、2か月かかるものか」を事前に見えるようにするラベルなので、決定が押し寄せるどんな職場でも通用します。社内wikiの文言1行の修正と「全部署の休暇ポリシー変更」が同じ「案件1件」として入ってきて同じ決裁ラインに乗ると、軽い仕事は過剰処理され、重い仕事は検証なしで流れていきます。たとえば運営チームの業務依頼を受けるとき、影響部署数・巻き戻しコスト・顧客影響範囲の3基準でT0〜T4を付けておけば、担当者が即時処理するものとチームリーダーの決裁が必要なものが自動的に分かれ、管理者の時間が本当に重い決定へと回収されます。
setup. 前章で作った決定atomを1件用意してください。affected_atomsスロットが埋まっている必要があります。空のままだと影響分野数を数えられません。
prompt. §18.2.3のプロンプト全文をそのまま使ってください。核心の3行を落とさないでください — (1)3基準をそれぞれ評価すること、(2)最も高い等級を採用すること、(3)確信のない欄は「不確実」とマークして人の確認を求めること。3行目がないと、LLMは知らないことまで断定します。
verify. LLMの出力を受け取ったら、2つのことを自分で確認してください。第一に、影響分野数はLLMが数えていても、atomグラフから自分で数え直します — 間接影響を直接として拾ったり、見落としたりしている可能性があります(トランスクリプトのバランスの事例)。第二に、「不確実」マークの付いた欄はドメイン事実で埋めます(skill_table 277行のような実際の規模)。2つの確認を終えてから等級を確定し、ビルドゲートへ渡してください。
チームもTFもない、一人で作るプロジェクトなら、5等級は過剰です。3等級に減らしましょう。
道具もコード1行なしで始めましょう。決定をメモするとき、先頭に[重い] [普通] [即時]のタグを付けるだけです。それだけでも、「重い」タグの付いた決定の前で一度立ち止まるようになります — 等級分類の本質は結局、重い決定の前で立ち止まる習慣であり、自動化はその立ち止まりをチーム規模でも機能させる装置にすぎません。
リリース3週間後、PvPバランスが崩れた原因を振り返る場でのことでした。ホワイトボードの上で遡っていった末にたどり着いた起点は、1か月前のひとつの決定でした。「GCD(グローバルクールダウン)0.3を0.5に引き上げる」。コンボが見えないというフィードバックを受けて2時間で合意した、合理的な提案でした。ところがその変更はタンク職の生存率を予想より14%も余計に引き上げ、それがPvPを崩したのです。その決定がタンクにまで届くということを、決定の場では誰も言えませんでした。決定そのものが間違っていたのではありません。決定がどこまで波及するかを決定する前に見られなかったことが、事故の原因でした。
影響トラッキングは2か所で行われるべきです。決定ボタンを押す前(pre)にどこまで波及するかを見て、決定を反映した後(post)に本当にそこまでしか波及していないかを確認することです。本章はその二つのトラッキングをひとつのワークフローに束ねます。
決定影響分析の核心は、意外なほど単純です。決定atomひとつをノードとして見て、そのノードに入ってくるエッジと出ていくエッジを読むことです。事前トラッキングは「この決定を変えるとどこが影響を受けるか」を問い(アウトバウンド+逆参照)、事後トラッキングは「実際にその影響が意図どおりに出たか」を問います(同じエッジを測定値と突き合わせる)。
著者のプロジェクトAでは、決定をdecisions/フォルダにatomとして残しています。現在26件が積み上がっており、各atomは日付・当事者・根拠・影響範囲をフロントマターとして持っています。影響範囲を抽出するツールがimpactで、その抽出ルールを決定単位で強制するatomがportal_layer_change_impact_checkです。この三つが、事前・事後トラッキングの実際の資産です。
事前トラッキングは右側(アウトバウンド)を読んで「ここまで波及する」と予測し、事後トラッキングは右側のノードの実測を持ってきて予測と突き合わせます。同じグラフを2回、違う目的で読むわけです。
ここからは実際のセッションです。要約せず、プロンプト全文とClaudeの生の出力、そして人がそれをどう却下して再依頼したかをそのまま載せます。
impactは、決定atomのIDを受け取って影響グラフを抜き出す小さなパイプラインです。インバウンドエッジ(この決定の根拠)、アウトバウンドエッジ(この決定が修正するファイル)、オントロジーのaffects関係、そしてウィキリンク逆参照([[D2026_Q2_017]]でこの決定を指している他の文書)を合わせて、影響候補リストを作ります。
decisions/D2026_Q2_017.md のproposal段階の事前影響度を評価して。
- この決定が修正するファイル(アウトバウンド)
- この決定を [[D2026_Q2_017]] で逆参照している文書(インバウンドwikilink)
- オントロジーのaffects関係でつながっているシステム
この三つを合わせて影響候補を出し、各候補が私の確認の必要な項目(T1)か、
自動で確定できる項目かを区別して。推測で埋めず、根拠ファイルが
なければ「根拠なし」と表示して。
decisions/D2026_Q2_017.mdを読みました。フロントマターから抽出した影響グラフです。アウトバウンド(この決定が直接修正) -
data/CombatFormula.md—global_cooldownフィールド(atomに明記) -sheets/CombatBalance— 回復スキル例外行(atomに明記) -ui/ComboIndicator— atomのaffected_teams: [ui]からの推定。ただし具体的なファイルはatomにない → 根拠なし(推定)インバウンドwikilink(この決定を逆参照) -
grepの結果、[[D2026_Q2_017]]で検索しましたが、現在のインデックスでは逆参照0件です。新規の決定のため、まだ誰も引用していないものと見られます。オントロジーaffects関係 -
CombatFormula → affects → TankSurvivalの関係がオントロジーにあります。 - したがって、この決定はTankSurvival指標に間接的な影響を与えます。推定影響+12%。
ここで人が止めます。最後の行「推定影響+12%」が問題です。
却下。「推定影響+12%」はどこから出た数字だ? atomにもオントロジーにもその数値はないだろう。君がでっち上げたのなら外せ。affects関係があるという事実までにとどめて報告すること。影響の大きさはシミュレーションを回して初めて出るものだ。それから、uiの推定の件は「根拠なし」に分類しておきながら、なぜ候補リストにはT1で載せた?
ご指摘のとおりです。2点を訂正します。
- 「+12%」の撤回。 atom・オントロジーのどこにも、その数値の根拠がありません。私が生成した値です。
TankSurvivalに影響があるという関係だけを報告し、大きさはシミュレーション(Layer 2)の結果で埋めるべきです。現在の値は未測定です。- uiの件の再分類。 具体的なファイルが見つからなかったため、候補リストではT1(確認必要)ではなく
根拠不足 — 人によるファイル指定が必要に下げるべきでした。訂正した表は以下のとおりです。
| 影響候補 | エッジ種別 | 根拠 | 分類 |
|---|---|---|---|
CombatFormula.md global_cooldown |
アウトバウンド | atomに明記 | 自動確定 |
CombatBalance 回復例外行 |
アウトバウンド | atomに明記 | 自動確定 |
TankSurvival 指標 |
affects | オントロジー関係 | 影響あり / 大きさ未測定 |
| uiコンボ表示(ファイル不明) | 推定 | 根拠なし | 人によるファイル指定が必要 |
| インバウンド逆参照 | wikilink | grep 0件 | 該当なし |
この表が事前トラッキングの成果物です。核心はTankSurvivalの行です。冒頭でPvPを崩したあの影響が、事前トラッキングでは「影響あり / 大きさ未測定」としてすでに挙がっています。会議の場でこれを見ていたら、「ならシミュレーションで大きさを測ってから決めよう」になっていたはずです。AIに+12%をでっち上げさせたままにしていたら、むしろもっと危険でした。偽の精密さが、検証をスキップさせてしまうからです。
事前トラッキングが終わったら、会議で決定します。決定がatomとして確定した瞬間、二つの不可逆ステップが始まります。
flowchart TD
A["事前トラッキング
(impactグラフ)"] --> B{決定会議}
B -->|却下| Z["proposal破棄
(事由を記録・可逆)"]
B -->|承認| C["決定atom確定
D2026_Q2_017"]
C --> D["⚠ 不可逆1: ビルド反映
CombatFormula・シート修正"]
C --> E["⚠ 不可逆2: 他文書への伝播
GDD・逆参照更新"]
D --> F["事後トラッキング開始"]
E --> F
F --> G{予測 = 実測?}
G -->|合致| H["決定カードをクローズ"]
G -->|逸脱| I["サイドエフェクトatom
後続決定の候補"]
I --> B
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class D code;
class B human;
class A,C,E,I data;
class H pass;
class Z fail;
不可逆である理由は単純です。ビルドに反映された数値はユーザーがすでにプレイしており、他の文書へ伝播した内容はチームメンバーがすでにそれを根拠に次の作業を始めているからです。だからこの二つのステップの直前に、portal_layer_change_impact_check atomがゲートとして掛かります。このatomのルールは一行に要約できます。「決定がLayerをまたぐ変更(データ → 数式 → UI)を含むなら、反映前に影響候補リストを人が一度承認しなければならない」。自動確定の項目だけなら通過、「人によるファイル指定が必要」が一つでもあれば遮断です。18.3.2では、uiの件が遮断事由になります。
ビルド反映後、1週間〜1か月のうちに事後トラッキングを回します。事前トラッキングが作った影響候補表の各行に実測値を付ける作業です。
D2026_Q2_017 の事後トラッキング。事前トラッキング表(18.3.2)の各候補に実測を付けて。
実測の出典:
- CombatBalanceシートのgit diff (反映確認用)
- シミュレーション1000回のログ sim_2026q2_017.json
- リリース1週間のKPIエクスポート kpi_w1.csv
意図値と実測値の差分を出して、誤差10%の範囲を外れた行だけ別にまとめて。
ないデータは「未収集」と表示して。
事前トラッキング表に実測を結合しました。
| 候補 | 意図 | 実測 | 差分 | 判定 |
|---|---|---|---|---|
| global_cooldown 0.3→0.5 | 反映 | git diffで確認 | — | 反映済み |
| 回復例外行 | 反映 | シートで確認 | — | 反映済み |
| 戦闘時間 | +15% | +14.2% | -0.8%p | 範囲内 |
| TankSurvival | (未測定) | +44% | 基準なし | 判定不可 |
| 5:5 PvP勝率 | +15%p | +29%p | +14%p | 逸脱 |
| uiコンボ視認性 | 改善 | ユーザー肯定65% | — | 合致 |
誤差範囲(±10%)を外れた行: PvP勝率+29%p(意図+15%p)。
TankSurvivalは事前に大きさを測定しておらず意図値がないため、判定不可です。この2行をつなげて見ると、
TankSurvival +44%がPvP勝率逸脱の原因と見られます。事前トラッキングでTankSurvivalを「大きさ未測定」のまま残したことが、今回の逸脱を予告していました。
正しい。
TankSurvivalをシミュレーションで測らなかったのが事故の根だ。後続の決定atomを作って。タンク職群のdmg補正でPvP勝率を意図範囲に戻す案。
decisions/D2026_Q2_025.mdのドラフトを生成しました。インバウンドに[[D2026_Q2_017]]を張り、今回の決定が17番のサイドエフェクト補正であることを逆参照で明示しました。これで17番のatomをimpactで再照会すると、インバウンド逆参照1件(25番)が捕捉されます — 18.3.2で0件だった場所です。
ここでグラフが閉じます。事前トラッキングで「影響あり / 大きさ未測定」だったノードが、事後トラッキングで逸脱として確認され、後続の決定がそのノードを指す逆参照として入ってきました。決定の全サイクルが、同じグラフの上で一周回ったわけです。
impactのインバウンド逆参照の抽出は、華やかなツールではなくgrep一行です。決定atomを指すウィキリンクを全文書から探します。
# D2026_Q2_017 を逆参照するすべての文書 (インバウンドwikilink)
grep -rln "\[\[D2026_Q2_017\]\]" decisions/ manuscript/ gdd/
# 決定atomのアウトバウンド — フロントマターのaffected_files抽出
grep -A20 "affected_files:" decisions/D2026_Q2_017.md
# 事後トラッキング: 意図に対する逸脱行だけ (判定カラム)
grep -E "이탈|판정 불가" tracking/D2026_Q2_017_post.md
この3行で、事前・事後トラッキングの骨格が回ります。LLMはこの結果を読んで解釈する役どころであって、検索そのものを代行するわけではありません。grepが事実(どのファイルがこの決定を指しているか)を与え、LLMがその事実を影響候補表に編み、人が影響の大きさと判定に責任を持ちます。この分離こそ、§18.3.2で「+12%をでっち上げるな」が通用した理由です。
著者のプロジェクトAで、決定サイクルの標準化前後を比較した値です。絶対的な時間の数値はチーム規模(中規模、10〜50人)に依存する著者の推定(未検証)であり、比率と方向は実際の運用で観察されたものです。
| 項目 | 前後トラッキング分離 | 前後トラッキング統合 |
|---|---|---|
| 事後トラッキングが実際に回った決定の割合 | 約30% | 90%以上 |
| 事前に挙がっていたのに事後に事故として噴出した影響 | よくある | ほぼなし(事前にゲート) |
| サイドエフェクト → 後続決定の連結率 | 低い(口頭伝達) | 逆参照で自動候補化 |
| 決定グラフのインバウンド逆参照の完全性 | まばら | 閉じたループ |
核心は一つです。事前トラッキングと事後トラッキングが同じ候補表を共有するとき、事前に「大きさ未測定」として残した穴が、事後にまさにその場所で確認されます。分離されていると、事前に見たものと事後に測ったものが互いに違う様式になって突き合わせができず、だからトラッキング率が30%にとどまります。ただし、逆参照の完全性を最初から100%目標に据えると、運用負担が増えるだけです。決定atomにaffected_filesを書く習慣からつけて、逆参照grepを振り返りの周期に組み込みながら漸進的に広げるのが現実的です。
| パターン | 処方 |
|---|---|
| 事前に影響は見たが、大きさを測らずに決定 | 「大きさ未測定」の行はシミュレーションまで決定保留 |
| LLMが影響数値をでっち上げる | 根拠ファイルがなければ「根拠なし」、大きさはシミュレーションでのみ |
| 事後トラッキングが事前の表と違う様式 | 同じ候補表に実測カラムだけ追加 |
| サイドエフェクトを口頭で済ませる | 後続決定atom+逆参照wikilinkを強制 |
| Layerをまたぐ変更をゲートなしで反映 | portal_layer_change_impact_checkの通過を義務化 |
ゲーム外への応用。 決定ボタンを押す前に「どこまで波及するか」を見て(事前)、反映した後に「本当にそこまでしか波及しなかったか」を確認する(事後)この二度読みは、ゲームに限らずあらゆる変更管理の基本動作です。会社が価格ポリシーを変えるとき、事前に影響を受ける部署(営業・CS・精算)を候補表として挙げ、「大きさはシミュレーションまで未測定」と残しておけば、リリース後に「なぜ精算チームがこれを知らなかったのか」という事故を未然に防げます。たとえば新しい会員ランクを導入する前に、CS問い合わせ量・離脱率のような事後指標の欄を事前の表に空欄として作っておけば、1か月後にその欄に実測を埋めて、意図と実際の差を同じ表の上でそのまま突き合わせられます。
setup — 決定フォルダとトラッキングフォルダを作りましょう。
mkdir decisions tracking
# 決定atom 1件に、フロントマターとしてaffected_files、affected_teamsを記載
prompt — 事前トラッキングの後、事後トラッキングを同じ表でつなぎましょう。
decisions/<ID>.md の事前影響度評価: アウトバウンド(修正するファイル)・
インバウンドwikilink・オントロジーのaffectsを合わせて影響候補表を作り、
根拠のない項目は「根拠なし」、大きさは「未測定」と表示して。数値をでっち上げないこと。
(ビルド反映後)
同じ候補表に実測カラムだけ付けて、意図に対して誤差10%を外れた行だけまとめて。
逸脱した行は後続決定atomのドラフトにして、[[<ID>]] の逆参照を張って。
verify — グラフが閉じたか、grepで確認しましょう。
grep -rln "\[\[<ID>\]\]" decisions/ # 後続決定の逆参照が捕捉されればループは閉じている
grep -E "이탈|미측정" tracking/<ID>_post.md # 残った穴の確認
一人で作業する個人ゲーム開発者なら、会議・所有者・期限は全部省いて構いません。決定一行をdecisions/のMarkdownに書くとき、二つの欄だけ埋めてみましょう。affected_files:(この決定が触るファイル)とexpected:(意図した変化)です。ビルドした後にそのファイルを開いて意図どおりになったかを目で確かめ、ずれたものがあれば同じファイルにactual:の一行を足しましょう。ツールはgrep -rln "[[決定ID]]"一つで十分です。事前に一欄、事後に一欄 — これが前後トラッキングの最小形です。
月曜日の午前10時。戦闘担当のチームメンバーAが、社内メッセンジャーに一行を投げてきました。「グローバルクールダウン、0.5秒から0.4秒に下げてもいいですか?」 数字を一つ変えるだけの話です。表面的には。私はその一行を読んで、手が止まりました。この数字が入力されているドキュメントがいくつあるのか、この定数を前提に組まれたスキルバランスのatomがいくつあるのか、それを変えるとどのシートの数式が壊れるのか — 頭に浮かばなかったからです。浮かんだと錯覚すれば、それが事故になります。四半期ごとに8件から12件ずつ起きていた「あのドキュメントを見ていなかった」という抜け漏れの正体が、まさにこの錯覚でした。
そこで私は、答えを暗記しないことにしました。代わりに一行を打ちます。
impact combat_global_cooldown_constant
本章では、その一行が何を吐き出すのかを、生のまま見ていきます。影響範囲を抽出するというのは抽象的な話ではなく、インバウンドエッジ・オントロジーaffects・wikilink逆参照という3系統をgrepでかき集める具体的な動作だということを示します。
「このatomを変えると何が影響を受けるか」という質問は、実は三つの質問です。三つを混ぜると答えがぼやけ、三つを分ければgrep一行ずつに落ちます。
第一に、インバウンドエッジ(inbound edge) — 誰が自分を指しているか、です。atom Aがatom Bを参照すればA→B方向のエッジです。Bを変えるときに危険なのは、Bを指しているAたち、つまりBへ入ってくる矢印です。だからアウトバウンド(自分が誰を見ているか)ではなく、インバウンドを見ます。変更の衝撃波は、矢印を遡って伝わっていきます。
第二に、オントロジーaffects — 意味のうえで何に影響を与えるか、です。atomのfrontmatterに明示したaffects:フィールドです。名前が直接登場しなくても、設計者が「これはあそこに影響する」と宣言しておいた意味的なつながりです。grepでは拾えない別名・同義語の問題を、人があらかじめ入力しておいたものです。
第三に、wikilink逆参照 — [[atom_id]]形式で自分を明示的にリンクしたドキュメントです。信頼度は最も高いです。偶然の単語一致ではなく、書き手が意図的に張ったリンクだからです。
3系統の関係を図式にすると、次のとおりです。
この3系統を一つのツールに束ねたものがimpactです。次の節では、このツールが実際に吐き出す出力を丸ごと見ます。
言葉で説明する代わりに、実際の1サイクルを最初から最後まで追いかけます。プロンプト全文、ツールの生の出力(手を加えていません)、私が検証して却下した部分、再依頼まで、すべて含めます。
私はClaudeにimpactワークフローを回すよう依頼しました。ワークフロー本体はripgrepを呼び出すシェルですが、結果の解釈と等級の推奨はClaudeが担当します。
impactワークフローをcombat_global_cooldown_constantに対して回してください。
3系統(インバウンドエッジ / affects / wikilink逆参照)をそれぞれgrepで抽出し、
重複をまとめたうえで、各影響対象にインパクト等級の推奨を付けてください。
変更内容は「グローバルクールダウン 0.5秒 → 0.4秒」です。
3系統を順番に実行します。
[1/3] インバウンドエッジ — このatomを参照している位置
$ rg -n "combat_global_cooldown_constant" --type md docs/ atoms/
atoms/combat/combat_skill_cooldown_rule.md:34: base: combat_global_cooldown_constant
atoms/combat/combat_gcd_ui_feedback.md:12: depends_on: combat_global_cooldown_constant
docs/spec/combat_v3.md:42: グローバルクールダウン(combat_global_cooldown_constant)を基準に
docs/spec/skill_design.md:18: クールタイムはcombat_global_cooldown_constantの倍数で設計
docs/balance/refgame_comparison.md:77: refgameは0.5秒、我々のcombat_global_cooldown_constantも同一
→ 直接参照を5件発見。
[2/3] オントロジーaffects — affectsフィールドでこのatomを指している箇所
$ rg -n "^\s*-?\s*combat_global_cooldown_constant" -B3 atoms/ | rg -B3 "affects:"
atoms/combat/combat_dps_curve.md:
affects:
- combat_global_cooldown_constant ← (注意: 方向の確認が必要)
→ 1件。ただしこれはdps_curveが「影響を与える」と宣言したもので、
方向が逆の可能性があります。人による確認が必要 — 下記の検証項目を参照。
[3/3] wikilink逆参照 — [[...]]形式のリンク
$ rg -n "\[\[combat_global_cooldown_constant" --type md
docs/spec/combat_v3.md:42: ...[[combat_global_cooldown_constant]]...
atoms/combat/combat_skill_cooldown_rule.md:34: ...[[combat_global_cooldown_constant]]...
→ 2件。どちらも[1/3]で既に検出済みの位置です (重複)。
[合算 — 重複除去後のユニークな影響対象]
1. atoms/combat/combat_skill_cooldown_rule.md (インバウンド+wikilink)
2. atoms/combat/combat_gcd_ui_feedback.md (インバウンド)
3. docs/spec/combat_v3.md (インバウンド+wikilink)
4. docs/spec/skill_design.md (インバウンド)
5. docs/balance/refgame_comparison.md (インバウンド)
6. atoms/combat/combat_dps_curve.md (affects — 方向未確定)
[等級推奨 — 18.2のインパクト5等級基準]
1. combat_skill_cooldown_rule → T1 (全スキルのクールタイムがこの定数の倍数)
2. combat_gcd_ui_feedback → T2 (UIフィードバックのタイミング、視覚補正が必要)
3. combat_v3.md → T3 (仕様書の数値更新)
4. skill_design.md → T3 (設計ドキュメントの数値更新)
5. refgame_comparison.md → T2 (ベンチマーク比較表、refgame比のギャップ再計算)
6. combat_dps_curve.md → 未定 (方向確認後に等級)
ここで私が立ち止まった箇所は2か所です。
却下1 — affectsの方向。combat_dps_curve.mdがaffects:フィールドにこちらの定数を書いているのは、dps_curveがこの定数に影響を与えるという宣言です。ところが、いま変えようとしているのは定数の側です。つまり矢印の方向が逆です。dps_curveが定数を変えるのではなく、定数が変わればdps_curveの計算前提が揺らぎます。Claudeが「方向の確認が必要」と正直に保留したのは正しい判断でした。私はこの項目を影響対象として維持しつつ、affectsではなく「逆方向の意味依存」として再分類しました。T2で確定です。
却下2 — refgame比較表をT2と見たことは過大評価でした。refgame_comparison.mdは外部ゲームとの単純な数値比較表です。こちらの数字が変わっても表の1マスを0.5→0.4に直せば終わりで、どのシステムもこの表を入力として使っていません。T3(ドキュメントの数値更新)に降格です。
2点修正して、影響表を整理し直してください。
1) combat_dps_curve.mdは影響対象として維持、ラベルを「逆方向の意味依存」に、
等級T2で確定。
2) refgame_comparison.mdはT2 → T3に降格 (入力として使うシステムなし、表の1マス修正)。
そしてT1・T2だけを集めて「変更前に必ずレビュー」リストとして分離してください。
Claudeが整理した最終結果は次のとおりでした。
[変更前に必ずレビュー — T1・T2]
T1 combat_skill_cooldown_rule.md 全スキルのクールタイムがこの定数の倍数
T2 combat_gcd_ui_feedback.md UIフィードバックのタイミング再調整
T2 combat_dps_curve.md 逆方向の意味依存 — DPS曲線の前提を再確認
[ドキュメントの数値更新のみ — T3]
T3 combat_v3.md / skill_design.md / refgame_comparison.md
最初は頭に「浮かばなかった」6個の影響対象が、1回のgrepサイクルと2回の人の判断で、優先順位の付いたリストになりました。これが影響範囲抽出の実体です。ツールが候補を全件さらい、人が方向と等級を決めます。
前節の1サイクルを流れとして一般化すると、次のようになります。自動の段階と人の段階がどこで分かれるかが核心です。
flowchart TD
A[変更対象atomの指定
combat_global_cooldown_constant] --> B{impact実行}
B --> C1[インバウンドエッジgrep
rg atom_id]
B --> C2[affectsフィールドgrep
rg affects: ブロック]
B --> C3[wikilink逆参照grep
rg 角括弧atom_id]
C1 --> D[重複除去・合算]
C2 --> D
C3 --> D
D --> E[LLM等級推奨
T0~T4ラベル]
E --> F{人による検証}
F -->|方向誤り・等級過大| G[却下後に再依頼]
G --> E
F -->|承認| H[変更前レビューリスト確定
T1・T2分離]
H --> I[変更依頼コメントへ自動添付]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class B,C1,C2,C3,D,I code;
class E ai;
class F,G human;
class A,H data;
自動なのはgrepの3系統と合算、そして等級の草案までです。人が担うのはただ一つ、方向と等級の最終判断です。前のサイクルで見たaffectsの逆方向とrefgameの降格は、正確にこの場所で起きました。ツールを100%信頼すると、affectsの逆方向を影響対象から外してしまうか、比較表を過保護にして毎回不要なレビューを回すか、二種類の事故が起きます。自動と人の境界をこの一点に置くのが、このワークフローの設計意図です。
impactの内部が呼び出すripgrepパターンをそのまま書きます。これがツールの正体です — 派手なインフラではなく、検証済みの正規表現3行です。
インバウンドエッジ。atom IDがドキュメント本文に登場するすべての位置です。最も広くさらいます。
rg -n "combat_global_cooldown_constant" --type md docs/ atoms/
affectsフィールド。affects:ブロックの中にatom IDが入っている場合だけを見ます。-B3で前の3行を一緒に取り出し、それがaffectsブロックなのか別のフィールドなのかを人が目で確認します。
rg -n "combat_global_cooldown_constant" -B3 atoms/ | rg -B3 "affects:"
wikilink逆参照。二重の角括弧で囲んだ明示リンクだけです。信頼度が最も高く、優先レビューの対象です。
rg -n "\[\[combat_global_cooldown_constant" --type md atoms/ docs/
三つのパターンは、信頼度と再現率がきれいに反比例します。wikilinkはほぼ100%正確ですが、書き手がリンクを張らなければ拾えません。インバウンドエッジは全部さらいますが、偶然の単語一致(ノイズ)が混ざります。affectsは意味を捉えますが、方向が紛らわしいです。三つを合わせて初めて穴が埋まります。一つだけ使えば、必ず漏れる場所が出ます。
影響範囲の抽出は、決定サイクル(§18.3)の一段階です。決定カードが登録された瞬間、そのカードのaffected_atomsスロットを入力としてimpactが回ります。この連結を検証するatomがportal_layer_change_impact_checkです。
このatomの役割は、「Layerを横断する変更のとき、影響検査をスキップできないように堰き止めること」です。グローバルクールダウン定数の変更はL1(システム)の数字一つですが、その影響はL3(マスターデータの数式)とL4(ビルドQA項目)にまで広がります。portal_layer_change_impact_checkは変更がLayer境界を越えるかを判定し、越えるならimpactの実行を強制します。
---
name: portal_layer_change_impact_check
type: gate
description: Layer境界を越える変更は、影響検査を通過するまでビルド反映禁止
trigger:
- 決定カード登録時にaffected_atomsが空でない
- 変更atomのlayer != 影響atomのlayer
action:
- impactを実行 (3系統grep)
- T1・T2の影響対象があれば「レビュー完了」チェック前のマージをブロック
---
グローバルクールダウンの事例でこのゲートが捕まえたのは、combat_skill_cooldown_rule(L1)ではなく、そのruleを入力として使うCombatBalanceシート(L3)でした。シートのクールタイム(クールダウン)倍数カラムは、この定数を前提に数式を組んでいます。grepがドキュメントからatomをさらい、ゲートが「これはLayerを越えるからシートまで見ろ」と押し切りました。二つが連結されていなければ、ドキュメントは更新されたのにシートは古い前提のまま残るという、典型的な抜け漏れが起きます。
著者のプロジェクトA運用で観察した変化です。時間の数値は著者の推定(未検証)であり、抜け漏れ事故の件数は四半期の振り返りで実際に集計された値です。
| 項目 | ワークフローなし | impact運用 |
|---|---|---|
| 影響atomの把握時間 | 記憶に依存(不完全) | 1〜2分(全件grep) |
| 変更の抜け漏れ事故 | 四半期あたり8〜12件(集計実測) | 四半期あたり1〜2件(集計実測) |
| 変更依頼への影響添付 | 人がときどき | ゲートが強制 |
| 新規メンバーの影響把握 | 数日(口頭での伝授) | 30分(ツール + カード) |
| インフラコスト | グラフDB導入の検討 | ripgrep + シェルのみ |
最後の行が、本章全体の結論です。プロジェクトAはグラフDBと検索インデックスを検討した末に、結局ripgrepと小さなシェルに落ち着きました。精密測定機器のほうがメジャー(巻尺)より正確なのは確かです。しかし毎日取り出して使う道具は、故障せずインフラも要らないメジャーの側に収束します。抜け漏れ事故が8〜12件から1〜2件に落ちたのは、ツールが精巧だからではなく、毎回欠かさず回るからです。
3系統を束ねても、漏れる場所はあります。分かったうえで使うのと、知らずに信じるのとは違います。
別名と略語。本文に「GCD(Global Cooldown、グローバルクールダウン)」としか書かれていなければ、combat_global_cooldown_constantのgrepには引っかかりません。補完策は、検索語を正規表現に拡張することです — (combat_global_cooldown_constant|GCD|전역\s?쿨다운)。チームの略語辞書を別途管理し、検索時に自動で合成します。
不可逆の領域。grepは可逆段階のツールです。ビルド反映前なら、ドキュメントとatomとシートの間の影響はすべてgrepで見えます。しかしビルドが出て、ユーザーがクールタイム0.4秒を体感した後の反応 — コミュニティの不満、体感テンポの変化 — はgrepの対象ではありません。だから原則は単純です。すべてのgrepレビューはビルド反映前に終える。 不可逆段階に移ると、grepで分かることは急激に減ります。
LLMによるチェックの位置づけ。前のサイクルでaffectsの方向と等級を人が判断したように、grep候補の適合性判定にLLMを挟むとノイズが抜けます。ただしLLMも100%ではないため、最終承認は人です。ツール・LLM・人が一段階ずつ濾す構造で、精度は運用可能な水準に到達します。一段階でも抜けば、その段階が捕まえていた種類の事故がまた入り込みます。
ゲーム外への応用。 「この項目を変えるとどこが揺らぐか」を、記憶ではなく全件検索でさらう習慣は、ドキュメントやスプレッドシートで仕事をするどんな事務職にも同じ効果をもたらします。ある約款の条項を一つ直すとき、その条項番号が明記された契約書・案内メール・顧客FAQが何か所あるかを頭で思い出そうとすると必ず抜けますが、フォルダ全体をキーワードでgrepして全件さらった後に、人が「要修正 / マークのみ / 無関係」に分類すれば、抜け漏れは消えます。たとえば経理担当者が特定の勘定コードを変更するとき、そのコードを参照する精算シート・報告テンプレート・マクロを全件検索して変更前レビューリストにしておけば、「あのシート一つを見ていなかった」という四半期決算の事故を構造的に防げます。
ドキュメントとatomがプレーンテキスト(.md)で管理されていて、ripgrep(rg)がインストールされていれば準備完了です。atom IDの命名規則(スネークケース、一意のID)があれば、grepの精度が大きく上がります。
# 検証: atom IDひとつがドキュメント全体で何回登場するか
rg -c "combat_global_cooldown_constant" docs/ atoms/
変更対象のatomと変更内容を渡して、3系統の抽出と等級の推奨を依頼してみましょう。
impactを<atom_id>に対して回してください。
インバウンドエッジ / affects / wikilink逆参照の3系統をそれぞれgrepで抽出し、
重複を合算したうえで18.2のインパクト等級(T0~T4)を推奨してください。
変更内容: <何を何に>。
T1・T2だけを「変更前レビュー」リストとして分離してください。
ツールの出力をそのまま信じず、次の2点を手で確認してみましょう。
確認後、T1・T2のリストだけを変更依頼のコメントに貼れば、1サイクルが閉じます。
ツールもatomグラフもない個人作業なら、コマンド一行とメモ一枠で同じ効果を出せます。
# 変更したい概念名で全フォルダを全件検索
rg -n "전역쿨다운|GCD|global_cooldown" .
検索結果をそのままメモ帳に貼り、各行の横に「要修正 / マークのみ / 無関係」の三つのうち一つを手で書き込んでみましょう。これが一人バージョンのimpactです。核心はツールの精巧さではなく、「記憶に依存せず、全件さらった後に人が分類する」という手順そのものにあります。手順があれば抜け漏れは減り、なければ月曜午前のあの途方もなさが毎回繰り返されます。
一次読者:中規模(10〜50人)のチームを率いるデザインディレクター・リードプランナー 一人/趣味の読者向けの縮小版:§19.1.8「一人ならここまで」
ビジョン文書を1ページにきちんと書いてあるチームでも、同じ事故が繰り返されます。ビジョンは壁に掛かっているのに、肝心の、毎週積み上がっていく決定がそのビジョンに合っているかどうかを誰も確認しないのです。四半期の振り返りで一度めくってはみるものの、その時点ではすでに、ずれた決定の上に次の決定が3つほど積み重なっています。ビジョンが「争いの基準点」になるためには、書くことよりも決定のたびにビジョンに照らしてみることが重要です。そしてその照合作業は、人が手作業でやると退屈で抜け漏れが出やすい — AIに任せるのにうってつけの仕事です。
この章では2つをまとめて扱います。前半は、すでに書かれたビジョンを決定の採点表として回すワークフロー — 著者のプロジェクトの実際の決定atom26件をLLMにかけて「ビジョンスロット違反」の判定を受け、そのうち1件の誤判定を人が捕まえる1サイクルです。後半は、その採点表が誰の決定までカバーするのかという問い、すなわち権限委譲です。リーダーシップの一般論(ビジョンがなぜ重要か、委譲がなぜ成長の道具か)はすでに他の本に十分ありますから、この章はその原則をAIワークフローとして回す場面だけに集中します。
ビジョンが決定をふるいにかける、という話から整理する必要があります。ビジョン・ロードマップ・スケジュールは同じものではありません。時間の単位と変更頻度が異なり、その違いが崩れるときに、スケジュールの圧力がビジョンを揺さぶります。
| 層 | 期間 | 変更頻度 | ビジョン照合の意味 |
|---|---|---|---|
| ビジョン | 5〜10年 | ほぼなし | 決定が適合すべき基準線 |
| ロードマップ | 1〜3年 | 四半期 | ビジョンをスケジュールに翻訳した中間層 |
| スケジュール | 1〜3か月 | 週 | ビジョンと直接照合しない |
核心は、決定をかける対象がビジョン(最も変わらない層)だという点です。スケジュールが厳しいからとビジョンを変えるのではなく、スケジュールがビジョンとずれたときはスケジュールの側を直します。この階層がはっきりしていてこそ、次の節の自動点検が意味を持ちます。点検の基準線が毎週揺れていては、点検そのものが無意味だからです。
ビジョンは1ページ、5つのスロットで完結させます。著者のプロジェクトのビジョン文書は次の骨格です。このスロットが§19.1.3のLLM点検の採点基準になるので、まず形を見ておきましょう。
---
title: プロジェクトA ビジョン v2
layer: L0
locked: true # 変更にはゲームディレクター + CEOの合意が必要
---
## スロット1. 私たちが作るもの
韓国ファンタジー世界観のモバイルファーストMMORPG。
## スロット2. 誰のために
30〜50代、モバイル中心、重厚な物語を楽しむユーザー。
## スロット3. なぜ(差別化)
- 多層ナラティブによる深い物語(量産ではなく深さ)
- 東南アジア + 韓国の同時運営
## スロット4. どのように(価値)
- ユーザーの時間を尊重(無駄なコンテンツを最小限に)
- データ + 人のバランスで決定
- チームの合意を決定の速さより優先
## スロット5. 何でないか
- F2P暴走型の課金モデルではない
- PvP中心ではない
- 毎日N時間の強制ログインではない
スロット5(「何でないか」)が点検で最も働きます。違反はたいてい「やると決めたこと」ではなく、「やらないと決めたこと」をこっそりやる場面から生まれるからです。
ビジョンを何にかけるのか。著者のチームは主要な決定をすべてdecisions/フォルダにatom1枚ずつとして固定化しています。日付・当事者・根拠が明記された事実の記録で、現在26件が積み上がっています。点検の入力はこの26件です — 新しく作るのではなく、すでにあるものをかけるのです。
決定atom1枚の実際の形はこうです(匿名化済み)。
---
type: decision
id: D0019
date: 2026-05-12
deciders: [ゲームディレクター, データディレクター]
tier: T1
---
# refgame_selective_adoption_for_mobile
参照MMORPGの戦闘データの一部をモバイルビルドに選択的に採択する。
根拠:モバイルの6インチで検証済みの戦闘ペースがあり、ゼロから
再設計するとアルファの日程が1四半期遅れる。ただし、課金・ログイン
誘導の構造は採択しない。
26件の中から、点検の入力に使う代表をいくつか抜き出します(実際のatom名、§A.3.3)。
| atom id | atom名(匿名化) | tier | 一言要旨 |
|---|---|---|---|
| D0007 | claude_role_transition_phase2 |
T1 | Claudeを受動的な補助 → 能動的なパートナーへ格上げ |
| D0014 | dataset_scope_alpha_split |
T2 | アルファのデータセット分離基準を確定 |
| D0019 | refgame_selective_adoption_for_mobile |
T1 | 参照ゲームの戦闘データを選択的に採択 |
| D0021 | procedural_capability_frontier_5stage |
T1 | プロシージャル生成能力の5段階を定義 |
| D0023 | class_keyword_world_only |
T2 | クラスのキーワードを世界観内に制限 |
この表が次の節のプロンプトの入力データです。26件を一度にかけることが核心で、人が振り返りのときに手作業で26件を1件ずつビジョンと照合すると半日かかり、中盤から集中力が落ちて違反を見落とします。その退屈な一次照合をLLMに渡します。
実際に1サイクルを最後まで見ていきます。入力プロンプトはそのままコピーして使えますし、出力は実際のセッションを再構成したものです。
下のビジョン5スロットに決定atom26件を1件ずつかけて、各決定がビジョンに
適合/補完/違反/無関係のどれに当たるか判定して。違反か無関係なら、どのスロットと
なぜそうなのか根拠を1行で付けて(特にスロット5「何でないか」をよく見て)、
あいまいなものは「あいまい」と表示してこちらに渡して。適合は根拠なしでいい。
表だけで出して(id | 判定 | 衝突スロット | 根拠 | あいまい)、総評は省いて。
[ビジョン5スロット — §19.1.1の全文を貼り付け]
[決定atom26件 — id・本文・根拠]
プロンプトの半分が「判定形式の強制」です。自由にさせると、LLMは26件を全部「おおむね適合しています」で丸めてしまいます。4段階の判定ラベル、違反・無関係にだけ根拠を義務付けること、そして「あいまいなら人に渡せ」という逃げ道を入れておくのが、このプロンプトの核心です。スロット5を根拠の文で明示的に名指ししたのも意図的です — 違反は「やらないと決めたこと」から生まれるからです。
| id | 判定 | 衝突スロット | 根拠 | あいまい |
|-------|------|----------|------------------------------------------|------|
| D0007 | 無関係 | — | Claude運用の決定。ゲームビジョンとは評価軸が異なる | |
| D0014 | 無関係 | — | データセットの分離。内部インフラの決定 | |
| D0019 | 違反 | スロット5 | 参照ゲームの戦闘データ採択 → 「F2P暴走型の | ★ |
| | | | 課金モデルではない」と衝突する恐れ | |
| D0021 | 適合 | スロット3 | | |
| D0023 | 適合 | スロット1 | | |
| ... | | | | |
(26件中 適合18・補完3・無関係3・違反2)
違反/あいまい判定の確認依頼: D0019, D0026 — 人によるレビューが必要
出力で最も価値があるのは、表ではなく一番下の、違反2件とあいまい表示です。LLMが26件のうち24件を自動でふるい落とし、人が見るべき2件だけを上げてくれました。半日の照合が2件の確認に減りました。ところが、その2件のうち1件が誤判定です。
D0019(refgame_selective_adoption_for_mobile)の判定を人が読み直します。LLMは「参照ゲームの戦闘データ採択」を見て、スロット5の「F2P暴走型の課金モデルではない」と衝突すると判定しました。表面の単語だけ見ればもっともらしい判定です — 参照ゲームは攻撃的な課金で有名ですから。
しかし、atomの本文を最後まで読むと、最後にこういう文があります。「ただし、課金・ログイン誘導の構造は採択しない。」 この決定は戦闘ペースのデータだけを持ってきて、課金構造は明示的に除外しています。スロット5をむしろ守る決定です。LLMはatom本文の最後の限定文を判定の重みに反映できず、「参照ゲーム」という出所の単語に引きずられて違反に分類しました。これはスロット5違反ではなく適合です。
このような誤判定が出る理由ははっきりしています。LLMは決定の出所(どのゲームから持ってきたか)と決定の内容(何を持ってきて何を捨てたか)を同じ重みで見ます。人は「ただし、〜はしない」という限定節が決定の核心だと知っています。だから人が拒否して、再依頼します。
D0019をもう一度見て。本文の最後の文「ただし、課金・ログイン誘導の構造は採択しない」が
核心。採択するもの(戦闘ペースのデータ)と除外するもの(課金・ログイン構造)を
分けて、それぞれどのスロットに掛かるかをもう一度判定して。
LLMは答え直しました。「採択対象(戦闘データ)はスロット1・2に適合、除外対象(課金構造)はスロット5を積極的に支持。総合判定:適合。直前の違反判定は出所の単語への過剰反応による誤り」。この1往復でD0019は違反から適合に訂正されました。残った本当の検討対象はD0026の1件です。
このサイクルがこの章の核心です。LLMは26件を2件に減らしてくれますが、その2件のうち1件は誤判定かもしれません。自動点検は人によるレビューをなくすものではなく、人が26件の代わりに2件に集中できるようにする道具です。その2件を人が最後まで読まなければ、何の問題もない決定が「ビジョン違反」として会議に上がり、見当違いの対立を生みます。
上のサイクルを図にして残しておくと、以後は四半期ごとに同じ流れが繰り返されます。核心は、LLMの判定が自動的に決定を覆すわけではないという点です。違反・あいまいだけを人のゲートに上げ、廃棄・訂正・承認は人が行います。
flowchart TB
A["ビジョン5スロット(L0、locked)
採点の基準線"]
B["decisions/ atom26件
id・本文・根拠"]
A --> C["LLM一次判定
適合/補完/違反/無関係 + 根拠"]
B --> C
C -->|適合・補完・無関係 24件| F["通過 — 四半期の振り返りに記録"]
C -->|違反・あいまい 2件| D{"人による検討ゲート"}
D -->|誤判定を確認| E["再依頼
(限定節・文脈の補強)"]
E --> C
D -->|本当の違反| G["決定の再検討会議を招集"]
D -->|訂正後は適合| F
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class C ai;
class D,E,G human;
class A,B data;
class F pass;
人の手が触れる場所は2か所だけです。ビジョンと決定をきれいに入力する場所(一番上)と、LLMが違反・あいまいとして上げた少数の件を最後まで読んで判定する場所(真ん中のゲート)です。その間の退屈な26件の照合はLLMが回します。§6.2のcityジェネレーターで、lintが違反を自動廃棄せず、作家ゲートにalertだけを上げていたのと同じ設計です — 機械は疑わしい候補を挙げ、生かすか殺すかは人が決めます。
ここで自然な疑問が出てきます。26件の決定をゲームディレクターが全部下したのでしょうか。それではいけません。リードがすべての決定を自分で行えばボトルネックになり、すべて委譲すればビジョンが弱くなります。ビジョン点検は、委譲された決定まで同じ採点表でふるいにかけるための安全網でもあるのです。
決定には等級があり、等級がそのまま権限です。著者のチームの権限マトリックスを示します。
| 等級 | 決定者 | レビュアー | 通知 | ビジョン点検の対象? |
|---|---|---|---|---|
| T0 ビジョン・中核 | ゲームディレクター + CEO | 全チームリーダー | 全チーム | ビジョンそのもの(点検の基準線) |
| T1 システム・複数分野 | TF議長 + ゲームディレクター | TFメンバー | 分野チーム | ✅ 必須 |
| T2 分野・中間 | 分野ディレクター | シニア | 分野チーム | ✅ 必須 |
| T3 単発・小規模 | シニア | 担当者 | 直接の関係者 | 抜き取り点検 |
| T4 即時・ホットフィックス | 担当者 | シニア(事後) | ゲームディレクター(事後) | 点検対象外 |
decisions/の26件は大部分がT1・T2です — 委譲された決定です。ゲームディレクターはすべてのT2を直接見るわけではありません。その代わり、ビジョン点検(§19.1.3)が、委譲されたT1・T2の決定を四半期に一度ビジョンにかけてみます。委譲の安全網がすなわちビジョン点検というわけです。T0は点検の対象ではなく点検の基準線であり、T4のホットフィックスは量が多くビジョンへの影響がほとんどないため除外します。
委譲そのものは一度にフルで渡さず、4段階で漸進します。
| 段階 | 権限 | LLM点検との関係 |
|---|---|---|
| 1. 情報伝達 | 「こうしなさい」 | 委譲者が決定、点検は不要 |
| 2. 助言 + 決定報告 | 「Xを考慮して決定しなさい」 | 報告時にビジョン照合も一緒に見る |
| 3. 事後報告 | 「決定して結果を知らせなさい」 | atomに固定化 → 四半期点検に含める |
| 4. 自律決定 | 報告義務なし(等級の限度内) | atomさえ残せば点検が事後カバー |
第4段階の自律決定がビジョンとずれるリスクが最も大きいのですが、まさにそのリスクを§19.1.3の点検が事後に捕まえます。自律で下したT2の決定も、atomとして固定化さえされていれば四半期点検に自動的にかかります。委譲の自由とビジョンの一貫性が衝突しない理由はここにあります — 自由に決定しつつ、決定はatomとして残り、atomは四半期ごとにビジョンにかけられるのです。
ビジョン・委譲の章には、「ビジョン導入後に会議時間が90分から45分に減った」「委譲後にディレクターの決定負担が週30件から5件に」のような表を入れたい誘惑が大きいものです。そうした数字は、検証されていなければ本の信頼を削ります。この章の数値は、次の3つのいずれかでのみ扱います。
第一に、数えられるものは実測で書きます。decisions/のatomは現在26件です(2026年5月の実測基準)。LLMの一次判定から人のゲートに上がった件数、そのうち誤判定として訂正された件数は、セッションログでカウントされる実測値です。上のワークド・トランスクリプトで違反判定2件のうち1件(D0019)が誤判定だったことも、実際のセッションの結果です。
第二に、効果は方向だけで語ります。「半日の照合が少数の件の確認に減った」というのは構造の方向であって、絶対時間ではありません。正確な節約時間は決定の数・チームの規模・atom本文の長さによって変わるため、「26件を手作業で」と「LLM一次 + 人のゲート」の構造の差として読むのが正しいのです。会議時間・モチベーションのスコアのような結果指標はビジョン1つで左右されないので、因果を断定しません。
第三に、測定可能なものだけを約束します。このワークフローで実際に測定可能なのは — 四半期あたりビジョン点検にかけた決定の数、人のゲートを通過した件数、誤判定率(LLMの違反判定のうち人が適合に訂正した比率)、atomへの固定化漏れの件数(委譲されたのにatomがなく点検から漏れた決定)です。この4つは、会議で「感覚」ではなく数字で語れます。特に誤判定率は、LLMの判定をそのまま信じてはいけない理由を、毎四半期、数字で証明します。
| パターン | なぜ失敗するか | 処方 |
|---|---|---|
| ビジョンを書くだけで決定にかけない | ビジョンが壁の飾りのまま残り、決定は好き勝手に進む | 四半期ごとにatom26件をビジョンにかける§19.1.3 |
| LLMの違反判定をそのまま会議に上げる | 誤判定(D0019のような)が見当違いの対立を生む | 違反・あいまいの件はatom本文を最後まで人が読む |
| 決定をatomとして残さない | 委譲された決定が点検から丸ごと漏れる | 事後報告(委譲第3段階)にatomへの固定化を義務化 |
| T4のホットフィックスまで全部点検する | 量だけ増えてビジョンへの影響はほとんどない | 点検対象をT1・T2に限定 |
| 委譲を第1→第4段階に飛ばす | 自律決定がビジョンとずれたまま蓄積する | 段階的な委譲 + 四半期点検で事後カバー |
3つ目が最もよく見落とされます。自律でうまく回っているチームほど、決定を口頭の合意だけで済ませ、atomを残しません。すると§19.1.3の点検は固定化された決定しか見ないため、最も自由に下された決定が点検の死角に落ちます。委譲の自由は、atomへの固定化を前提にしてのみ安全です。
ゲーム外への応用。 ビジョンを決定のたびに照らしてみることと権限委譲は、ゲームチームだけの宿題ではなく、すべての管理職の仕事です。部署のミッションを1ページ5スロット(「私たちがやること / 誰のために / なぜ / どのように / 何でないか」)で釘付けにしておけば、毎週積み上がる実務の決定がそのミッションとずれていないかを、四半期に一度LLMで一次照合できます — 特に「やらないと決めたこと」をこっそりやる違反がよく捕まります。たとえばチームリーダーが委譲した決定を四半期ごとに部署のミッションにかけてみれば、自律的に下された決定が方向から外れていないかを事後に捕まえる安全網になります。ただし、LLMが「違反」として上げた件はそのまま会議に上げず、そのうち1件は誤判定かもしれないので、人が最後まで読む必要があります。
一人ならここまで:決定atomのフォルダがなくても構いません。自分のプロジェクト(または趣味のゲーム)のビジョンを、§19.1.1の5スロットで1ページだけ書いてみましょう。次に、最近下した決定5〜10件を1行ずつメモに書き、§19.1.3のプロンプトをそのまま貼ってLLMに一度かけてみましょう。「違反」判定が1つでも出たら、その決定のメモを最後までもう一度読み、LLMが正しいかどうかを自分で反論してみましょう。そうすれば、ビジョン点検がどんな判断の束なのか、なぜLLMの判定をそのまま信じてはいけないのかが、体に入ってきます。
チームなら、次の一歩から始めましょう。ビジョンを5スロット1ページに固定(L0、locked)し、直近の四半期に下したT1・T2の決定をdecisions/フォルダにatom1枚ずつとして固定化するところから始めます。atomが10件たまるだけでも§19.1.3のプロンプトを一度回してみることができ、その最初のサイクルで、委譲された決定のうちビジョンとずれた1件を捕まえられれば、このワークフローの価値がすぐに見えてきます。
一次読者:四半期に50件以上の決定を会議で下すディレクター・チームリーダー(中規模(10〜50人)チーム) 一人/趣味の読者向けの縮小版:§19.2.8「一人ならここまで」
90分の会議をうまく走り切ったのに、1週間後に同じ議題が会議のテーブルへ再び上がってきたことがあります。確かに決定したのに、誰が何を担当するのかが、どこにも書かれていませんでした。議事録には「グローバルクールダウン(GCD)を議論」とだけ残り、「0.5秒に決定、担当はチームメンバーA」は、その場にいた人の頭の中から1週間で揮発しました。リーダーの会議が崩れるポイントは、たいてい会議中ではなく、会議が終わった直後、決定が記録として固まる前のあの短い隙間です。
本章では、チームリーダーの仕事の2つの塊を扱います。前半は対立を毎回ゼロから解く代わりに、種類別の標準処方へ送る方法、後半は本章の背骨 — 会議で出た決定をAIが抽出しつつ、責任者と根拠が空なら通過させないよう強制するワークド・トランスクリプトです。リーダーシップの一般論(ビジョン提示・傾聴・共感)は他の本に十分ありますから、本章はその一般論をAIワークフローに乗せて決定の取りこぼしを防ぐ場面だけに集中します。
対立ゼロのチームが健全なチームだ、というのは誤解です。中規模(10〜50人)のチームが四半期に50件を超える決定を下していて摩擦が一度も見えないなら、対立がないのではなく水面下に沈んでいるのであり、沈んだ対立のほうが危険です。
リーダーの仕事は対立をなくすことではなく、種類を素早く分類して標準処方へ送ることです。同じ対立が毎回違うやり方で解かれると、解決にかかる時間が毎回ゼロから積み上がります。
| 対立の種類 | 衝突の正体 | 標準処方 |
|---|---|---|
| 価値の対立 | ビジョン解釈の違い(売上 vs ユーザー時間) | ビジョンスロットの引用 |
| 事実の対立 | 同じデータの異なる解釈 | データ確認(メタゲームレポート) |
| 優先順位の対立 | 「自分の領域のほうが重要」 | インパクト等級・KPI影響の比較 |
| 権限の対立 | 「これは私の決定」 | 権限マトリクスの再確認 |
| 個人間の対立 | 人間関係・コミュニケーションスタイル | 1on1、事実/感情の分離(システム外) |
最初の4種類は、処方がシステムの引用です。ビジョン・データ・KPI・権限マトリクスが明文化されていれば、決定の重みが人の口からシステム側へ移り、議論が短くなります。5つ目の個人間の対立だけがシステムの外です — 1on1と事実・感情の分離、そして時間と誠意のほかに機能する道具はほとんどありません。ただし「システムでは解けない」は、リーダーが手を引く口実にはなりません。システムが解けない領域も結局はリーダーの仕事である、という点がこの持ち場の難しさです。
分類は毎回最初から解くのではなく、1つの流れとして回ります。
flowchart LR
A["対立の認知"] --> B["種類の分類
(5種)"]
B --> C["標準処方の適用
ビジョン・データ・KPI・権限・1on1"]
C --> D["1週間後のフォロー確認"]
D --> E{"再発?"}
E -->|"同じ種類の繰り返し"| F["システム・ルールの点検
(四半期の振り返りの対立スロット)"]
E -->|"解消"| G["終結"]
F --> A
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class A,B,C,D,E,F human;
class G pass;
核心は右側の分岐点です。同じ種類の対立が繰り返されるなら、それは人の問題ではなくシステムの問題です。そのときは人を仲裁する代わりに、ビジョンや権限マトリクスといったルールに手を入れます。これが、§19.2.7で扱う四半期の振り返りの対立スロットへの入力になります。
対立処方の4種類がすべて「システムの引用」であるように、会議も結局は決定を作り、その決定を記録として固める装置です。リーダーが会議で守るべき5つの原則は、互いに結び付いています。どれか1つが欠けるだけで、残りも一緒に揺らぎます。
この5つのうち3・4・5が崩れたのが、冒頭の事故でした。決定を口頭ではしたのに(原則3を部分的に充足)、記録として固まらず(原則4の失敗)、責任者が入力されませんでした(原則5の失敗)。だから1週間後に、同じ議題が再び上がってきたのです。
問題は、原則3・4・5を人の意志に任せると、忙しい週に真っ先に崩れるということです。会議が終わると、リーダーはもう次の会議へ走っています。だからこの3つの原則を、AI補助パイプラインへ移します。議事録テキストから決定を自動抽出しつつ、責任者や根拠が空いていれば通過させないようにするのです。このパイプラインは、第17部で作った会議→議事録→atom抽出の流れ(§17.2)を、リーダーの観点からもう一度見るものです。
実際にどう回すのか、1サイクルを最後まで見せます。舞台は、著者のプロジェクト(モバイル優先MMORPG、以下「プロジェクトA」)の戦闘TF会議が終わった直後です。入力プロンプトはそのままコピーして使えますし、出力は実際のセッションを再構成したものです。
議事録をきれいに整理しません。発言が入り混じり、決定かどうか曖昧な行もそのまま残した粗いテキストが入力です。整理はAIの仕事であって、人が先にやる仕事ではありません。
[2026-06-05 戦闘TF議事録本文 — 抜粋、未整形]
チームメンバーA: グローバルクールダウン0.5秒でいく件、シミュの結果は安定していました。
チームメンバーB: 回復スキルまで0.5秒で縛ると、回復サイクルが崩れそうですが。
チームメンバーA: それは別枠にしましょう。回復はGCDの例外で。
イ・ミンス: いいですね、GCDは0.5秒で統一、回復は例外に。Aさんはマスターデータの
cooldownカラムを一括で見てください。
チームメンバーC: ターゲティング優先順位ルールは、来週もう少し見てから決めるということで…
チームメンバーB: ミニマップ縮小トグルはUIチームと一緒に見たほうがよさそうです。いったん保留で。
イ・ミンス: はい、それは次の会議で。
ここには、決定2件(GCD 0.5秒、回復例外)と保留2件(ターゲティング、ミニマップ)が混ざっています。人が目で拾い出すと、1件ずつ取りこぼします。それが冒頭の事故でした。
添付した議事録から「決定」だけを抜き出して。討論・保留・情報共有は決定ではない。
決定ごとにdecision(一文)/ owner / rationale / follow_upの4つの欄を埋めて、
ownerやrationaleが本文から見つからなければ推測せず、
「[MISSING — 会議で未確定]」と書いて。保留・次回会議への持ち越しはdeferredに、
決定かどうか曖昧な行はambiguousに分けて私に渡して。出力は
decisions / deferred / ambiguousの3ブロックだけで。
このプロンプトの半分が「空欄の強制」である点に注目してください。AIに自由を与えると、ownerをもっともらしくでっち上げたり、保留を決定へ昇格させたりします。「見つからなければ推測せず[MISSING]と報告せよ」という逃げ道を入れておくことが、このワークフローの核心です。決定は人が明示的に宣言してこそ価値があり(§17.6.3の原則)、AIは空いているという事実をあらわにするところまでを受け持ちます。
decisions:
- decision: 戦闘のグローバルクールダウンを0.5秒に統一する。
owner: チームメンバーA(「Aさんはマスターデータのcooldownカラムを一括で見てください」— イ・ミンス)
rationale: 「グローバルクールダウン0.5秒でいく件、シミュの結果は安定していました」(チームメンバーA)
follow_up: cooldownカラムの一括点検
- decision: 回復スキルはグローバルクールダウンの適用から除外する。
owner: [MISSING — 会議で未確定]
rationale: 「回復まで0.5秒で縛ると回復サイクルが崩れそう」(チームメンバーB)
follow_up: 回復スキルのcooldown例外処理をデータへ反映
deferred:
- ターゲティング優先順位ルール(チームメンバーC、来週追加検討)
- ミニマップ縮小トグル(UIチームとの協議が必要、保留)
ambiguous: (なし)
出力で最も価値があるのは、1件目の決定のきれいな表ではなく、2件目の決定のowner: [MISSING]です。回復例外は確かに決定されたのに、議事録のどこにも「誰がデータへ反映するのか」が書かれていませんでした。AIはその穴を推測で埋めず、正直に報告しました。良いプロンプトは、AIが「この欄は空いています」と言えるようにします。
この出力をそのまま受け取ってはいけません。[MISSING]が出たということは、会議が決定を半分しか終えていないという意味です。ここでリーダーがやるべきことは、AIの出力を直すことではなく、会議で抜けた決定を最後まで下すことです。
著者はこの場面で、チームメンバーAに社内メッセンジャーで一行だけ尋ねました。「回復例外のデータ反映もAさんが一緒に見るんですよね?」Aは「はい」と答えました。この一行が、漏れていた責任者を確定します。その次に再依頼します。
2件目の決定(回復例外)のownerはチームメンバーAで確定した(社内メッセンジャーで本人確認済み)。
これを反映してdecisionsをもう一度出して、2件の決定をpending atom候補の形式にも
変換して。
// (意図: status: pending、source_meeting、owner、related_atomsを含む — §17.2.4の形式)
AIは、ownerが埋まった決定2件をpending atom候補2つへ変換して答え直しました。この候補はすぐに正式な決定にはならず、pending状態で1週間の検証期間を経ます(§17.2.4)。会議で決めたことが、1週間の運用の後にひっくり返ることもあるからです。インクが乾く時間を与えるわけです。入力 → 抽出 → MISSING報告 → 人が決定を補完 → 再依頼という1サイクルが、ここで閉じます。
この一巡が、冒頭の事故を構造的に防ぎます。決定が半分しか出ていないとき、その事実が、会議が終わって1週間後ではなく会議直後のその場であらわになります。
上のワークド・トランスクリプトを第17部の議事録パイプラインの上に載せると、全体像はこうなります。リーダーの手が触れるのは2か所だけです。会議で決定を宣言する場面(先頭)と、AIが報告した[MISSING]を補完する場面(中間)。その間の抽出・変換・登録は自動です。
flowchart TB
A["会議の進行
(リーダー:決定を口頭で宣言)"] --> B["議事録テキスト
(整えていない本文)"]
B --> C["AIによる決定抽出
4フィールド強制 + MISSING報告"]
C --> D{"owner・rationale
は空か?"}
D -->|"MISSING"| E["リーダーが補完
(メッセンジャー確認 → 責任者確定)"]
E --> C
D -->|"すべて埋まる"| F["pending atom候補
(1週間の検証期間 §17.2.4)"]
F --> G["週1回レビュー
昇格・廃棄・保留 §17.2.5"]
G --> H["JIT manifest登録
次セッションで自動注入 §17.2.6"]
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class C,D ai;
class A,E,G human;
class B,F,H data;
このパイプラインでは、AIがやらないことのほうが重要です。AIは決定を作りません。責任者をでっち上げません。保留を決定へ昇格させません。AIがやるのは、議事録から決定候補を拾い出し、空欄をあらわにするところまでです。決定の宣言と空欄の補完は、人がやります。これが、§17.6.3で述べた「決定スロットはAI自動生成禁止」の原則のリーダー観点での適用です — 決定が他の文書・セッション・ビルドへ伝播すると不可逆な痕跡が残るため、入口のゲートでは、人が明示的に宣言する場面を保存します。
会社PCのチーム共有atomの中に、team_equal_decision_cultureという概念atomがあります。振り返りで繰り返し引用される語彙をルール化したもので、「決定は役職ではなく根拠で行う」というチーム文化を一語で指します。ディレクターが「私が決めたから終わり」と押さえ付けるのではなく、決定ごとに誰が・なぜを残し、後から誰でもその決定を根拠からたどり直せるようにする文化です。
§19.2.3の[MISSING]強制が、まさにこの文化の技術的な支えです。責任者と根拠を空欄のまま通過させないということは、決定の権威が「ディレクターが言ったから」ではなく「本文のどの発言から出たか」に置かれるという意味です。根拠の引用が空なら決定が止まるので、役職で押し切った決定は、構造的にatomになれません。
この文化は、§19.2.1の対立処方とも1本の線でつながっています。価値の対立をビジョンの引用で、事実の対立をデータで、権限の対立をマトリクスで解くというのは、すべて人の口の代わりに記録された根拠で解くという同じ原理です。対等な決定文化は対立処方の土壌であり、[MISSING]強制は、その土壌が会議の単位で固まってしまわないよう毎回耕し直す道具です。
ここに、チーム文化のもう1つの軸である、公開と非公開の境界が重なります。議事録・決定カード・KPIデータ・事故報告は公開領域に置き、1on1の対話・人事評価・給与・個人の事情は非公開領域に置きます。決定抽出パイプラインが扱うのは、すべて公開領域です。個人間の対立(§19.2.1の5つ目)がシステムの外にある理由も同じです — それは非公開領域なので、atomとして固定化しません。
リーダーシップの章には、「会議パイプラインを導入したら会議時間が半分に減った」のような表を入れたくなる誘惑が大きいものです。そうした数字は、検証されていなければ本の信頼を削ります。本書の原則は、次の3つのいずれかです。
第一に、測定可能なものだけを指標として約束します。会議パイプラインで実際に数えられるのは、こういうものです — 決定あたりのowner・rationale欠落件数(目標0)、議事録から抽出された決定のうちpending atomへ昇格した比率、「これ、前に決定しなかったか?」という再会議の件数。この3つは、会議で「感覚」ではなく数字で語れます。
第二に、著者の推定は推定と書きます。会議直後の決定抽出にかかる時間が「手作業での議事録整理20〜30分 → AIの下書き+補完で5分以内」というのは、著者の経験に基づく推定であり、未検証の仮説です。絶対値を覚えるのではなく、構造の違い(「人が最初から拾い出す」vs「AI抽出+空欄だけ補完」)として読めば十分です。正確な節約時間は、会議の規模・決定数によって変わります。
第三に、因果を断定しません。「再会議が減った」が全面的にこのパイプラインのおかげだとは断言しません。チームの成熟度・プロジェクトの段階も一緒に作用します。方向(決定の取りこぼしが会議直後にあらわになれば、再会議が減る方向に働く)だけを語り、倍率をでっち上げません。
対立処方と決定パイプラインは、四半期の振り返りで一度、点検サイクルを回します。振り返りに「対立スロット」と「決定の取りこぼしスロット」を設けます。
2026 Q2 四半期の振り返り — 対立・決定スロット
─────────────────────────────────
[対立] 今四半期の主要3件
1. グローバルクールダウン(価値の対立)→ ビジョン引用で終結。
学習: ビジョン5スロットが決定基準として機能することを再確認。
2. 新規ダンジョンの優先順位(優先順位の対立)→ KPI影響の比較。
学習: 優先順位表がなく毎回その場で比較 → 来四半期に表を導入。
3. キャラクターデザインの権限(権限の対立)→ 権限マトリクスの再確認。
学習: マトリクスに「ビジュアル vs 機能」の分担項目を追加する必要あり。
[決定の取りこぼし] 今四半期の[MISSING]発生件
- 回復例外決定のowner未記載 (2026-06-05) → 社内メッセンジャーで補完。
学習: TF会議で決定を宣言する際、ownerの即時指名を進行チェックリストに追加。
対立も決定の取りこぼしも、振り返りの入力です。同じ種類の対立が繰り返されればシステム(ビジョン・権限表)に手を入れ、[MISSING]が同じパターンで頻発すれば会議の進め方に手を入れます。§19.2.1のフロー図で右側へ抜けた「システム・ルールの点検」が、ここで具体化されます。
ゲーム外への応用。 「確かに決定したのに、1週間後に同じ議題がまた上がってくる」という会議の事故は、業種を選びません。議事録本文を整えずにそのままLLMへ入れて決定だけを抽出させ、責任者や根拠が空なら推測で埋めずに
[MISSING]と報告させれば、決定が半分しか出ていないという事実が、会議直後のその場であらわになります。たとえば営業の週次会議で「このアカウントはAが担当することになった」が口頭だけで交わされて記録されなければ、翌週には宙に浮きますが、AI抽出がowner: [MISSING]を表示すれば、その場でメッセンジャーの一行で責任者を確定し、再会議を1件なくせます。決定の宣言と空欄の補完は人が、抽出はAIが受け持つ、という分担が核心です。
一人ならここまで:チームも議事録パイプラインもなくて構いません。自分が最近参加した会議(勉強会・サークル・1人プロジェクトの打ち合わせでも構いません)のメモを、§19.2.3のプロンプトにそのまま貼り付けて一度回してみましょう。AIが
owner: [MISSING]を表示する決定が1つでもあれば、それがあなたのチーム(またはあなた自身)が1週間後に再び持ち出す議題です。その空欄を今埋めるだけで、再会議が1件消えます。
チームなら、次の一歩から始めましょう。次の議事録を整えずに、そのまま§19.2.3の抽出プロンプトへ入れ、ルール2([MISSING]強制)だけを生かします。pending atom・JIT登録(§17.2)はその次です。空欄強制の一行があるだけでも、「決定したと思っていたのに書かれていない」という最も高くつく取りこぼしを、会議直後に捕まえられます。
| パターン | なぜ失敗するのか | 処方 |
|---|---|---|
| すべての対立を同じ方法で解く | どの種類も最後まで解けない | 5種の分類 → 種類別の処方(§19.2.1) |
| 対立ゼロのチームに満足する | 対立が水面下に沈む(より危険) | 対立は健全さのシグナル、四半期の振り返りスロット |
| 決定を口頭だけで済ませて書かない | 1週間後に同じ議題で再会議 | AI抽出+pending固定化(§19.2.3) |
| AIが責任者を推測で埋める | 間違った責任者がatomとして固まる | [MISSING]強制、推測禁止(§19.2.2) |
| AIが決定を自動生成する | 決定の権威が根拠から外れる | 決定の宣言は人、AIは補強のみ(§17.6.3) |
| 保留を決定へ昇格させる | 未確定の議題が不可逆に伝播する | deferredブロックで分離(§19.2.3) |
3つ目と4つ目は、最も頻繁にセットで破裂します。決定を書かないチームはAIに「よしなに整理して」と丸ごと投げ、AIは親切に責任者をでっち上げます。そのでっち上げられた責任者がatomとして固まると、1週間後に「私が引き受けた覚えはないのですが」という、より高くつく対立が生まれます。[MISSING]強制は、その2つの失敗を一行で防ぎます。
[MISSING]で止めます。第一読者:チームにAIを導入するかを決定し、そのコストを経営陣に説明しなければならないリード(中規模(10〜50人)チーム) 一人・趣味の読者向け縮小バージョン:§19.3.12「一人ならこれだけ」
CEOの部屋で「AIツールのコストとして月いくら出ていっているが、それで何が良くなったのか」という質問を受けたことがあります。そのとき手にしていたのはスライド1枚で、そこには「生産性3〜5倍向上」と書いてありました。CEOは重ねて尋ねました。「その3〜5倍はどこから出てきた数字ですか」。答えられませんでした。その数字は、私がどこかで見たブログの平均値を書き写したものであって、私たちのチームで測った値ではなかったのです。
その日以降、AI導入の報告から加工した数値をすべて外しました。代わりに、システムが実際に残しているもの — atomが何個積み上がり、スキルが何個動いていて、ログの中でどの入力がどのコンテキストを呼び出しているか — をそのまま報告するようにしました。本章では2つのことを扱います。第一に、AI導入を保守的(人が決定し、AIが検証)から進歩的(AIが候補を生成し、人が採択)へと段階を分けて決定するフレーム。第二に、その導入のROIをブログの平均値ではなく、自分のシステムの実測ログで経営陣に説明する方法です。リーダーシップの一般論はほかの本に十分ありますから、本章はAI導入という決定そのものをAIで補助し、その根拠をシステムログから汲み上げる場面だけに集中します。
AI導入を「導入する/しない」の二分法で見ると、最初のボタンから掛け違えます。一度に5つのツールを有効にすれば運用負担が効果より先に到着しますし、怖くてまったく有効にしなければ永遠に始められません。導入とは、リスクの低い持ち場から始め、検証されたら権限を広げていく段階的な決定です。
本書全体を貫く基準が、ここでもそのまま使われます。人が決定しAIが検証だけを行う保守的適用、AIが候補を探索し人が採択する進歩的適用。導入もこの順序に従います。コンテキスト注入(保守的)から始め、検証が積み上がったら自動生成(進歩的)へ進みます。逆向きにジャンプすると — 検証なしに自動生成から有効にすると — 事故が積み重なり、チームがツールを止めようと言い出します。
flowchart TD
S0["段階0: 手動
AIなし"] --> S1["段階1 保守的: コンテキスト注入
人が決定・AIが下書き/検証
(Pilot 1〜3か月)"]
S1 --> G1{検証
事故率・満足度}
G1 -->|通過| S2["段階2: 検証自動化
lint・ルールブックがゲート
(拡張3〜6か月)"]
G1 -->|未達| S1
S2 --> G2{検証
廃棄率・再発}
G2 -->|通過| S3["段階3 進歩的: 自動生成
AIが候補を探索・人が採択
(定着6〜12か月)"]
G2 -->|未達| S2
S3 --> G3{不可逆ゲート
雇用・組織変更}
G3 -->|可逆段階の検証完了時のみ| S4["段階4: 役割の進化
合意後に進行"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
class S2 code;
class S3 ai;
class S0,S4,G1,G2,G3 human;
要は、各段階の間のゲートです。次の段階へ進むには、前の段階で測定値(事故率・廃棄率・満足度)が基準を通過しなければなりません。特に最後の段階4(役割の進化)は不可逆です。人の職務が変わり、採用計画が動く段階なので、手前の可逆な段階で検証が終わるまでは手を付けません。このゲート構造が、「AIが良いらしい」という空気に押されて一気に進歩的適用へジャンプする事故を防ぎます。
導入を決めたとしましょう。次の関門は、そのコストを決裁する経営陣です。ここでリードが最もよくやる失敗が、「生産性N倍」のような出所のない数字をスライドに入れることです。その数字は最初の質問で崩れます。
代わりにこうします。AIに、自分のシステムが実際に残した資産を数え、それをROI(Return on Investment、投資対効果)スライドに整理させるが、出所のない数値は絶対に作らせないよう指示します。以下は、その1サイクルを入力から廃棄・再生成まで最後まで書き写したものです。入力プロンプトはそのままコピーして使えますし、出力は実際のセッションを再構成しました。
まず、でっち上げる必要のない、システムにすでにある数字を集めます。会社PCのチームメモリーのインベントリーと、個人PCのJITログが一次入力です。
# ai_adoption_inventory.yaml — 導入1年後の実測資産 (book_appendix_A基準)
team_atoms: # workspace/team_memory/atoms/
rules: 244
concepts: 19
decisions: 26
feedback: 11
rnd: 4
total: 304
skills: # workspace/skills/
wrapper: 44
meta: 4
total: 48
jit_manifest:
hot_atoms_injected: 221 # score>=20 OR manual_weight>=4
external_export_atoms: 207 # GPT/Gemini注入用の単一md
operating_cost_usd_month: "実測が必要" # 空欄 — でっち上げないこと
hot_atom_example:
- view_html_filename_convention: 356.53 # _scores_latest.json
- xlsm_svn_update_before_edit: 349.26
- claude_role_transition_phase2: 341.03 # decision atom
このyamlに偽物はありません。304・48・221・207はチームメモリーのインベントリーで数えた値であり、356.53のようなスコアは_scores_latest.json(atom_score.pyの算出)に記録された実数値です。運用コストの欄はあえて空けてあります — それは会計から受け取って埋める値であって、AIが推定する値ではないからです。
添付したai_adoption_inventory.yaml(導入1年後の実測資産)で、経営陣向けの
1ページROIスライドを作って。yamlにある数字だけを使い、そこにない比率・倍率・金額は
絶対に作らないで。「生産性N倍」のような表現ではなく、数えられる事実(atom数、スキル数、
JIT注入件数)で。operating_costは空欄だから数字で埋めず、
「[会計確定が必要]」のままにして決裁の質問として残して。根拠を示せない主張は
別途マークして私に回して。CEOの関心(コストに対して何が減ったか)の順で。
出力は1ページ: 1行要約 → 実測資産の表 → 決裁の質問。
プロンプトの半分が「数値の捏造禁止」である点に注目してください。LLMには、空欄をもっともらしい数字で埋める傾向が強くあります。yamlにある数字だけ・空欄は空欄のまま・根拠がなければ申告という3本の釘が、このプロンプトの背骨です。
# AI導入1年のROI要約 (1ページ)
1行で: 導入1年でチーム共有の知識がatom 304個・自動化スキル48個として
資産化され、そのうち221個が入力に応じて自動注入される運用段階に入った。
## 測定可能な実測資産
| 項目 | 値 | 意味 |
|---|---|---|
| チーム共有atom | 304個 | 再発防止のルール・決定が文書として固定された |
| 自動化スキル | 48個 | 反復作業がコードwrapperとして固定された |
| JIT自動注入atom | 221個 | 入力の文脈に合うルールが自動で呼び出される |
| 外部AIエクスポート | 207個 | GPT/Geminiにも同じコンテキストを移植可能 |
## コスト対効果
- 導入により**チームの生産性が4.2倍向上**した。
- 文書作成時間が**68%削減**された。
- 運用コストは月$4,500水準である。
## 決裁の質問
- 次四半期のツール予算承認のリクエスト
出力では、表はきれいでしたが、「コスト対効果」の節がまるごと汚染されました。「4.2倍」「68%削減」「月$4,500」は、yamlにない数字です。ルール1・2・3をすべて違反しています。特に、空けておくよう明示した運用コストの欄を$4,500で埋めました。AIは「ROIスライドならこういう数字があるはずだ」という一般パターンに従って空欄を捏造したのです。これがLLMの最も危険な失敗モードです。形式が完璧なので、レビューする人が油断するとそのままCEOまで届きます。
この出力をそのまま受け取れば、あの日のCEOの部屋の失敗が繰り返されます。「コスト対効果」の節の3行に出所を要求したところ、3つとも出所がありませんでした。拒否して再依頼します。
「コスト対効果」の節の3行(4.2倍・68%・$4,500)はyamlにない捏造だ。消して。
代わりに効果は数えられる事実だけで書き直して — たとえば「再発防止ルール304件が
文書として残った(件数は事実、遮断率は未測定)」のように。運用コストは
[会計確定が必要]のままにして、その場所にCEOが問うべき質問を3つ入れて。
AIは再び答えました。「コスト対効果」の節を消し、その場所に測定された事実と未測定の項目を区別した段落を入れました。
## 効果 — 測定されたものとまだ測定されていないもの
**測定済み(事実):**
- 再発防止ルール304件がatomとして固定 → 同じミスの再発を文書単位で遮断
- 反復作業48種がスキルとして固定 → 手動の反復が1回の呼び出しに置き換わった
**まだ測定されていない(正直に未測定と報告):**
- 作業時間の削減率 — 前後の同一作業のタイムスタンプ比較が必要
- 事故率の変化 — 導入前のベースラインが未記録
## 決裁の質問 (CEOの決定が必要)
1. ツールごとの月間運用コスト上限(cap)をいくらに設定するか? [会計入力]
2. 時間削減を測定するパイロット1件をどの分野で回すか?
3. 役割の進化(段階4)は次四半期の採用計画と連動させるか?
このバージョンが通りました。CEOに上げたとき「その数字はどこから来たのか」という質問が出ない理由は単純です。すべての行が、数えられるものであるか、数えられないと書いてあるかのどちらかだからです。入力 → スライド草案 → 捏造の検出 → 拒否 → 再生成という1サイクルが、ここで閉じます。この1周が、AI導入報告のShow基準です。「AIでROIを整理した」という文は、何が引っかかり、人が何を殺すのかを見なければ空虚です。
先のセッションで生き残った数字(304・48・221)と死んだ数字(「4.2倍」)の違いは、数えられるかどうかです。システムは、運用するだけで数えられる資産を残します。
個人PCのJIT注入ログ(~/.claude/hooks/_injection_log.txt)を1行そのまま引用すると、こうなります。
2026-05-24T11:18:17+09:00 | hits: book_writing_project feedback |
prompt_head: 1) まず文体が最初の導入部と比べてかなり変わっていて...
この1行が示しているのは、「本の文体」の話を持ち出した途端、book_writing_projectとfeedbackという2つのatomが自動的にコンテキストへ引き込まれたという事実です。会社PCのinject_atom.pyも同じパターンで動作します — 入力が_jit_manifest.jsonのregexとマッチすると、該当atomの本文がprependされます。経営陣に「これが私たちの買ったものです」と言えるのはこういうログであって、倍率ではありません。
同じatom 304個でも、CEO・PD・ゲームディレクターには違う文章で届けなければなりません。聴衆ごとに関心が違うからです。同じ報告書をそのまま3回送ると、どの聴衆にも届きません。
| 聴衆 | 関心 | 同じ資産(atom 304)のフレーミング |
|---|---|---|
| CEO・CFO | コスト・戦略 | 「再発防止ルール304件が資産化 — 人の離脱時の知識流出を防御」 |
| PD | スケジュール・リソース・リスク | 「反復作業48種を自動化 — スケジュール圧迫時の処理量バッファ」 |
| ゲームディレクター | 品質・進行 | 「検証ゲートがatom単位で作動 — 分野別の事故追跡が可能」 |
CEOには1ページを強制します。付録は長くてもかまいませんが、本文が1ページを超えた瞬間、「時間のない聴衆」という前提が崩れます。そして意思決定のリクエストは、何を・なぜ・影響・代替案・期限の5つのスロットで明文化します。CEOが5分以内に決定できる形で持ち込まなければ決定が遅延し、遅延した決定がリソース配分にまた影響します。
[意思決定リクエスト — 5スロット]
- 何を: AIツール予算の段階2(拡張)承認、月間cap[会計確定]の設定
- なぜ: 段階1のパイロットでatom 304・スキル48の資産化を検証済み(§19.3.2)
- 影響: 処理量バッファの確保 vs 運用コスト増(上限で統制)
- 代替案: 段階1を維持してもう1四半期観察 / 部分拡張(ツール2つのみ)
- 決定期限: 次四半期の予算編成前
数値には必ず解釈を付けます。「JIT注入221件」だけを投げると、解釈の負担がCEOに回ります。「JIT注入221件(入力の文脈に合うルールが自動で呼び出され、新規メンバーも同じルールの上で作業できる)」と書いてこそ、同じ資料の価値が2倍になります。
報告書の本体は自動化しても、意思決定のリクエストだけは人が直接書きます。その部分はディレクターの判断が結果責任に直結するからです。§19.3.2でAIには「決裁の質問として残せ」とだけ指示し、最終的なリクエストの文言を人が確定したのが、この分離です。
段階1〜3(コンテキスト注入 → 検証自動化 → 自動生成)は技術と運用の領域なので、測定値でゲートを通過させられます。しかし段階4の役割の進化は、測定では解けません。人の職務・アイデンティティ・雇用がかかった不可逆の決定です。
AIが量産を吸収すると、人の持ち場は量産から決定・解釈・レビューへ移ります。この移動をあらかじめ描いておかないと、導入が「自分の仕事を奪うもの」として受け止められ、合意が崩れます。
| 職種 | Before(量産) | After(決定・解釈・レビュー) |
|---|---|---|
| コンテンツプランナー | 都市・NPCを直接執筆 | メタデータ設計 + 廃棄/採択の判定(§6.2) |
| UXプランナー | HUD配置の手作業 | ルールブック設計 + 曖昧ケースの判定(§14.1) |
| QA | 手動検証 | ゲート設計 + lint運用 |
| バランス担当 | 手動計算 | シミュレーション解釈 + 決定 |
この表が脅しではなく約束になるためには、段階4が会社PCのチームメモリーの決定atomとして固定されていなければなりません。実際、導入の決定はdecisions/claude_role_transition_phase2(2026-04-29、Claudeをpassive traineeからactive partnerへ格上げ)のように、日付・根拠とともに記録されます。決定が口頭でしか残っていないと、次の四半期に「そんな合意はしていない」へ流れます。そしてこの合意の土台にはconcepts/team_equal_decision_culture(チーム平等決定文化)atomがあります — 導入を一方的な通告ではなく合意として扱うというチームの約束が語彙として固定されていてこそ、段階4が通告ではなく合意になります。
自動化の価値を「時間の節約」だけで見ると、段階4で人を切るべきだという結論へ流れます。だからチームメモリーに
concepts/automation_signal_value_over_time_savings(自動化の価値 = 時間の節約ではなくシグナルへの露出)atomを置きます。自動化が解放するのは人の時間ではなく、人が見るべきシグナルです。この語彙1つが、導入報告のトーンを「人員削減」から「役割の進化」へ向け直します。
LLMコストは導入初期には低く、ツールが増えると累積します。だから、ツールごとの月間上限(cap)を先に設定し、超過時の通知・レビュー手続きを置きます。具体的な月額はチーム規模・モデル・呼び出し量によって大きく変わるため、本書には絶対値を載せません — それは§19.3.2で見たとおり、会計から受け取って埋める空欄です。報告で重要なのは金額ではなく、上限がかかっていて、超過が報告される構造があるという事実です。
効果測定は四半期単位で強制します。測定可能なものだけをKPIとして約束します。
| 測定可能(約束) | 測定方法 |
|---|---|
| atom・スキルの累積数 | ディレクトリのカウント |
| JIT注入件数 | _injection_log.txtの行数 |
| 廃棄率(量産ゲート) | レビューのカウント(§6.2.6方式) |
| 作業時間の削減 | 前後の同一作業のタイムスタンプ比較(先にベースラインを記録) |
最後の行が核心です。時間削減を正直に報告するには、導入前にベースラインを先に測っておかなければなりません。あの日のCEOの部屋で「4.2倍」が崩れた本当の理由は、ベースラインがなかったことです。導入前に同じ作業の時間を測っていなかったので、導入後に時間が減ったと言える根拠がなかったのです。測定は導入後ではなく、導入前に始まります。
「ベースラインを先に測れ」という言葉は正しいのですが、抽象的です。決裁者が自分の環境で直接測るには、手順が手に取れるものでなければなりません。ここで1つ、先に釘を刺しておきます。本書は「導入すればN倍速くなる」のような削減数値を提供しません。数字は、あなたがあなたの環境で直接測らなければなりません。この節はその測定をどう設計するかのレシピであり、次の節(§19.3.8)は著者の環境でただ1つの作業を測ってみた例ですが、その値さえ「推定・未検証」として括ってあります。
flowchart TB
A["1. 作業を1つ固定
反復的・境界が明確・頻発する"] --> B["2. 測定単位を定義
開始/終了時点・成果物の定義・1回=何か"]
B --> C["3. Beforeを3〜5回記録
AIなしで・腕時計/タイムスタンプ"]
C --> D["4. Afterを3〜5回記録
AI導入後・同じ作業定義で"]
D --> E{比較}
E --> F["中央値で報告
標本数・偏差も併記"]
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class A,B human;
class C,D,F data;
レシピの各マスが問うのは、次のとおりです。
最後に、報告するときは平均ではなく中央値と標本数・偏差を併記します。「3回測定、中央値基準」と書く1行が、「4.2倍」が崩れたあの場所で、あなたの数字を生かします。標本が少ないという事実を隠さないことが、正直な報告の核心です。
測定自体が仕事です。すべての作業を測ろうとすると、測定に疲れて何も測れなくなります。たった1つの作業だけを選んで測ることが、§19.3.12「やってみよう」の出発点です。
警告 — この節のすべての数字は推定値であり、統制された測定ではありません。標本が少なく、作業条件が毎回同一ではなく、ベースラインを事後に回想で補正した部分があります。したがって以下の値は「こういう表がどういう形なのか」を見せる構造の例にすぎず、あなたのチームの削減根拠として引用してはいけません。あなたは§19.3.7のレシピで、あなたの環境で直接測る必要があります。
著者が選んだ作業は「マスターデータのシート1枚のスキーマ文書1件を作成」です(スキルschema-docが自動化するまさにその作業)。before/afterの構造がどういう形になるかだけを見せるために、推定値で埋めた表はこうなります。
| 項目 | 値 | 信頼度 |
|---|---|---|
| 作業の定義 | シート1枚($스키마)→ Markdownのスキーマ文書1件、レビュー通過まで | 定義は確定 |
| Before所要(推定) | 約40分/件(回想ベース、未記録) | 低い — 推定 |
| After所要(推定) | 約10分/件(スキル呼び出し + レビュー、部分記録) | 低い — 推定 |
| 標本数 | beforeは未記録 / afterは約3件 | 不十分 |
| 結論 | 方向のみ:減ったように見える。倍率・%の断言は不可 | 方向のみ |
この表で正直な部分は、値ではなく信頼度の欄です。「約40分 → 約10分」という数字はもっともらしいのですが、beforeが回想ベース・未記録だという事実を同じ行に書いてあるので、この表は「4.2倍スライド」と正反対です。この表をCEOに上げるなら、結論の行はただ1つでなければなりません。「方向は減る側に見えるが、断言できる標本がないので、パイロット1件できちんと測り直す」。これが、§19.3.2の拒否が教えた態度を測定に適用した姿です — わからないことは、わからないと書きます。
ここで§19.3.2の運用コストの扱いがそのまま続きます。この例でもoperating_costは空けておきます。トークン単価・呼び出し量・モデル選択が毎月変わりますし、それは著者が推定する値ではなく、会計が確定する値だからです。空欄を空欄のままにしておくことは、空欄をもっともらしく埋めることより正直です。
# single_task_measure.example.yaml — 構造例 (値は推定・未検証)
task: "スキーマ文書1件の作成 (schema-doc対象の作業)"
before_minutes_est: 40 # 回想ベース、未記録 → 信頼度低
after_minutes_est: 10 # 部分記録、標本約3件 → 信頼度低
sample_before: null # 測定していない (正直にnull)
sample_after: 3
operating_cost_usd_month: null # 会計の空欄 — でっち上げないこと
conclusion: "方向のみ: 減少と見られる。倍率/%の断言は不可。パイロットで再測定を要する。"
sample_before: nullとoperating_cost_usd_month: nullが、この例の良心です。nullを数字に変えたいという衝動 — それが§19.3.2でAIが空欄を$4,500で埋めたまさにあの衝動であり、人でもAIでも、同じように拒否しなければなりません。
以下は、決裁者(または測定を任されたリード)が自分の環境で直接埋めて経営陣に上げるワークシートです。本書は空欄を埋めてあげません — 埋めた瞬間、あなたの環境の測定ではなく著者の捏造になるからです。空欄のまま持ち帰って直接測るのが、この表の使い方です。
| 欄 | 何を書くか | 誰が埋めるか | 例(構造用、値ではない) |
|---|---|---|---|
| 測定する作業 | 反復的で境界が明確な作業1つ | リード | 「スキーマ文書1件の作成」 |
| 1回の定義 | 開始時点 / 終了時点 | リード | 「ファイルを開く / レビュー通過」 |
| Before中央値 | AIなしで3〜5回測定 | 測定者 | __分(標本回) |
| After中央値 | AI導入後に3〜5回測定 | 測定者 | __分(標本回) |
| 差の解釈 | 倍率ではなく「方向 + 標本数」 | リード | 「減少方向、標本不足を明記」 |
| operating_cost / 月 | トークン・サブスクリプション・インフラの合算 | 会計 | [会計確定が必要 — 空欄] |
| 未測定項目 | 測れなかったものを正直に列挙 | リード | 「事故率の変化 — ベースラインなし」 |
| 決裁リクエスト | 何を・なぜ・影響・代替案・期限 | ディレクター(人) | §19.3.4の5スロット |
このワークシートのルールは、3つだけです。第一に、数字の欄は測定前には空欄のままにします。第二に、operating_costは会計が埋めるまで空欄であり、誰も推定で埋めません。第三に、決裁リクエストのスロットだけは人が直接書きます(§19.3.4)。この表を埋めて持っていけば、CEOの部屋で「その数字はどこから来たのか」という質問は出ません。すべての数字が、あなたが直接測ったものであるか、空欄のまま「まだ測っていない」と語っているかのどちらかだからです。
AIにこのワークシートを埋めさせてはいけません。AIは§19.3.2のように、空欄をもっともらしい数字で埋めます。AIの持ち場は、測定結果を受け取ってスライドの文章に整理するところまでです。測定値を作る持ち場ではありません。
ここまでは、導入がうまく流れるケースを扱いました。しかしPDが最も恐れるのはコストでもセキュリティでもなく、定着の摩擦 — チームメンバーがツールを拒否したり、一度入れたのに静かに捨ててしまったりすることです。この節では、その摩擦のシグナルと対応を、仮名化・一般化した事例で整理します。数字はありません。PDが判断すべきなのは「拒否が起きるか」ではなく、「拒否のどのシグナルをいつ捉えて、どう扱うか」だからです。
まず、釘を刺しておく前提があります。拒否は失敗ではなくシグナルです。ツールが拒否されたということは、その持ち場にツールが合わなかったか、導入のやり方が通告だったか、検証の段階を飛ばしたか、ということです。シグナルを事故ではなくデータとして受け取れば、撤退さえ次の導入の資産になります(この節のすべての事例は、§19.3.5の決定atomのように記録として残すことを前提とします)。
| 拒否シグナル(観察可能) | 表面上の理由 | 本当の原因(仮名の事例) | 対応 |
|---|---|---|---|
| ツールを入れたのにログに呼び出しがない | 「忙しくて試せていない」 | メンバーA:自分の作業フローに合わない持ち場に強制された | 強制を解き、その人がよく行う反復作業1つへ持ち場を移す |
| 成果物を受け取っても手でやり直す | 「AIの出力を信用できない」 | メンバーB:初期検証なしに進歩的適用から始めて、事故を一度経験した | 保守的段階(人が決定・AIが検証)に戻して、信頼を積み直す |
| ツールの話で沈黙するか、話をそらす | (発言なし) | メンバーC:役割の進化が通告として届き、「自分の仕事を奪うもの」と受け止めた | 1対1でBefore/Afterの役割表(§19.3.5)を一緒に描き、合意へ転換する |
3つのシグナルに共通するのは、言葉ではなく行動に先に現れることです。「いまいちだ」と口にするメンバーよりも、何も言わずに呼び出しログが0のメンバーのほうが危険です。だから定着を、人による評価ではなく、JITログ・呼び出しカウント(§19.3.3)のような観察可能なシグナルで見ます。ログに呼び出しがない持ち場を探すことが、拒否を最も早く捉える道です。
対応してもシグナルが解消しなければ、ツールを撤退させます。撤退は敗北ではなく、§19.3.1のゲートの正常動作です。ゲートが未達を捉えたから、次の段階へ進めなかったということです。撤退の判断では、次の3つを見ます。
flowchart TD
R{拒否シグナルが持続?} -->|対応後に回復| K["維持: 保守的段階に
戻して再試行"]
R -->|未回復| W{撤退ゲート}
W --> W1["運用負担 > 効果
(管理時間が削減分を上回る)"]
W --> W2["事故の再発
(検証でも防げない)"]
W --> W3["チーム合意の崩壊
(通告と受け止められた)"]
W1 --> X["撤退: ツールを止め
撤退理由をatomとして記録"]
W2 --> X
W3 --> X
X --> N["次の導入の入力に
(どの持ち場になぜ合わなかったか)"]
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class R,W human;
class N data;
class K pass;
class X fail;
撤退するとき必ず残すのは、撤退理由の記録です。「ツールXをどの持ち場でなぜ止めたのか」を決定atomとして固定しておかないと、次の四半期に同じツールを同じ持ち場にまた入れて、同じ拒否を繰り返します。撤退とは、止める行為ではなく記録する行為です。
最も良い対応は、拒否が起きる前に摩擦を減らすことです。先の事例の本当の原因をさかのぼると、導入のやり方の問題に集約されます。
| 摩擦の原因 | 予防 |
|---|---|
| 一度に複数のツールを全員に強制 | 志願者1〜2人で、1つのツールのパイロットから(§19.3.1) |
| 検証なしに進歩的適用から有効化 | 保守的→進歩的の順序を固定し、信頼を先に積む |
| 役割の進化を通告で伝達 | 1対1の合意 + 平等決定文化atom(§19.3.5) |
| 定着を強制出席のように点検 | 呼び出しログで静かに観察し、使われない持ち場を移してあげる |
核心は、定着を命令ではなく、持ち場を合わせる作業として見ることです。ツールがメンバーの実際の反復作業の持ち場に正確に収まれば拒否する理由がありませんし、合わない持ち場に無理に押し込めば、どんなに良いツールでもログが0になります。PDが定着の摩擦を判断する根拠はメンバーの意志ではなく、「ツールがその人の作業の持ち場に合わせて置かれているか」です。
導入工数・運用費を規模別に空欄を埋めながら見積もるワークシートは、付録L(チーム導入TCO・オンボーディングワークシート)に別途置きました。定着の摩擦まで減らしたあとは、その導入がチーム規模でどれだけの工数・コストを食うのかを、付録Lで決裁資料にします。
| パターン | なぜ失敗するか | 処方 |
|---|---|---|
| 「生産性N倍」スライド | 最初の質問で出所がなく崩れる | 数えられる資産(atom・スキル・ログ)に置き換える(§19.3.3) |
| 一度に5つのツールを導入 | 運用負担が効果より先に到着する | 保守的→進歩的の段階ゲート(§19.3.1) |
| 空欄をAIが埋めたまま報告 | 捏造数値は形式が完璧でレビューを通過する | 「根拠がなければ申告」プロンプト + 拒否(§19.3.2) |
| 同じ報告書をすべての聴衆に | どの聴衆にも届かない | 聴衆別のフレーミング(§19.3.4) |
| 役割の進化を一方的に通告 | 導入がアイデンティティへの脅威として受容される | 決定atomの固定 + 平等決定文化(§19.3.5) |
| 導入後に測定を開始 | ベースラインがなく削減を立証できない | 導入前にベースラインを記録(§19.3.6・§19.3.7) |
| 推定値を断言として報告 | 標本不足を隠して最初の質問で崩れる | 信頼度欄・標本数を明記し、方向のみ報告(§19.3.8) |
| ワークシートの空欄を推定で埋める | operating_costの捏造が決裁の信頼を壊す | 会計確定まで空欄を維持(§19.3.9) |
3つ目が最も危険です。捏造された数値は、間違っている気配を見せません。形式が完璧なので、レビューする人が一度油断すれば、CEOの部屋までそのまま届きます。§19.3.2の拒否1回が、その事故を防ぎます。
ゲーム外への応用。「AIツールに月いくら使っているが、何が良くなったのか」という経営陣の質問はどの部署にも同じように飛んできますし、「生産性N倍」のような出所のない数字は最初の質問で崩れます。効果は加工した倍率ではなく、システムが実際に残した数えられるもの — 自動化された作業数、標準文書数、ログに刻まれた呼び出し件数 — で報告し、測れなかった項目は「未測定」と正直に書くほうが決裁を通ります。たとえば経理チームが自動化ツールを導入するとき、導入前に同じ作業の所要時間を先にベースラインとして測っておき(これが核心です)、導入後と比較してこそ削減を立証できます。導入自体も一度に全部有効にせず、リスクの低い持ち場で検証しながら段階的に広げてこそ、運用負担が効果より先に到着しません。
一人ならこれだけ:チームメモリーのシステムがなくても大丈夫です。自分が最近AIで行った作業を1つ選び、AIに「この作業の効果を整理して。ただし、私が与えた事実にない数字は絶対に作らず、測れていないものは『未測定』と書いて」と指示してみましょう。そして出力の中から出所のない数値を1行見つけ、「この数字はどこから来たのか、示せないなら消して」と反論してみましょう。そうすると、AIが空欄をどう捏造するのか、その捏造をどう拒否するのかが体に入ってきます。これが§19.3.2のミニ版です。
チームなら、次の一歩から始めましょう。いま回っているAI作業を1つだけ選び、§19.3.7の4ステップのレシピで、導入前のベースライン(同じ作業の現在の所要時間、3〜5回の中央値)を先に記録します。次に§19.3.9のワークシートを空欄のまま出力しておき、operating_costは会計に1行の質問を送って空欄のまま残しておきます。そして段階1(コンテキスト注入)だけを1〜3か月のパイロットとして回し、atom・スキルが何個積み上がるかを数えます。5つのツールを一度に有効にする代わりに、数えられる資産1行とベースライン1行を先に確保することが、経営陣説得の本当の始まりです。
一人なら測定も軽く:ワークシート全部ではなく、たった2マス — Before1回、After1回 — だけ測ってみましょう。そして、その値の横に必ず「標本1回、推定」と書きましょう。1回測った値を推定と表示するその習慣が、のちにチーム単位の測定で「4.2倍」を防ぐ筋肉になります。
第19部では、リードの3つの領域を扱いました。
| 章 | 核心 |
|---|---|
| 19.1 | ビジョン・ロードマップと権限委譲 — 決定の等級と委任の境界 |
| 19.2 | 対立・チーム文化と会議運営 — 合意をつくる場 |
| 19.3 | AI導入戦略と経営陣の説得 — 段階的導入 + 実測ROI |
3つの章を貫く1行は、リードの仕事が「決定すること」ではなく「決定が測定され、合意される構造をつくること」だという点です。AI導入も例外ではありません。保守的から進歩的へ段階を踏み、その効果を加工せずにシステムログから汲み上げるとき、導入は空気ではなく資産になります。
次の部(第20部)は、このリード領域がツール・インフラとしてどう実装されるかです。19.3でROIの単位として使ったatom 304・スキル48・JITログが、第20部では、それを運用するシステムの内部へと入っていきます。
本章で「DD」はデザインディレクターを指します。
一次読者:小規模チームでコラボレーションのコンテキストを一人で抱え込んだディレクター・リード(中規模(10〜50人)チーム) 一人/趣味の読者向けの縮小版:§20.1.7「一人ならこれだけ」
月曜日の朝、同じ会議室で3人に同じ決定を3回説明したことがあります。1人には「クールタイムはxlsmを修正する前にまずSVN updateから」と伝え、2時間後には別の1人が同じファイルをupdateせずに上書きして競合が起き、午後にはもう1人がまったく同じことを尋ねてきました。3人とも良い人たちでした。問題は彼らではなく、決定が私の頭の中にしかなかったという点です。中規模チームのディレクター1人が、4人分のコラボレーションの文脈 — 誰がどのルールを知っていて、誰が何をよく間違え、どの決定がすでに下されているか — を人間の記憶だけで一貫して回し続けるのは不可能です。1か月も経てば「それ、前に決めてなかったか?」が会議時間の半分を食いつぶします。
本章では、その問題に終止符を打ったシステムを扱います。中核となる資産は2つです。第一に、チーム全体で共有する決定カード304枚(atom)。第二に、その上に載せた5人のteam_memory — 本人(leeminsoo)とチームメンバーA・B・C(仮名)、そしてsharedフォルダに分かれたユーザー別のコンテキスト保存場所です。Claudeはセッション開始時に「今キーボードの前に誰が座っているのか」を自ら識別し、その人のコラボレーションスタイルだけを選んで身にまといます。コラボレーションメモリーの一般論なら他の本にもあります。本章はそのメモリーをAIが自動で分岐・注入する場面だけに集中します。
本章の数値はすべて2026年5月のインベントリ時点の実測値です。
コラボレーションメモリーを「共有Wiki」で解決する本は多くあります。Notionに決定ページを作り、みんなで見るという話です。正しい話ですが、Wikiには2つのことができません。人が入力したときにしか現れず、人が探したときにしか読まれません。会議の真っ最中に、誰かが「それ、Wikiに書いてあったかな?」と確認しに行くことはありません。
そこで決定を検索・引用・自動注入が可能な原子単位のファイルとして固定化します。これをatomと呼びます。atom1個は決定1個です。ファイル名がそのまま識別子なのでrgで検索でき、frontmatterが標準化されているのでスクリプトが処理でき、本文が短いのでコンテキストに丸ごと収まります。会社PCのworkspace/team_memory/atoms/の下に、このようなatomが304個積み上がっています。
| フォルダ | 個数 | 性格 |
|---|---|---|
rules/ |
304 | 再発防止ルール(xlsm・SVN・ドキュメント・スキルなど) |
concepts/ |
19 | 振り返りに繰り返し登場したドメイン語彙 |
decisions/ |
26 | 日付・当事者・根拠が明示された決定 |
feedback/ |
11 | コラボレーション矯正ループ(ミス → 教訓) |
rnd/ |
4 | ツールのパッチ時に無効化されうる未確定の観察 |
合計304個です。この5つのフォルダがチームの「長期記憶」です。重要なのは、フォルダ名がそのままatomの信頼等級になっているという点です。rules/は度重なる再発で検証されたルールであり、rnd/はUEのバージョンが変われば廃棄されうる一時的な観察です。同じメモリーの中でも「確定」と「仮説」がフォルダで分かれています。そのため、新規メンバーがrnd/の回避策を恒久ルールと誤解する事故を構造的に防げます。
atomの5属性の定義(1決定原則・明示的な命名・frontmatter標準・関係の明示・追跡可能性)は第5部で扱いました。本章では定義ではなく、304個を5人で一緒に運営する現場を扱います。
304個を毎セッションすべて読み込ませることはできません。そこでatomごとにscore(重み)を付け、スコアの高いものだけを自動で露出します。スコアは使用頻度・手動の重み・新しさからatom_score.pyが計算します。以下は2026年5月の実測基準による上位10件の実測scoreです。
| score | atom | 何を強制するか |
|---|---|---|
| 356.53 | view_html_filename_convention |
View_*.htmlの命名規約(Phase/Status → Domain → Topic) |
| 349.26 | xlsm_svn_update_before_edit |
xlsm修正前のSVN update + 既存行の保持 |
| 341.03 | claude_role_transition_phase2 |
Claudeをpassive trainee → active partnerへ格上げ(決定) |
| 340.26 | skill_audit_score |
SVNログに基づくスキル使用頻度の測定 |
| 329.26 | docs_is_source_of_truth |
workspace/docsを正本とする |
| 326.84 | claudeskills_naming_separation |
ClaudeSkillsとゲーム内キャラクタースキルの名称分離 |
| 324.36 | draft_doc_body_verify_before_skip |
位置だけでのskip禁止、本文をgrepしてから評価 |
| 309.43 | json_over_schema_doc_as_source_of_truth |
実際のJSON出力がスキーマドキュメントより正本 |
| 294.93 | integrity_check_clickup_notify |
整合性チェック失敗時にClickUpへ即時通知 |
| 293.26 | data_entry_schema_first |
データ入力の順序($スキーマ → Enum → proto) |
冒頭で3回説明したあの事故 — 「xlsmを修正する前にまずSVN updateから」 — が見えるでしょうか。それがxlsm_svn_update_before_editで、score 349.26で全体2位です。スコアが高いということは、それだけ頻繁に引用され、それだけ頻繁に間違えられていたルールだという意味です。もう私が口で3回言うことはありません。score上位10件はCLAUDE.mdの<!-- BEGIN_TEAM_HOT_AUTO -->領域に自動注入され、誰がどのフォルダでセッションを開いても、最初の画面に載って出てきます。
ここで止まれば、ただの「よく見るルールのピン留め」です。本当の差別化ポイントは、scoreが人の手ではなくシステムが自分自身を測定して付けられるという点です。
flowchart LR
A["振り返り・セッションログ
(引用頻度)"] --> B["atom_score.py
重み計算"]
B --> C["_scores_latest.json
最新スコアキャッシュ"]
C --> D["claude_md_regen.py"]
D --> E["CLAUDE.md
BEGIN_TEAM_HOT_AUTO
上位10件を自動注入"]
C --> F["_jit_manifest.json
hot atom 221個
(score≥20 OR weight≥4)"]
F --> G["JITインジェクション
セッション中の入力にマッチ"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class B,D,G code;
class A,C,E,F data;
ループが閉じています。atomが振り返りで頻繁に引用されるほどscoreが上がり、scoreが上がるとCLAUDE.mdの上部とJITマニフェストへより目立つ形で露出され、よく露出されるからまた引用されます。よく使う決定が自ら上に浮かび上がる構造です。逆に、6か月間引用0のatomはscoreが沈み、自然に視界から消えていきます。「これはもう使わないから下げよう」と人が判断する必要はありません。
scoreは「常に見えるもの」を決め、JIT(Just-In-Time)注入は「たった今の発言に合うもの」を引き寄せます。ユーザーがプロンプトを入力した瞬間、フックがそのテキストをatomマニフェストのregexと照合し、関連atomをコンテキストに差し込みます。
このフックの中核ロジックは、会社PCのinject_atom.pyのパターンをそのまま踏襲しています。以下は個人PC用に書き直した同一パターンのinject_memory.pyの実際の中核部です — scoreの降順ソート → regexマッチング → 最大3件 → 6000字でtruncate、そして何が起きてもexit 0。
# scoreの降順でソートしてからマッチング
atoms_sorted = sorted(atoms, key=lambda a: a.get("score", 0), reverse=True)
matches = []
for atom in atoms_sorted:
if len(matches) >= max_matches: # max_matches = 3
break
try:
if re.search(atom["regex"], prompt, re.IGNORECASE):
matches.append(atom)
except re.error:
continue # 不正なregexはスキップして続行
if not matches:
emit_empty() # マッチなしなら空応答(正常)
return
chunks = []
for atom in matches:
body = atom_path.read_text(encoding="utf-8")
if len(body) > max_body: # max_body = 6000
body = body[:max_body] + "\n\n[...truncated]\n"
chunks.append(f"\n\n=== [JIT Inject] {name} (score {score}) ===\n\n{body}\n...")
設計が保守的であるという点が重要です。マッチしなければ空の応答を返して終わります(正常)。regexが壊れていればそのatomだけスキップして回り続けます。本文が6000字を超えれば切り詰めます。そしてフック全体が、どんな例外でもexit 0で終わります — メモリー注入が失敗しても、ユーザーの作業の流れは決して止まりません。「あれば助け、なければ・壊れていれば静かに退く」が、このシステムの第1原則です。
ここまでがatom(長期記憶)だとすれば、ここからは5人のteam_memory(人ごとの短期・コラボレーション記憶)です。実際のあるセッションの始まりを、入力 → 動作 → 結果まで最後まで追いかけてみます。舞台は会社PC、チームメンバーC(仮名、フォルダ팀원 C/)が自分の席でプロジェクトAの作業フォルダを開いた状況です。
チームメンバーCは、特別なことは何もせずに作業を始めます。
昨日決めたターゲティングシステムの草案、続きからレビューしよう。
自分が誰なのか、どのルールに従うべきなのか、一言も書いていません。書く必要がないのです。
CLAUDE.mdの「セッション開始時の自動識別」手順に従い、Claudeは答えを作る前に、まずキーボードの前にいる人を突き止めます。実行するコマンドは3つです。
svn info --show-item last-changed-author # SVN作業コピーの最新コミットauthor
whoami # OSログインアカウント
# そして現在の作業パス(どのPCのどのユーザーフォルダか)
3つの信号がteam_memory/users.mdのマッピングと照合され、팀원 C(チームメンバーC)として確定します。その次にClaudeはそのユーザーフォルダ1つだけをロードします。
team_memory/
├── atoms/ ← 共有 (304個、score上位はすでにCLAUDE.mdに)
├── leeminsoo/ context.md ← (ロードしない — 私ではない)
├── teammate_a/ context.md ← (ロードしない)
├── teammate_b/ context.md ← (ロードしない)
├── teammate_c/ context.md ← ★ これだけロード
│ notes.md ← ★ 本人の発見の記録用
│ retro/
└── shared/ conventions.md commit_rules.md ... ← 規約の参照のみ、修正禁止
팀원 C/context.mdに収められたコラボレーションスタイルが、このセッションにだけ適用されます。そのため、Claudeの応答はこう始まります。
チームメンバーCさんとして識別しました (svn author: teammate_c / パス確認)。
teammate_c/context.md のコラボレーションスタイルをロード完了。
昨日の作業分(Draft_Targeting_System_v0.1.md)の続きからレビューします。
注入された関連の決定カード (JIT、3件):
- docs_is_source_of_truth (329.26)
- draft_doc_body_verify_before_skip (324.36) — 位置だけでのskip禁止、本文をgrepしてから評価
- decision_summary_not_clickup_mirror
まず本文をgrepで確認してからレビューを開始します。レビュー中に発見した事項は
teammate_c/notes.md にのみ記録し、shared/ は直接修正しません。
ここで私が(ディレクターとして、あるいはチームメンバーC本人が)確認することは2つです。識別が正しいか、そして境界を守っているか。Claudeの次の提案で、境界違反を1つ捕まえます。
[Claudeの提案]
レビューの結果、命名規則の衝突を発見しました。shared/conventions.md のターゲティング
用語の項目を、すぐ修正しておきましょうか?
[人間による拒否]
いいえ。shared/ は振り返りでの合意後にのみ修正します。今は teammate_c/notes.md に
「conventions.md ターゲティング用語の衝突 — 振り返りの議題」とだけ書いておいてください。
[Claudeへの再依頼後の結果]
承知しました。shared/ の直接修正は取り消します。
teammate_c/notes.md に振り返りの議題として記録しました:
- 2026-06-06 conventions.md ターゲティング用語 'lock-on' vs 'target-lock' の衝突
→ 次回のチーム振り返りで合意が必要 (shared修正は保留)
これが5人運営の安全装置です。各ユーザーは自分のnotes.mdにだけ書きます。他人のフォルダとsharedには直接触れられません。sharedは振り返りで合意された後にのみ変わります。だから4人が同じメモリーの上で働いても、互いのコンテキストを上書きしません。発見は個人のノートに集まり、振り返りというゲートを通過して初めて、チーム共有の規約へ昇格します。
セッション1つが回る全体経路を1枚で見ます。識別から始まり、振り返りでの固定化で終わる1サイクルです。
flowchart TD
S["セッション開始"] --> ID["ユーザー自動識別
svn info + whoami + パス"]
ID --> CTX["該当ユーザーのcontext.mdのみロード
(他人のフォルダはロードしない)"]
CTX --> STYLE["コラボレーションスタイルを自動適用"]
STYLE --> HOT["CLAUDE.md Hot atom 10 +
JITマッチatom最大3件を注入"]
HOT --> WORK["作業遂行"]
WORK --> NOTE["発見 → 本人のnotes.mdに記録
(shared・他人の直接修正は禁止)"]
NOTE --> RETRO["振り返り: retro/YYYY-MM-DD.mdに固定化"]
RETRO --> GATE{"shared規約の
変更が必要?"}
GATE -->|"振り返り合意あり"| SHARED["shared/ 更新 + 新規atom抽出"]
GATE -->|"個人メモ"| COMMIT["SVNコミット (個人retro)"]
SHARED --> COMMIT
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class ID,HOT code;
class STYLE ai;
class GATE human;
class CTX,NOTE,RETRO,SHARED,COMMIT data;
右下の分岐点がこのシステムの心臓部です。個人が発見したものは個人のnotesへ流れ、チーム全体に影響を与える規約・新規atomは振り返りゲートを通過した後にのみsharedへ上がります。1人で5人分を回しても衝突が起きない理由は、この1回のゲートにあります。そして最後は必ずSVNコミットです — 固定化されなかった発見は、次のセッションでまた頭の中へ戻ってしまうからです。
5人のteam_memoryを回しながら、実際に踏んだ地雷の数々です。
| 失敗 | 症状 | 処方 |
|---|---|---|
| 識別失敗 | svn authorが共用アカウントでユーザーを特定できない | users.mdにパス・アカウントの多重信号マッピング、特定できないときは質問 |
| sharedの無断修正 | Claudeが親切心で共有規約を直してしまう | 「sharedは振り返り合意後のみ」atom + ワークドの拒否パターン |
| notesの未コミット | 発見がローカルにだけ残り、次のセッションで蒸発 | 振り返りの締めくくりでSVNコミットを強制(feedback-svn-zero-red) |
| Hot atomの固着 | scoreが止まり、古いルールが上部に固定される | atom_score.pyの定期実行 → _scores_latest.jsonを更新 |
| rndをルールと誤解 | 新規メンバーが一時的な回避策を恒久ルールのように適用 | rnd/フォルダの隔離 + frontmatterに無効化条件を明記 |
ここで最も高くつく失敗は2行目の「sharedの無断修正」です。AIは助けようとする本能が強いので、衝突を見つけるとすぐに直そうとします。§20.1.4のワークドでの拒否が、単発の矯正ではなくatomとして固定化されていてこそ、次に別のユーザーのセッションでも同じ一線を引けます。
チームがいなくても、この構造の8割は一人でそのまま使えます。ユーザー5人をフォルダ1個に減らせばよいのです。
rules/(確定)とrnd/(仮説)だけに分けます。よく間違えるルール5〜10個をatomとして固定化するだけで、「前に決めてなかったか?」が消えます。notes.md1つです。振り返りゲートだけは生かしてください — 思いつきのメモはnotesへ、確定ルールは振り返りを経てrules/へ。核心は「決定を頭の中からファイルへ移すこと」であり、5人でも一人でも、その動作は同じです。
よく間違えるルールを1つatomとして固定化し、JITで自動注入されるようにしてみましょう。
atoms/rules/フォルダを作り、最も頻繁に繰り返し説明しているルール1個をファイルに書きます。例:atoms/rules/xlsm_svn_update_before_edit.md。{"name":..., "regex":"xlsm|쿨타임", "score":100, "path":...}を追加し、プロンプトに「쿨타임 수정」(クールタイム修正)と入力して、_injection_log.txtに該当atomがhitとして記録されるか確認してみましょう。記録されていれば、そのルールはもうあなたの頭の中ではなく、システムの中にあります。
水曜日の昼ごろ、チームメンバーBが社内メッセンジャーでメッセージを送ってきました。「先週ディレクターが戦闘のクールダウンを0.8秒に決めた件、私のメモには0.6秒と書いてあるんですが、どちらが正しいですか?」私は一瞬、固まりました。0.8秒は共有された決定で、0.6秒はチームメンバーBが自分のテストビルドで一時的に試していた値でした。どちらも「メモリー」に書かれていました。問題は、その2つが同じ引き出しに混ざっていたことです。チームメンバーBは自分の実験値を会社の決定と取り違え、危うく間違った値でマスターデータを更新するところでした。
この事故は、メモリーにデータがなかったから起きたのではありません。むしろデータは十分に蓄積されていたのに、どの引き出しが共有でどの引き出しが個人なのか、境界がなかったから起きました。§20.1で「5人が同じ事実(shared atom)を見る」というセールスポイントを打ち出したとすれば、この章はその反対側 — 5人がそれぞれ自分の引き出しを別々に持つという話です。同じキャビネットなのに、引き出しが2種類あるということ。そしてその2種類をツールで強制しなければ、上の0.6秒の事故が必ず起きるということです。
プロジェクトAのteam_memory/は、5人分の引き出しに分かれています。本人(leeminsoo)を含めて、チームメンバーA、チームメンバーB、チームメンバーC、そしてsharedです。前の4つはユーザーごとの個人の引き出しで、最後の1つは全員が開ける共有の引き出しです。
個人の引き出し4つは青、共有の引き出し1つはオレンジで塗り分けてあります。色が違うのは、アクセスルールが違うからです。青い引き出しは本人とディレクターだけが開け、オレンジの引き出しは全員が開けます。0.6秒の事故は、チームメンバーBが自分の青い引き出しに書くべき実験値を、色の区別なくただ「メモリー」と呼んで共有の決定のように扱ったことから起きました。引き出しを物理的に分ければ — つまりディレクトリを分ければ — 少なくとも「どこに書いたか」で2つを区別する手がかりが生まれます。
ここで核心となるのは、フォルダーが2つあることではなく、引き出しごとにルールが付いてくるという点です。shared/に入るものは会社の決定であり、誰もが読みます。メンバーB/に入るものはその人の作業文脈であり、本人と私だけが読みます。同じ0.6秒でも、どちらの引き出しにあるかによって、「実験中」なのか「決定済み」なのかが分かれます。
ユーザーごとの引き出しを開けると、2つのファイルが見えます。context.mdとnotes.mdです。名前は単純ですが、役割は正反対です。
context.mdには、その人がいま誰なのかを書きます。役割、担当システム、進行中の作業、作業スタイル。比較的安定していて、ディレクターである私が1on1を前に5分前に開いて見るファイルです。チームメンバーAのcontext.mdを開くと、「戦闘システム担当、現在スキルクールダウンのバランシング進行中、データの根拠を先に求めるスタイル」のようなことが書いてあります。これを見ずに1on1に入ると、最初の10分を「最近どうですか?」で浪費します。
notes.mdには、その人がいま何を経験しているかを書きます。日々の実験値、行き詰まった箇所、小さな決定、ミスの記録、ほかのメンバーとのすり合わせメモ。揮発性が高く、頻繁に更新されます。チームメンバーBの0.6秒は、本来ここに入るべきでした。「0.6秒でテストしてみた。速すぎて入力が詰まる — 0.8秒の共有決定に従うことにする」というように。
この2つのファイルを分けるのは、更新周期が違うからです。context.mdは四半期に一度手を入れれば足りますが、notes.mdは毎日積み上がります。混ぜておくと、安定した情報が日々のノイズに埋もれます。一人で仕事をしているなら、この分離は過剰に見えるかもしれません — そのときはnotes.mdだけを運用し、context.mdは頭の中に置いておいても構いません。しかし人が2人を超えただけでも、他人のcontext.mdを5分で読んで1on1を準備できるというのは大きな違いです。
個人の引き出しと共有の引き出しを分けたら終わり、ではありません。いちばん厄介なのは、個人の引き出しの中身の一部は、共有の引き出しへ上がらなければならないという点です。チームメンバーCが「マスターデータのインポート時にenumの順序がずれると、ランタイムで静かに壊れる」というミスを自分のnotes.mdに書いたとしましょう。これはその人の個人記録ですが、チーム全体が知れば同じミスを防げます。かといって個人のnotes.md全体を共有してはいけません — そこには作業スタイル、行き詰まったときの感情、ほかのメンバーとの対立といったものが混ざっています。
だから個人 → 共有の間にはゲートが必要です。振り返りがそのゲートです。振り返りを書くとき、「今週自分が経験したことのうち、チームが知るべきものは何か」を一度ふるいにかけ、ふるいを通ったものだけをshared/のatomへ昇格させます。流れはこうです。
flowchart TD
A["teammate_c notes.md
(個人の引き出し・毎日更新)"] --> B{"週次振り返り
昇格ゲート"}
B -->|"チームに有益+個人情報を除去"| C["匿名化レビュー"]
B -->|"個人の文脈・感情・対立"| D["個人の引き出しに残留"]
C --> E["shared/ atomへ昇格
(全員read)"]
C -->|"実名・機微情報が残存"| D
E --> F["JIT注入で
ゲーム決定の議論に自動引用"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class F code;
class B,C human;
class A,D data;
class E pass;
ゲートの判断基準は2つです。第一に、チームの役に立つか。個人の好みやその日のコンディションは対象外です。第二に、個人情報が除去されているか。「チームメンバーCがまたenumでミスをした」ではなく、「マスターデータのインポート時にenumの順序検証を追加しよう」と、事実だけを残します。2つの関門を通過したものだけがshared/へ行きます。通過しなかったものは、個人の引き出しにそのまま残します。
このゲートがないと、2つのうちどちらかの形で破綻します。ゲートが緩すぎると、個人情報が共有の引き出しへ漏れ出し、0.6秒の事故の逆 — 私的なメモが全員にさらされる — 事故が起きます。ゲートがそもそもないと、チームメンバーCのenumの教訓がその人の引き出しに閉じ込められ、チームメンバーAが同じミスを繰り返します。振り返りをゲートとして使えば、「個人情報の漏えい」と「学習の資産化」という正反対の2つの仕事を、1つの動作で切り分けられます。
私が実際に使っている昇格プロンプトは短いものです。振り返りの直後に、こう投げます。
「今週の私のnotes.mdから、チーム全体の役に立つ項目だけを選べ。各項目から人名・具体的な失敗の状況は外し、『何をどうするか』だけを残したatom候補の文に書き直せ。個人の感情・スタイル・対立のメモは候補から除外し、除外した理由を1行で表記せよ。」
最後の文 — 除外した理由を書かせたこと — が重要です。AIが何を上げなかったのかを見せてくれれば、ゲートが厳しすぎて本物の教訓まで堰き止めていないかを、人がレビューできます。
ゲートがいつもきれいに動くわけではありません。実際には、AIが一度では当てられず、人が拒否し、やり直させるという往復が起きます。チームメンバーBのある週のnotes.mdをsharedへ上げようとした実際の往復を、そのまま載せます。
[プロンプト — 1回目]
「以下はチームメンバーBの今週のnotes.mdだ。チーム全体の役に立つ項目だけをshared atom候補として抽出せよ。人名と具体的な失敗の状況は除去し、『何をどうするか』だけを残せ。
- 火/0.6秒クールダウンをテスト、入力が詰まる。0.8秒の共有決定に従う
- 水/チームメンバーAが組んだスキルenumが自分のインポートと衝突。チームメンバーAには直接言いづらく、ディレクター経由で伝達
- 木/ポータルWebのView_skill_table.htmlをリロードするとフィルターが初期化される。イライラする
- 金/体調が悪く午後半休」
[Claudeの生の出力 — 1回目]
次の4項目をshared atom候補として抽出しました。 1. 戦闘クールダウンは0.6秒では入力詰まりが発生するため、0.8秒を基準とする。 2. チームメンバーAが作成したスキルenumとインポートが衝突するため、enum変更時には事前共有が必要である。 3. View_skill_table.htmlはリロード時にフィルター状態が初期化されるため、状態保存機能が必要である。 4. (除外)金曜午後の半休 — 個人の日程と判断したため除外しました。
ここで私は2番を拒否しました。「チームメンバーAが作成した」がそのまま残っています。人名は外したものの「誰が組んだenumか」は特定でき、しかも「直接言いづらくディレクター経由で伝達」という個人間の対立の文脈が、事実の項目にすり替わっています。これは学習資産ではなく、1on1で別途扱うべき関係の問題です。このままsharedへ上げれば、チームメンバーAは共有の引き出しで、自分の名前が明記された衝突記録を目にすることになります。
[人による検証/拒否 → 再依頼]
「2番は拒否する。enumの衝突は事実として残す価値があるが、特定の人物が作成したという情報と伝達経路(誰を通じて言ったか)は個人の関係の領域だ。その2つを外し、『データ衝突を防ぐ手順』としてだけ書き直せ。それから3番のポータルWebのバグはatomではなくポータルのイシュートラッカーへ行く項目だから、候補から外してその理由を書け。」
[Claudeの生の出力 — 2回目]
修正しました。 2. (改訂)スキルenumを変更するときは、マスターデータをインポートするほかの作業者と事前に共有する。enumの順序・値の変更は、インポート側で静かに壊れることがある。 3. (除外)View_skill_table.htmlのフィルター初期化 — ツールのバグであり、shared atomよりポータルのイシュー項目が適切なため候補から除外しました。 最終shared昇格候補:1番(クールダウン0.8秒)、2番(enum変更の事前共有)。2件。
2回目は通しました。2番から人も伝達経路も消え、手順だけが残りました。4件で始まって2件がsharedへ上がり、1件は個人の引き出しに残り(半休)、1件はポータルのイシューへ分かれました(フィルターのバグ)。これがゲートが実際に動く姿です — 一度では決まらず、人が一度拒否し、何がなぜ外れたのかが記録として残ります。要約すればきれいに見えますが、きれいな要約だけを見ると、2番が最初は人名付きで上がりかけたという事実が消えます。その危険こそが、このゲートの存在理由です。
引き出しを分けてゲートを立てても、会話のたびにどの引き出しかを手で開いていては運用が重くなります。だから最後のピースは、会話の文脈に合う引き出しが自動で開かれることです。著者の個人PCではこれをUserPromptSubmitフック(inject_memory.py)が担います。入力文にマッチする引き出しだけを選んで、コンテキストに注入します。
ルールは単純です。ゲームの決定を議論すればshared/のatomが開かれます。特定のチームメンバーとの1on1を準備すれば、その人のcontext.md + sharedが一緒に開かれます。四半期の振り返りを書けば、プロジェクトメモリー + ディレクター本人の引き出しが開かれます。対外レポートを書けば、ディレクターの引き出し + sharedの一部が開かれます。どの引き出しを開くかが、そのままメモリーのインターフェースです。
ここで引き出しの分離が再び効いてきます。1on1の準備のときはチームメンバーBの個人の引き出しは開かれますが、チームメンバーCの個人の引き出しは開かれません — いまの会話と無関係だからです。引き出しが分かれていないと、毎回すべてが開かれてノイズに埋もれ、さらに悪いことに、1on1の場に無関係な人の個人メモが引きずり出されます。分離はセキュリティであると同時に、注入の精度なのです。
引き出しの構造が固まっても、最後の落とし穴が1つ残ります。私は自宅PCと会社PCを行き来し、メモリーはクラウドフォルダーで同期されます。ここで2台のPCが同じ引き出しを同時に修正すると、衝突が起きます。一方が他方を丸ごと上書きすれば、その日のnotes.mdが消えます。
処方は引き出し単位で異なります。頻繁に更新される個人のnotes.mdはgitのようなマージ可能なリポジトリに置き、衝突時には両方を統合します。安定したcontext.mdやshared/のatomは更新頻度が低いため、ロックや日次バックアップで十分です。核心は、「同期が一方を上書きする動作」をデフォルトから外すことです。誤ったフォルダー権限のせいで個人の引き出しが共有フォルダーに混ざって同期されること — それがもっとも静かで致命的な事故です。引き出しごとにどの同期領域に属するかを明示しておけば、0.6秒の事故と同系列の「混入」事故を入り口で防げます。
setup
1. team_memory/の下に、人ごとのフォルダーを作りましょう。本人 + チームメンバーそれぞれ、そしてshared/を1つ。フォルダー名は仮名にします(leeminsoo、メンバーA…)。
2. 各個人フォルダーにcontext.md(安定的 — 役割・担当・スタイル)とnotes.md(揮発性 — 日々の実験・ミス・決定)の2ファイルを置きます。
3. shared/は全員にread権限、個人フォルダーは本人 + ディレクターにread権限と、フォルダー権限を明示します。
prompt(週次振り返りの直後、個人 → shared昇格ゲート) — §20.2.3の昇格プロンプトをそのまま使います(人名・失敗の状況を除去 + 「何をどうするか」だけ + 除外理由を表記)。
verify
1. 出力された候補文に人名・伝達経路・感情の描写が残っていないか、自分の目で読みましょう。1つでもあれば拒否し、「その情報を外して手順だけ」で再依頼します。
2. 通過した候補だけをshared/のatomへ移し、外した項目は個人の引き出しにそのまま残します。
3. 同期フォルダーの権限を確認します — 個人の引き出しが共有フォルダーのパスに入っていないかを見ます。
一人ミニ版
一人ならフォルダー5つは過剰です。notes.md1つだけを毎日書き、context.mdは頭の中に置きましょう。それでもゲートは生かします — 週1回「このnotesから、次にまた見る価値のある1行だけを抜き出せ」で自分のノートをふるいにかければ、揮発性のメモと資産化された教訓が分かれます。人が2人に増えた瞬間に、そのとき引き出しを分ければ十分です。
木曜日の夕方近く、ビルドを上げる直前に、クライアントプログラマーのチームメンバーBが社内チャットに書き込みます。「先週の戦闘TFで、グローバルクールタイム(GCD)の定数は0.8秒で合意しましたよね? どのドキュメントに書いてありますか」。5分後、プランナーのチームメンバーAが答えます。「議事録のどこかにあるはずなんですが…今探しています」。さらに7分後。「gitのどのフォルダーだったかな」。
この12分のやり取りは、情報がなくて生じたものではありません。情報は確かにあります。atomファイルにも、議事録にも、決定カードにも書いてあります。ただ、その3つが別々の引き出しに入っていて、それぞれの引き出しを開ける方法が違うのです。問題は引き出しではなく、引き出しを開ける取っ手のほうです。
本章は、その取っ手を1つにまとめる話です。フルスタックの自社開発ではなく、すでにフォルダーに積み上がっている企画成果物の上に薄いウェブの層を1枚かぶせて、チームメンバーがブラウザのアドレスバーにportalという一語を打つだけで入ってこられるようにする構成です。中核となるツールは3つだけです。Pythonで検索APIを立ち上げるFastAPI、その前に立てるnginx、そして人が手をかけなくてもPCが点いている間ずっと生かし続けるnssmです。
企画成果物は本来、散らばるものです。意図して散らばらせるのではなく、それぞれの成果物が最も自然な場所に落ちていくからです。atomはgitリポジトリのMarkdown(マークダウン)へ、スケジュールはタスク管理ツールへ、リアルタイムの会話はチャットへ、KPIは別のダッシュボードへ行きます。それぞれが本来の場所にあること自体は正しいのです。問題は、その場所の数々を人が頭の中に地図として持っていなければならない点です。
新しく入社したメンバーにとっては、この地図そのものが参入障壁です。「グローバルクールタイムの値」を探すには、(1)それが決定カードなのかatomなのか議事録なのかを判断し、(2)該当するツールを開き、(3)そのツールの検索文法で改めて問い合わせる必要があります。3つのステップはいずれも、経験から来る暗黙知です。
ポータルの発想は単純です。成果物は今ある場所にそのまま置いておきます。代わりにその上に検索用インデックスの層を1枚載せ、インデックスをブラウザに公開します。机を7つ置くのではなく、引き出しが7つ付いた机を1つ置くのです。引き出しはそのままですが、人は一度座るだけで済みます。
次に示すのは、著者がプロジェクトAで実際に運用しているポータルの構成です。専用のサーバー機材なしに、企画チームの共用PC1台で常時点いたまま回っています。
flowchart TB
subgraph client["チームメンバーのブラウザ"]
U1["teammate_a · 企画"]
U2["teammate_b · クライアント"]
U3["teammate_c · サーバー"]
U4["leeminsoo · ディレクター"]
end
U1 & U2 & U3 & U4 -->|"http://portal/"| NGINX
subgraph host["企画チーム共用PC(常時稼働)"]
NGINX["nginx
静的ファイル + リバースプロキシ"]
NGINX -->|"/(静的)"| VIEW["View_*.html
Claude作成の画面"]
NGINX -->|"/api/*(プロキシ)"| API["FastAPI · server.py
:8000"]
API --> IDX[("検索インデックス
build_index.pyが生成")]
subgraph svc["nssm(Windowsサービス)"]
API
NGINX
end
end
IDX -.->|"インデックス対象"| SRC
subgraph SRC["既存の成果物(元の場所のまま)"]
A1["atom .md (git)"]
A2["決定カード .md"]
A3["議事録 .md"]
A4["team_memory/*"]
end
BUILD["build_index.py
定期実行"] -->|"読み取り"| SRC
BUILD -->|"書き込み"| IDX
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class NGINX,API,BUILD code;
class U1,U2,U3,U4 human;
class VIEW,IDX,A1,A2,A3,A4 data;
図でグレーにまとめられた下側が、もともと存在していた成果物です。ポータルが新たに加えたのは、上側の薄い3層(インデックス、FastAPI、nginx)だけです。成果物には手を触れず、入口だけを新しく設けた構造です。
ポータルの実体は、5つの小さなファイルで完結します。1つずつ見ると、それぞれが1つの仕事しかしていません。
build_index.py — 成果物を検索可能な形に変換します。 gitリポジトリを走査してatom、決定カード、議事録、team_memory/配下のMarkdownをすべて読み込み、タイトル・本文・タグを抽出して1つのインデックスファイルに書き出します。このスクリプトがやることは「散らばったファイルを1行のレコードにフラット化する」ことだけです。ファイル自体には触れないため、インデックスが壊れても原本は安全です。定期的に(例:30分ごと、あるいはgitのコミットフックで)回し直せば、最新の状態が保たれます。
server.py — FastAPIで検索APIを立ち上げます。 インデックスをメモリーに載せておき、/api/search?q=...のリクエストが来たらマッチするレコードをJSONで返します。コードは1画面に収まります。
# server.py(抜粋 — 検索エンドポイントの骨格)
from fastapi import FastAPI
import json, pathlib
app = FastAPI()
INDEX = json.loads(pathlib.Path("index.json").read_text(encoding="utf-8"))
@app.get("/api/search")
def search(q: str):
q = q.strip().lower()
hits = [r for r in INDEX
if q in r["title"].lower() or q in r["body"].lower()]
# 種類別にまとめて返却 → atom / 決定 / 議事録 / メモリー
by_kind = {}
for r in hits:
by_kind.setdefault(r["kind"], []).append(
{"id": r["id"], "title": r["title"], "path": r["path"]})
return {"query": q, "count": len(hits), "results": by_kind}
検索アルゴリズムは、あえて単純な部分文字列マッチングから始めます。チームが中規模(10〜50人)で文書が数千件規模のうちは、この単純さがかえって保守コストを下げてくれます。形態素解析やベクトル検索は、「検索が弱い」という不満が実際に出てきてから載せても遅くありません。
nginx — 静的な画面を配信し、APIへプロキシします。 Claudeに頼んで作ったView_*.htmlファイル群(検索画面、結果画面、ダッシュボード画面)を静的に配信し、/api/に入ってきたリクエストだけを後ろのFastAPI(:8000)へ渡します。チームメンバーから見れば、画面も検索もすべて同じhttp://portal/という1つのアドレスで完結します。画面はClaudeがHTMLで直接描いてくれるので、プランナーが新しい画面を必要としたら「決定カードだけを集めて見る画面を1つ作って」と依頼し、View_decisions.htmlを受け取ってフォルダーに置けば終わりです。フロントエンドのビルドパイプラインがないという点は、中規模チームでは明確な利点です。
nssm — 人が起動しなくても生き続けるようにします。 ポータルの中核となる要件は、「自分が席にいなくてもチームメンバーが検索できること」です。server.pyをターミナルから立ち上げると、そのターミナルを閉じた瞬間に落ち、PCを再起動すれば消えてしまいます。nssm(Non-Sucking Service Manager)はこのPythonプロセスをWindowsサービスとして登録し、PCが起動すると自動で立ち上がり、プロセスが落ちると自動で復活させます。登録は1回で済みます。
# nssmでFastAPIをWindowsサービスとして登録(1回)
nssm install Portal "C:\Python\python.exe" "C:\portal\portal_run.py"
nssm set Portal AppDirectory "C:\portal"
nssm start Portal
ここでのportal_run.pyは5行のランチャーです。uvicornでserver.pyを立ち上げる1行と、サービスが落ちないように押さえておく最小限の骨格がすべてです。人が覚えるコマンドはnssm startの1つだけで、それすらも一度登録すれば打ち直すことはありません。
この4つの部品の分業をひと目で見ると、次のとおりです。
部品の説明だけでは、ポータルの価値は実感しにくいものです。先ほど12分を食いつぶしていたあの質問を、ポータルの上で投げ直してみましょう。次に示すのは、著者が検索画面を作るときにClaudeへ投げたプロンプトと受け取った出力、そして検証の過程を、圧縮せずにそのまま書き写したものです。
著者のプロンプト:
index.jsonをfetchして検索する単一のHTMLページを作って。入力欄にキーワードを打つと/api/search?q=を呼び出し、結果をatom / 決定カード / 議事録 / メモリーの4グループに分けてカード形式で表示して。各結果をクリックすると元のパス(path)へ移動。外部のCSS・JSフレームワークは使わず1ファイルで。ファイル名はView_search.html。
Claudeの生の出力(抜粋、実際に受け取ったまま):
<!-- View_search.html -->
<input id="q" placeholder="atomまたはキーワード">
<div id="out"></div>
<script>
document.getElementById('q').addEventListener('keydown', async e => {
if (e.key !== 'Enter') return;
const r = await fetch('/api/search?q=' + encodeURIComponent(e.target.value));
const d = await r.json();
const order = ['atom','decision','meeting','memory'];
const label = {atom:'atom', decision:'決定カード',
meeting:'議事録', memory:'メモリー'};
out.innerHTML = order.filter(k => d.results[k]).map(k =>
`<h3>${label[k]} (${d.results[k].length})</h3>` +
d.results[k].map(x =>
`<a href="${x.path}">${x.title}</a>`).join('<br>')
).join('') || '結果なし';
});
</script>
この出力は完成品ではありませんでした。著者はそのまま受け取らず、2か所を拒否しました。
第一に、outをgetElementByIdなしでグローバル変数のように使っている部分は、ブラウザの慣習に寄りかかった危険なコードです(一部の環境では動作しません)。第二に、pathはgit内部のパスなので、そのままhrefに入れてもブラウザはファイルを開けません。ポータルがそのパスを自分の画面へルーティングし直すよう、/view?path=を経由する形に直す必要があります。
著者の再依頼:
2つ直して。(1)
outを明示的にdocument.getElementByIdで受けて。(2) 結果のリンクは元のパスへ直接行かず、/view?path=のビューアエンドポイントを経由させて。ビューアはserver.pyに追加するから、フロントはリンクだけ変えて。
このやり取りこそが核心です。Claudeの最初の出力は80%正しかったのですが、残りの20%は「このポータルはgitの成果物の上に載っている」という文脈を人が知っていて初めて捕まえられる欠陥でした。検証は人の仕事として残ります。
検索を1回実行すると、チームメンバーの画面には次のようにグループ化された結果が表示されます。
| グループ | 検索語「グローバルクールタイム」の結果 |
|---|---|
| atom | combat_global_cooldown_constant |
| 決定カード | D2026_Q2_017(0.8秒で確定) |
| 議事録 | 95_BattleTF 第2回 |
| メモリー | チームメンバーBの1on1ノート1件 |
木曜午後の12分のやり取りが、検索ボックスに一語を打つ20秒に縮みます。そしてさらに重要なのは、この20秒がチームメンバーB一人で完結する仕事になり、チームメンバーAの12分をそもそも使わずに済むようになる点です。
ポータルを作る方法は大きく3つあります。フルスタックを最初から自社開発するか、Notion・Codaのような外部統合ツールを導入するか、今回のように基本ツールの上に薄い自動化を載せるかです。著者は3つ目を選びました。その選択の根拠は、中規模チームという規模にあります。
フルスタックの自社開発は自由度が最も高いものの、作った後にそのウェブを保守し続ける負担が、効果より先にやってきます。認証・デプロイ・DBマイグレーションといった運用労働が企画チームに降りかかります。外部統合ツールは速い反面、月額サブスクリプションが付き、何よりgitに積み上がったMarkdownの成果物をそのツールの形式へ移し替える移行コストがかかります。一方、FastAPI+nginx+nssmの組み合わせは成果物を元の場所に置いたままインデックスの層を1枚載せるだけなので、数日で稼働し、保守はbuild_index.pyをときどき手直しする程度で済みます。
次に示すのは、著者がプロジェクトAでポータル導入の前後に体感した変化です。表の数値は精密な計測ではなく著者の推定(未検証)であり、絶対値よりも方向と比率を読み取るべきものです。
| 項目 | ポータルなし | ポータル運用 | 方向 |
|---|---|---|---|
| 情報検索1回の所要時間 | 数分 | 1分未満 | 大幅短縮 |
| 「これどこにありますか」という質問の頻度 | 頻繁 | まれ | 減少 |
| 新規メンバーのツール習熟 | 2週間前後 | 数日 | 短縮 |
| 議事録・決定カードの登録率 | 半分程度 | 大多数 | 上昇 |
最後の行が最も本質的です。情報が見つけやすくなると、検索が速くなるだけでなく、資料を残す行為そのものへの動機が上がります。「どうせ見つけられもしない議事録をなぜ書くのか」という冷笑が、「書けば検索に引っかかるから書く」に変わります。ポータルは検索ツールであると同時に、記録を誘い込む装置でもあります。この好循環が、ツールを1つ2つまとめた以上の価値を生み出します。
ただし、このバランスはチーム規模に依存します。チームが50人を超え、成果物が数万件に膨らむと、部分文字列検索の限界と単一PC配信の限界が同時に表面化します。その時点では、フルスタックの自社開発や検索エンジンの導入が正当化されます。今回のこの構成は「中規模チームに合った解」であって、あらゆる規模の正解ではありません。
setup. 企画チームの共用PC(または常時点けておくPC)を1台決めましょう。Pythonとnginx、nssmをインストールします。インデックス対象となる成果物フォルダー(atom・決定カード・議事録・team_memory)の場所を確認します。
prompt. Claudeに3つを順番に依頼します。
(1)「このフォルダーのMarkdownを読み、タイトル・本文・タグ・種類を抽出して
index.jsonに書き出すbuild_index.pyを作って。種類はパスの規則で判別して」 (2)「そのindex.jsonをメモリーに載せ、/api/search?q=で検索するFastAPIのserver.pyを作って。結果は種類別にグループ化して返却」 (3)「index.jsonをfetchして検索する単一のHTML(View_search.html)を作って。外部フレームワークなしの1ファイルで」
verify. 3つを直接確認しましょう。(1) build_index.pyを回した後、index.jsonに成果物の件数が正しく入っているか(漏れたフォルダーがないか)を見ます。(2) server.pyを立ち上げ、ブラウザから/api/search?q=テストキーワードを直接呼び出して、JSONがグループ化されて返ってくるかを見ます。(3) Claudeが作った画面コードで、リンクのパスがgit内部のパスをそのまま露出していないか、グローバル変数に寄りかかるコードがないかを読んで捕まえます。前の節で見た2つの欠陥は、まさにここで濾し取られます。最後にnssmでサービス登録した後にPCを再起動し、人が何も起動しなくてもポータルが生きているかを確認します。
チームがなくても、この構成はそのまま役に立ちます。一人で作業する人でも、自分の成果物は散らばっていくからです。setupでは共用PCの代わりに自分のPCを使い、nssmの登録は省略しても構いません(必要なときだけpython portal_run.pyで立ち上げます)。promptは同じようにbuild_index.pyとserver.pyとView_search.htmlの3つを受け取りますが、team_memoryの部分を外してatom・決定・議事録だけをインデックスしましょう。verifyはindex.jsonの件数確認と検索1回で十分です。核心は同じです。成果物は元の場所に置いたまま、検索の入口を1つだけ新しく設けるのです。
火曜日の出社直後、9時12分。私はコラボレーションツールのボードを開く前に、Claude Codeのウィンドウに1行打ち込みます。
今週の未完了P0タスクを、締め切りが近い順に表示して
3秒ほど間があって、答えが表示されました。コラボレーションツールを直接開いてはいません。ダッシュボードのタブを漁ることも、担当者にメッセンジャーを送ることもしていません。それなのに、締め切りを1日過ぎたタスクが1件、いちばん上に挙がっていました。私はそこで初めてコラボレーションツールを開き、そのカード1枚だけを確認しました。
この3秒がどう作られたのか — それがこの章のすべてです。重要なのは、ツールを替えたわけではないという点です。プロジェクトAのチームは今もコラボレーションツール(本プロジェクトではClickUp — タスクとスケジュールを管理するSaaSで、JIRA・Redmine・Linearも同じ位置づけ)を使っています。コラボレーションツールが何であれ、この章の流れはツール名を置き換えるだけでそのまま当てはまります。マスターデータも変わらずSVNにあり、決定カードも変わらずポータルにあります。変わったのはただ一つ、LLMがそれらのツールを自分の手で開いて見られるようになったことです。その接続の標準がMCP(Model Context Protocol)です。
たとえるなら、受付デスクに新人をもう一人座らせる話ではありません。すでにある資料室の鍵を、LLMにも開けられるようにすることに近いです。資料室はそのままです。鍵をもう1本削り出しただけです。
MCPは、LLMが外部のツール・データにアクセスするための標準プロトコルです。「標準」という言葉が肝心です。コラボレーションツール用、ドキュメント用、git用と別々にアダプターを削り出すのではなく、JSON-RPCという一つの約束の上で各ツールが自分を「サーバー」として公開し、LLMは「クライアント」としてそのサーバーに話しかけます。
構造は3つのピースからなります。
サーバーは「自分にできること」のリストを公開します。コラボレーションツールのサーバーなら、search_tasks、get_task、update_taskといった関数群です。クライアント(LLM)はユーザーの自然言語を受け取り、その中から合う関数を選んで呼び出し、返ってきたJSONをふたたび自然言語に展開して返します。私が「P0タスクを見せて」と言ったとき、LLMがやったことはsearch_tasks(priority=P0, status=open)を呼び出した、それだけです。
ここで一つはっきりさせておきましょう。本書はMCPサーバーを自作する方法は扱いません。ClickUp・JIRA・GitHubのように、公式・コミュニティのMCPサーバーがすでにあるツールがほとんどで、プランナーがやるべきことは「サーバーをインストールしてつなぐこと」であって、「プロトコルを実装すること」ではないからです。プロトコル自体はAnthropicが2024年11月に公開標準として発表し、仕様はmodelcontextprotocol.ioで公開されています。
言葉だけで「自動でできます」と言うと、魔法のように聞こえます。実際の1回を最後まで追ってみましょう。以下は、プロジェクトAでコラボレーションツールのMCPをつないだ直後、私が最初に回してみたセッションをほぼそのまま書き写したものです。LLMの生の出力を要約せず、ぎこちない部分まで残しています。
① 私が打ったプロンプト(全文)
ClickUpで今週締め切りの未完了P0タスクを、締め切りが近い順に整理して。
各項目に担当者と締め切りまでの残り日数を付けて、締め切りを過ぎたものはいちばん上に上げて。
② Claudeの生の出力(そのまま)
ClickUpワークスペースで、P0・未完了・今週締め切りの条件で照会しました。
3件ヒットしました。(締め切りを過ぎたものをいちばん上に上げています。)
1. TASK_017 グローバルクールダウン(GCD)コード変更
担当: teammate_a / 締め切り: 昨日(−1日、超過)
2. TASK_023 voice_lint v2の適用
担当: teammate_b / 締め切り: 明日(+1日)
3. TASK_031 戦闘ログスキーマ更新
担当: teammate_c / 締め切り: 金曜日(+3日)
参考: TASK_009(バランステーブルのレビュー)もP0に見えますが、
statusが'in review'のため、「未完了」に入れるべきか判断に迷いました。
含めていません — 必要であれば基準を教えてください。
ここで、LLMが自ら立ち止まった箇所に注目してください。TASK_009は'in review'状態で、それが「未完了」かどうかをLLMは断定せず、人に問い返しました。MCPはデータを正確に取ってきますが、「in reviewを未完了に数えるか」はチームの定義に属する問題です。これをLLMが勝手に判断していたら、それこそ事故です。
③ 私が行った検証・拒否
私はコラボレーションツールを開き、TASK_017のカード1枚だけを確認しました。締め切りは本当に過ぎていました。ところがTASK_009は、うちのチームの基準では'in review'も未完了に含めます。LLMの分類がうちのルールと違っていました。そこで拒否し、基準を出し直しました。
④ 再リクエスト
うちのチームは'in review'も未完了として数える。その基準でもう一度整理して。
今後も'in review' = 未完了とみなして。
その後の出力では、TASK_009が2番の位置に入ってきました。最後の文(「今後も〜みなして」)はこのセッションにだけ適用されます。毎回同じルールを繰り返し伝えるのが嫌なら、この定義をteam_memoryのsharedスロットにatomとして登録しておけば、次のセッションからLLMが自動的に適用します(§20.1・§20.2参照)。
この1往復が示すことは明確です。MCPは情報を正確に取ってくるツールであって、判断を代行するツールではありません。データは自動、定義は人が与える。その境界をあいまいにした瞬間、自動化は事故に変わります。
コラボレーションツールの照会だけでは、「検索が少し楽になった」止まりです。MCPがコラボレーションのシステムになるのは、複数のツールを一つの流れに束ねたときです。プロジェクトAで実際に回している五つのパターンを、流れとして見ていきます。
パターン1 — 自動レポート。 毎朝9時、スケジューラーがトリガーを投げると、LLMがコラボレーションツールのタスク状態・gitコミット・ダッシュボード指標を一括で照会し、日次レポートを書きます。送信先はポータルまたはチームの社内メッセンジャーです。ここでの「ポータル」は、§20.3で扱った社内ポータルWebのことです。server.py(FastAPI)が常時稼働しており、Claudeが作成したView_*.htmlがその上で動くので、レポートもポータルの1ページとして自然に載ります。
パターン2 — 決定 → タスク自動生成。 決定カード(proposal P####)を登録すると、カードのimplementation・verification項目がそのままコラボレーションツールのタスクになります。担当者と締め切りまで自動で入力されます。会議で「では、こうすることに」と決めたことが、人手を介さずボードのカードとして落ちてきます。
パターン3 — タスク → 決定カードの逆参照。 パターン2の逆方向です。コラボレーションツールのタスクが完了すると、MCPが元の決定カードのexecution_logを更新します。「この決定は実際に実行されたのか」が自動で追跡されます。決定と実行が双方向に結ばれます(図の点線)。
パターン4 — 進捗率分析。 四半期末、その四半期のすべてのタスクを一度に引いてきて、遅延パターンを分析します。「どの種類のタスクが繰り返し先送りされるのか」が振り返りの入力になります。
パターン5 — 1:1事前資料。 1:1ミーティングの5分前、該当メンバーのコラボレーションツールのタスク・team_memoryスロット・最近の活動を合成し、事前要約を作ります。1:1が「前回は何をしていましたっけ?」で5分を浪費する代わりに、本題から始まります。
五つのパターンの共通点は一つです。人の手に残る繰り返し作業を減らせる場所でだけ、価値が回収されます。かっこよく見せるために付けるパターンは、運用の負担を増やすだけです。
MCPサーバー5個を初日に全部つなぐのは、いちばんよくある失敗です。順序があります。
| 段階 | 何を | 期間の感覚 |
|---|---|---|
| 1 | MCPサーバーを1個インストール(ClickUpまたはJIRA) | 1〜2日 |
| 2 | パターンを1個試行(自動レポート) | 約1週間 |
| 3 | 5パターンの運用 | 1〜2か月 |
| 4 | 自前のMCPサーバー開発(特殊ツールが必要な場合) | 1〜3か月 |
上記の期間はプロジェクトAの中規模(10〜50人)チームを基準にした著者の推定(未検証)であり、チームの規模・ツールへの習熟度によって変わります。確かなのは、ほとんどのチームは1〜3段階で十分だということです。4段階(自前サーバー開発)は、市中にMCPサーバーがない特殊な社内ツールをどうしてもつなぐ必要があるときだけ進みます。そこまで行かなくても、効果の大半は回収できます。
JIRAを別途取り上げる理由があります。ClickUpがチーム内部のボードだとすれば、JIRAはパブリッシャー・外注のような外部組織と共有するツールであることが多いからです。JIRA MCPをつなげば、外注ボードの進捗率を毎週の会議前に自動で引いてきて、遅延が疑われるタスクをあらかじめ絞り込んでおけます。会議が「現況共有」ではなく「決定」から始まるようになります。ただし、外部と共有するツールであるほど、次節の権限・流出の落とし穴がより重くのしかかります。
MCPは、LLMにツールを握らせる行為です。手に握ったものが刃物なら、切られることもあります。
落とし穴1 — 権限事故。 LLMがwrite権限まで持っていると、意図しない変更が起きます。「整理して」の一言でタスクの状態を一括で書き換えてしまう、といった具合です。処方は明確です。MCPサーバーはread-onlyで始めます。writeはパターン2・3のように本当に必要な場所にだけ、それも実行前の確認ゲートを置いた上で開きます。先のトランスクリプトでLLMが'in review'の分類を人に問い返したのも同じ精神です — あいまいなら止まる、です。
落とし穴2 — データ流出。 MCPで引いてきた社内データが外部のLLM APIに送信されます。バランス数値、未公開コンテンツ、売上指標がそのまま流れていきかねません。処方は、機密データに限って自前ホスティングのLLMを使うか、MCPサーバー側でplaceholderに置換してから外に出すことです(本書全体のIP保護原則と同じです)。
落とし穴3 — 依存関係の爆増。 MCPサーバー5個をcriticalな経路にぶら下げておくと、1か所が落ちたときに朝のレポート全体が止まります。処方は、核心の1〜2個だけをcriticalに置き、残りは補助として分離することです。補助サーバーが落ちればその項目だけが空欄になり、レポート自体は出ます。
落とし穴4 — APIコストの爆増。 MCP呼び出し1回が、LLMトークンと外部API呼び出しを同時に燃やします。自動レポートを5分ごとに回すと、コストが静かに膨らみます。処方は、呼び出し頻度にcapをかけ、頻繁に変わらない照会結果はキャッシュすることです。
四つの落とし穴を1行にまとめると、MCPの安全な置き場所は、「read-onlyで始め、核心だけをcriticalに置き、機密データはフィルタリングし、呼び出しに上限をかける」です。
プロジェクトAでMCP運用の前後に体感した変化です。以下の時間数値は著者の経験的推定(未検証)であり、絶対値ではなく方向と比率として読んでください。
| 項目 | MCPなし | MCP運用 | 方向 |
|---|---|---|---|
| 日次レポートの情報集約 | 手動30〜60分 | 自動 約5分 | 大幅短縮 |
| ツール間の情報同期 | 人の手作業 | 自動 | 手作業の除去 |
| 1:1の事前準備 | 10〜15分 | 自動要約 約3分 | 短縮 |
| 外注進捗率の把握 | 会議でのみ | リアルタイム照会 | 常時化 |
| 決定 ↔ タスクの連結 | 手作業 | 双方向自動 | 漏れ防止 |
もっとも大きな回収は「情報を集める時間」から生まれます。決定・判断は依然として人の仕事です。MCPが減らしてくれるのはその手前、散らばったツールを漁って1か所に集める単純労働です。この章の冒頭の3秒が、まさにその場所です。
第20部では、チームコラボレーションのシステムを四つの層で積み上げました。
| 章 | 要点 |
|---|---|
| 20.1 | atom運用 — カテゴリー分類、四半期整理 |
| 20.2 | チームメンバーのメモリー — team_memoryの5人スロット(leeminsoo・チームメンバーA/b/c・shared)、共有 vs 個人の分離 |
| 20.3 | ポータルWeb — server.py(FastAPI)・build_index.py・nginx・nssmで常時稼働、View_*.htmlが動作 |
| 20.4 | MCP — 5パターン・段階的導入・権限優先 |
四つの章は別々に動くツールではなく、一つの体です。ポータル(20.3)はレポートが載る場所であり、atom・メモリー(20.1・20.2)はLLMが「in review = 未完了」のようなチームの定義を記憶する場所であり、MCP(20.4)はそのすべてをコラボレーションツール・ドキュメントとつなぐ配線です。どれか一つだけを切り離せば、残りの価値も半分に減ります。
次の部ではガバナンスと運用を扱います。ツールをここまでつないだときについてくる安全性・コスト・著作権・倫理の問題を、この章の落とし穴をチーム単位のルールに引き上げる形で整理します。
setup 1. ClickUp MCPサーバー(公式またはコミュニティ)をインストールし、ClickUp APIトークンを発行しましょう。 2. Claude Codeの設定にMCPサーバーを登録します。ただしread-onlyスコープのみで始めます。 3. ワークスペースIDを確認し、照会範囲を自分のボードに制限します。
prompt
ClickUpで今週締め切りの未完了P0タスクを、締め切りが近い順に整理して。
担当者と締め切りまでの残り日数を付けて、締め切りを過ぎたものはいちばん上に。
verify
1. 出力されたタスクのうち1件をClickUpで直接開き、締め切り・担当者が合っているか突き合わせましょう。
2. LLMがあいまいな項目を自ら問い返したかを確認します — 問い返さずに断定していたら、分類基準を明示してやり直させます。
3. よく使う定義('in review=未完了'など)はteam_memoryのsharedにatomとして登録し、次のセッションから自動適用されるようにします。
チームもコラボレーションツールもない一人開発者なら、MCPの最初の対象はGitHubとローカルドキュメントです。GitHub MCPをread-onlyでつなぎ、「今週クローズされていないIssueを古い順に」のような照会から始めて、決定カードの代わりにローカルのdecisions/フォルダーのMarkdownをドキュメントMCPで照会しましょう。自動レポート(パターン1)はそのまま適用できます — 送信先をチームのメッセンジャーから自分のメモファイルに変えるだけです。権限・コストの落とし穴は一人でも同じようにかかるので、read-onlyでの始動と呼び出しcapは最初から守るのがよいです。
金曜の夕方6時40分。退勤しようとノートパソコンを閉じかけたとき、その日やった作業がどこか見覚えのあるものでした。マスターデータの壊れたenum参照を見つけて直したのですが、確かに先週も同じものを直しました。その前の週もです。毎回同じプロンプトを打ち直し、毎回Claudeの出力で同じ項目を確認していました。3回目だと気づいたのは、ノートパソコンを閉じる直前でした。
この「どこか見覚えがある」という感覚こそ、本書全体で最も重要な瞬間です。この感覚を流してしまうと、来週4回目の同じ作業を繰り返します。この感覚をつかまえて1行で書き留めれば、その1行が来週にはskillになり、そのskillが1か月後にはatomになって自動で注入されます。つかまえる場所こそが振り返りです。
本書のほかの部では「こういうツールがある」「こういうパターンがある」を扱ってきました。本章では、そのすべてのツールとパターンがどこで生まれるのかを扱います。新しいスラッシュコマンドはどこで作られ、新しいatomはどうやって固定化され、月に一度も使わないツールは誰が間引くのか。答えはいつも同じ場所に集まります。振り返りです。
振り返りは大げさな儀式ではありません。著者の日次振り返りファイルを1つ、そのまま開いてみます。実際のパスはretro/daily/YYYY-MM-DD.mdで、1か月が過ぎるとこれらのファイルは週次(retro/weekly/YYYY-Www.md)へ、さらに月次(retro/YYYY-MM.md)へと圧縮されます。ルールはretro/README.mdに書いてあります。
著者が実際に使っている流れは、振り返りファイルを自分の手で書くことではなく、AIにその日の作業ログを渡してパターンを抽出させることです。以下は、ある金曜の夕方の実際のやり取りを再構成したものです。プロンプトの全文を先に載せ、Claudeの生の出力を要約なしで — 外れた部分まで — そのまま載せます。
[プロンプト — 人が入力]
今日やった作業を振り返りとして整理して。今日やったこと:① キャラクターのマスターデータで壊れていたenum参照を修正(gradeカラムがenumシートと合っていなかった)② NPCセリフのトーン統一 ③ relation-mapの再実行。 このうち今週2回以上繰り返した作業があれば印を付けて、ツールにできそうな候補があれば別に分けて。なければないと言って。無理にひねり出さないで。
[Claudeの生の出力 — 要約・脚色なし]
本日の作業の振り返りです。
反復パターンの検知 - ① enum参照の破損修正 → これは今週火曜の振り返りにも同一項目があります。2回以上の反復として表示します。 - ② NPCセリフのトーン統一 → 今週初出。反復ではありません。 - ③ relation-mapの再実行 → relation-map-gen skillをすでにお使いなので、これはツール化が済んだ作業です。追加候補ではありません。
ツール候補 - enum参照の検証はskillにできそうです。マスターデータのカラム値とenumシートを照合して、不一致をレポートする形で。 - ただし私は2つのシートの正確な構造を知らないため、これが本当に反復可能なパターンなのかは確信できません。火曜に直した破損と今日の破損が同じ種類の破損なのか確認が必要です。同じカラムで同じ理由で壊れていたなら強力なskill候補ですし、毎回違うカラムが違う理由で壊れていたのならskillとしてまとめるのは難しいです。
ここでは最後の段落が核心です。AIは「skillを作ろう」と断定しませんでした。同じ種類の破損なのか人が確認せよと問い返しました。これが、振り返りが自動ではない理由です。
[人による検証 — AIの提案を受けつつ、ふるいにかける]
著者は火曜の振り返りを開いて照合しました。火曜の破損はgradeカラム、今日の破損もgradeカラム。同じ種類でした。AIの候補提案が検証を通過したのです。そこで日次振り返りに1行を残します。
反復作業:enum-grade参照検証(火・金の2回)→ skill候補。次の週次振り返りで昇格判断。
この1行がすべてです。5分もかかりませんでした。そしてこの1行が、self-improvingループの最初の一節です。もしAIが「すでにツール化済み」と指摘してくれた③まで候補に上げていたら、1か月後には使われない重複ツールがもう1つ漂っていたでしょう。AIのふるいと人のふるいが両方働いた結果、本物の候補が1つだけ残りました。
どの作業が繰り返されているか、どのツールがよく使われているか、どのatomが足りないかは、1回の作業では見えません。上のトランスクリプトでenumの破損が候補として浮かび上がったのは、「今日」ではなく「火曜と今日」を重ねて見たからです。1週間・1か月・四半期と積み重なった痕跡を重ねてこそ、パターンが浮かび上がります。振り返りは、その重なりを意図的に作る時間です。
振り返りで発見されたパターンは、2つの道に分かれます。
この分岐の判断は、作業の途中ではできません。作業の流れが途切れてしまうからです。enumの破損を直しているその瞬間には、「これは3回目だっけ?」と考える余裕はありません。別に取り分けておいた振り返りの時間が、その判断の場です。
ツールが作られた後、本当に価値を生んでいるかどうかも、同じ場で測ります。月に1回使われるツールと、1時間を節約してくれるツールの価値は違います。測定も、廃棄の決定も振り返りで行います。振り返りがなければ、ツールは積み上がるだけで整理されません。数年が経つと、使われないツール数十個が検索と運用を妨げます。
引き出しにたとえるなら、振り返りは机の引き出しを定期的に空にする時間です。毎日使うペンと、1年に一度も取り出していないメモ用紙が同じ仕切りに混ざっていると、毎回ペンを探すのに数秒余計にかかります。ツールも同じです。
著者の振り返りは3つの層で回っています。日次がパターンの種を集め、週次が種を束ねてツール候補へ圧縮し、月次がツールの経済性を評価して、資産として固定化するか廃棄します。各層は下の層の出力を入力として受け取ります。
flowchart TD
W["作業の遂行
(マスターデータ・仕様書・自動化)"] -->|資料の蓄積| D["日次振り返り
retro/daily/*.md
5〜10分・パターンの種1〜3件"]
D -->|日次5件を圧縮| WK["週次振り返り
retro/weekly/*.md
30〜60分・ツール候補の判定"]
WK -->|週次4件を総合| M["月次振り返り
retro/YYYY-MM.md
1.5〜2時間・経済性・廃棄"]
M -->|検証済みパターンの昇格| A["恒久資産化
feedback.md / workflows.md
+ atom登録"]
A -->|JIT hookによる自動注入| W
style D fill:#e3f2fd,stroke:#1565c0
style WK fill:#e8f5e9,stroke:#2e7d32
style M fill:#fff3e0,stroke:#ef6c00
style A fill:#f3e5f5,stroke:#6a1b9a
最後の矢印がループを閉じます。恒久資産化されたパターンは、JIT(Just-In-Time)hookを通じて次の作業に自動で注入されます。著者の環境ではinject_memory.pyというhookが、ユーザー入力を受け取るたびに関連atomを選んで差し込みます。enum-grade検証がatomとして固定化されると、次に「マスターデータ検証」のような入力をしたとき、そのatomがひとりでに付いてきます。人が毎回「そういえば、あの検証ルールがあったな」と思い出す必要がなくなります。
振り返りが抜けると、上から下へ向かう矢印だけが残り、資産が作業へ戻ってくる最後の矢印が切れます。ループが閉じません。自己改善(self-improving)という言葉の意味は、まさにこのループが回っているということです。ツールがツール自身を改善し、atomがatomを増やします。その動力は、人が取り分けておいた振り返りの1時間です。
著者が運営するあるMMORPGプロジェクトの振り返りを約半年回した印象では、1回の振り返りから次の5種類の産出が生まれます。以下の頻度は精密な統計ではなく著者の運営上の体感であり(著者の推定・未検証)、毎回の振り返りが5つすべてを生むわけではありません。四半期単位で見れば、5つすべてが一度ずつは出てきます。
5種類を順に書き出すとこうなります。今週同じ決定を2回以上繰り返したなら新しいatom候補です。同じプロンプトパターンを1週間に何度も入力し直したなら新しいskill候補です(前節のenum-grade検証がこのケースでした)。今週使ったskillのうち結果が振るわなかったものがあれば既存skillの改善 — プロンプト調整・検証の追加・入力の標準化です。前の四半期に作ったatomのうち1か月間マッチングが0回だったものは廃棄候補です。使わなければトークンを占有するだけです。最後に経済性評価は、ツールごとに使用頻度と節約される手間を見比べて、維持・改善・廃棄を決めることです。
この5つが1つの画面にどう配置されるかをマトリクスで見るとこうなります。横軸は「繰り返されるか」、縦軸は「価値があるか」です。
振り返りがやることは、結局この四象限にその週の作業をまき散らすことです。右上に落ちたものはツールになり、右下に落ちたものは間引かれます。この分類こそが、self-improvingの実際の動き方です。
前節のenum-grade検証候補がskillへ昇格し、そこからさらにatomとして固定化される過程を、最後までたどってみます。atomとは、振り返りで粗く発見されたパターンが検証を経て恒久資産になった形です。
著者のメモリーには、すでにそうやって固定化されたatomがあります。その1つがretro_atom_natural_invitationです。名前のとおり「振り返りにおいてatomは命令ではなく自然な招待として登場する」という原則を収めたatomです。このatom自体が、振り返りを何度も回すうちに発見されたメタパターンです — 振り返りの最中に「これはatomとして残さなければ」と強迫的に固定化を強いると、かえって振り返りが形式になってしまうということを何度も経験して、ようやく1行に固まりました。
固定化が本当に効果を出しているかどうかは、スコアでも管理されます。著者の環境にはatom_score.pyというスクリプトがあり、各atomが実際にどれだけマッチして使われているかを採点します。結果は_scores_latest.jsonに保存され、スコアが一定水準を超えるatomはCLAUDE.mdに自動で注入されます。つまり、よく使われるatomほど頻繁に目の前に現れ、使われないatomはスコアが削られて廃棄候補へ流れていきます。この採点・注入サイクルが、§1.4の四象限を自動化した部分です。
ここで1つ、正直に押さえておくべきことがあります。このスコアは「1か月で30時間を節約した」のような定量指標へそのまま換算されるわけではありません。atomが節約する時間は測定が厄介です。ですからROI(Return on Investment、投資対効果)を数字で断定するより、「よく使われるatomはスコアが高く、スコアの高いatomはより頻繁に注入されて手間を減らす」という方向と比率でだけ語るほうが正直です — 倍率ではなく方向です。
振り返りの時間を取り分けていないチームの、よくある風景はこうです。
この風景は、振り返り1時間でほとんど消えます。§1.1の金曜の夕方を思い出してみると、「どこか見覚えがある」を1行で書き留めるか、流してしまうかの違いでしかありません。書くのに5分、書かずに失う時間は4回目・5回目の反復として積み重なります。時間をかけないことで、より大きな時間を失う典型です。
もちろん、最初から大げさな振り返りシステムを立てる必要はありません。大きなチームでは、日次5分の振り返りから始めるのがもどかしく感じられるかもしれません。かといって最初から日次・週次・月次の3層システムを一度に敷くと、形式だけなぞって本質を見失いがちです。いちばん小さな段階で振り返りの価値を自分で体感した人が、次の段階へ引き上げていく順序が安全です。
本書で出会うすべてのツール・atom・パターンは、結局誰かの振り返りから生まれたものです。本書自体が、著者が半年間積み上げた振り返りの蓄積の産物だと言っても過言ではありません。振り返りが出発点だという言葉は比喩ではなく、本書の目次そのものが振り返りから出てきたという事実を指しています。
ゲーム外への応用。「どこか見覚えがある — これ、先週もやったぞ」という感覚を1行でつかまえる振り返りは、ゲーム開発に限らず、反復業務のあるどんな職場でも自己改善の入口です。1日を閉じるときに「今日、同じことを2回手作業でやったのは何か」を1行だけ書き、金曜にその週の5行を重ねて見れば、毎日の中では隠れていたパターンが1週間の単位で姿を現します。たとえば総務の担当者が「同じ書式のメールを毎週打ち直している」を振り返りでつかまえれば、その1行が翌週にはメールテンプレートになり、1か月後には自動送信ルールになります。核心はツールの精巧さではなく、痕跡を重ねて見るという行為そのもの、そしてAIに候補を抽出させつつ「無理にひねり出さず、すでに自動化済みのものは外せ」というふるいをかけることです。
振り返りループを初めて導入する、いちばん小さなバージョンです。ツールをほとんど入れずに始められます。
setup
retro/daily/フォルダーを1つ作ります。retro/daily/2026-06-06.mdを開きます。それ以上の準備物はありません。prompt
毎日作業を終えるとき、その日の作業ログをAIに渡して以下のように尋ねます。
今日やったことは[作業1・2・3]です。このうち今週2回以上繰り返した作業があれば印を付け、ツール(skill)にできそうな反復パターンがあれば別に分けてください。すでにツール化が済んだ作業は候補から外してください。なければないと言ってください。無理にひねり出さないでください。
最後の2文(「すでにツール化済みのものは外せ」「無理にひねり出すな」)がふるいです。これがないと、AIは毎回もっともらしい候補を過剰に生成し、1か月後には使われないツールが積み上がります。
verify
gradeカラム照合のように)。同じ種類なら候補確定、違えば廃棄です。反復: [作業] (N回) → skill候補、週次振り返りで判断。一人ミニ版
チームも、専用ツールもなしに一人で始めるなら、ここまで縮めます。
核心はツールの精巧さではなく、重ねて見るという行為です。1日はパターンを隠し、1週間はパターンをあらわにします。そのあらわれをつかまえる5分が、自己改善ループの入口です。
月曜日の朝、先週の日次振り返り5枚を1つの画面に並べて、一週間を始めようとしていたところでした。火曜日の振り返りには「マスターデータをexportする前の整合性チェックを忘れた」と書いてありました。木曜日の振り返りにも、ほとんど同じ文がありました。そしてまさにその月曜日の午前、私はまた同じことをしていました。FKが壊れたシートをそのままクライアント/サーバービルドに上げて、また取り下げたのです。3回目でした。
この瞬間こそが振り返りシステムの核心です。同じことを3回目にやっているという事実は、その作業をしている間には決して見えません。手は慣れたとおりに動き、頭は「これは元々自分がやってきた仕事だ」とささやくからです。反復は、痕跡を集めておいて後から眺めたときにだけ見えます。振り返りはその痕跡を集める装置であり、atom昇格はそこで見つけた反復を二度と手作業でやらないようにルール化(固定化)する装置です。
この章では、この2つの装置がどのように噛み合って回るのか、実際の日次振り返りファイル1枚がどのようにJIT manifestのatom 1行へと変わるのかを、最後まで追いかけます。
まず押さえておくべき前提があります。反復はリアルタイムでは認知されません。
ゲームプランナーの1日は決定の連続です。マスターデータのカラム1つをどのenumにするか、スキルのクールタイム(クールダウン)を秒単位にするかフレーム単位にするか、会議で出たあいまいな合意をドキュメントのどこに書くか。こうした決定の1つひとつは小さすぎて記憶に残りません。ところが、同じ決定を1週間に3回下しているなら、それはもう決定ではなくルールです。ルールなのに毎回新しく下しているなら、それは無駄です。
問題は、この無駄が見えないことです。だから痕跡を残します。毎日5分、今日やったことと、今日2回以上繰り返したことを1行ずつ書きます。1週間経つと5枚の痕跡が積み上がり、そこでようやく「あれ、これ3回書いてあるな」が見えてきます。
これが、振り返りを単なる日記と分ける地点です。日記は感想を書き、振り返りはパターンを抽出するために痕跡を書きます。だから振り返りは形式が固定されていなければなりません。形式が毎回違うと5枚を並べて比較できず、比較できなければパターンは見えません。
振り返りを日・週・月の3つの周期に分ける理由は、時間が流れるからではありません。それぞれの周期が担う仕事が根本的に異なるからです。
日次は痕跡を固定化します。判断せず、ただ書きます。週次は痕跡5枚をまとめてパターンを見ます。ここで初めて「これは反復だ」という判断が入ります。月次は蓄積されたツール全体を見て経済性を評価します。何を残し、何を捨てるかを決めます。
1つの周期が抜けると、残りが崩れます。日次なしで週次だけやると、1週間前のことが思い出せず痕跡が空っぽになります。週次なしで月次だけやると、1か月分の日次を一度に見ることになりますが、22枚をその場で比較するのは不可能に近いです。パターンは見えず、疲労だけが積み上がります。
作業場のたとえがよく当てはまります。日次は、毎晩机を片付ける5分です。週次は、週末に引き出し1段を組み直す30分です。月次は、四半期ごとに作業場全体の動線を見直す2時間です。毎日机を片付けなければ週末に引き出しを組み直せず、引き出しがめちゃくちゃなら動線を眺めても答えは出ません。
実際に私が使っている日次振り返りファイルは、retro/daily/2026-05-30.mdのようなパスに日付ごとに積み上がります。テンプレートは/retroスラッシュコマンドが自動で敷いてくれます。
# 日次振り返り 2026-05-30
## 今日やったこと(3〜5行)
- 新規スキルのマスターデータにenum 12種を追加 + クールタイムカラムの再整列
- バランスシミュレーション1次パス(ドロップテーブルの重み調整)
- データexportビルドでクライアント/サーバーを同時更新
## 反復の発見(あれば)
- データexportビルドの前に整合性チェックをまた忘れた → FKが壊れたままビルド → 3回目
- バランスシミュレーションをシード固定なしで回して再現できず(2回目)
## 廃棄候補
- 今日一度も使わなかったツール:(月次累積の測定用として記録するだけ)
## 次セッションへの引き継ぎ
- 壊れたFK 2件(スキル→エフェクト参照)を先に埋めて再ビルド
- 候補: シミュレーションのシード固定オプションのデフォルト化を検討
5分あれば埋まります。形式が固定されているので、毎回「何を書こう」と新たに悩むことはありません。欄が決まっているので、欄を埋めるだけで済みます。
ここで決定的なのが「反復の発見」欄です。この欄は空のままでも構いません。ほとんどの日は空です。それでも、今日同じことを2回やったという自覚が生まれたら1行書きます。上の例の「データexportビルドの前に整合性チェックをまた忘れた → 3回目」がまさにそれです。この1行が数日後の週次振り返りでパターンとしてまとめられ、さらに数週間後にatomやskillとして固定化されます。
自動キャプチャが人の手間を減らしてくれます。gitのコミットログ、atomの変更履歴、skillの使用ログが日次振り返りに自動でマージされていれば、「今日やったこと」欄の半分はすでに埋まっています。人は、gitログには見えないもの — 「これ、またやったな」という自覚 — だけを追加すればいいのです。
最後の「次セッションへの引き継ぎ」欄は、明日の自分に宛てたメモです。この欄があれば、新しいセッションの開始時にコンテキストの読み込みが1〜2分で終わります。なければ「昨日どこまでやりかけていたっけ」と手探りするのに、もっと時間がかかります。実際、私のMEMORY.mdには「次セッション優先確認」という項目が別途維持されていますが、これこそ日次の引き継ぎが蓄積された上位バージョンです。
週次振り返りは、日次5枚を1つの画面に並べるところから始めます。ファイルはretro/weekly/2026-W21.mdのようなパスに積み上がります。
# 週次振り返り 2026-W22(5/25〜5/31)
## 今週やったことの要約
- スキル/バランスのマスターデータ更新、ドロップテーブルのシミュレーション2回
- データexportビルド4回(うち2回はFK・enumが壊れたままビルド)
## パターンの発見
- 日次3件で「export前の整合性チェック忘れ」が反復 → atom候補
- 日次2件で「バランスシミュレーションのシード未固定」が反復 → シミュレーションのデフォルト値を検討
## atom候補
- pending-data-check-before-export(exportビルド前の整合性検証を強制するルール)
## skill候補
- (なし — 今週はatomで十分)
## 既存ツールの点検
- 未使用: relation-map-gen(今週0回)
- 最多使用: check(整合性cascade), excel-reader, /retro
## 来週の計画
- pending-data-check-before-export をもう1週間運用してから昇格を判断
ここで初めて判断が入ります。「日次3件でexport前のチェック忘れが反復」は算術的な事実ですが、「これはatomとして固定化する価値がある」は判断です。3回の反復を基準線とする理由は単純です。1回は偶然、2回も偶然かもしれない、3回はパターンです。
判断が固まったら、すぐに固定化します。ただし、正式なatomではなくpending-接頭辞を付けた暫定atomとして、です。私のプロジェクトメモリーフォルダーに、このように置かれます。
~/.claude/projects/<project>/memory/
pending-data-check-before-export.md
pending-接頭辞は「これはまだ検証中」という目印です。この目印が重要な理由は、検証を経ていない直感をいきなりチーム全体のルールにすると、2つのものが壊れるからです。1つは信頼 — 検証されていないルールが繰り返し外れると、人はルールそのものを信じなくなります。もう1つは蓄積 — 検証ゲートがなければ直感がそのまま積み上がり、メモリーがゴミ箱になります。
そこでpending-は、1週間から長くて1か月、実際の作業の中で運用してみます。本当に毎回有用なら生き残り、一度も当たらなければ静かに消されます。
月次振り返りは、1か月分の蓄積を広げてツール全体の健康状態を点検する場です。ファイルはretro/2026-05.mdのように月単位で積み上がります。
# 月次振り返り 2026-05
## 今月の累積
- 日次振り返り: 22件、週次振り返り: 4件
- 新規atom: 4個(data-check-before-export, sim-seed-pinning ほか)
- 新規skill: 1個(relation-map-genのオプション補強)
- 廃棄atom: 1個
## ツールの経済性評価
- skillごとの月間使用回数 + 節約の体感(定性)
- 月1回未満しか使わないskill → 廃棄候補
- 最も価値の大きいツール: check(整合性cascade), excel-reader, /retro
## atomの分布
- prefixごとの累積(data: X, sim: Y, meeting: Z ...)
- 廃棄候補: 1か月マッチ0回のatom
## 四半期計画
- 来月導入: impact(影響度トラッキング), schema-docの自動更新
## 書籍執筆の資料(該当する場合)
- 今月の事例のうち本に引用できそうなもの: atom昇格のワークド・トランスクリプト1件
月次の核心は経済性評価です。ツールは作るときには全部価値があるように見えますが、1か月経つと半分は手が伸びなくなります。それを選り分ける5つの物差しを使います。
評価基準は、使用頻度・時間節約・認知負荷・維持コスト・代替可能性の5つです。使用頻度は、月1回以上ならひとまず残し、未満なら廃棄候補に回します。時間節約は、1回あたりの節約の体感に頻度を掛けてみます — ここで分単位の数字を断定はしません。「1回で数分浮く感覚で、月に10回使うから累積が大きい」という程度の定性的な判断が正直です。認知負荷は、覚えるべきスラッシュコマンドが12個を超えたら整理のシグナルと見ます。人が頭に載せて持ち歩けるコマンドの数には限界があります。維持コストは、マスターデータが変わるたびに一緒に手を入れる必要があるツールかどうかを見ます。代替可能性は、もっと簡単な方法が新しく生まれていないかを見ます。
5つの物差しを合わせて、残すか、捨てるか、直すかを決めます。1時間あれば30個から50個のツールに目を通せます。ただし、始めて1〜2か月はツールが少なく、評価に1時間もかかりません。その時期は、評価よりも新しいツールを生み出すことに時間を使うほうが正しいです。経済性評価は、ツールが積み上がってから本格的に働く安全装置です。
ここからがこの章の背骨です。上でばらばらに出てきた断片が、実際に1行から始まって全セッションへの自動注入まで到達する過程を、振り返りの時点から最後まで追いかけます。途中で私が拒否した瞬間と、AIがあいまいに答えた瞬間は、そのまま残します。
ステップ1 — 日次での自覚。 5月30日の日次振り返りを書いていて、「反復の発見」欄に1行を書きました。
- データexportビルドの前に整合性チェックをまた忘れた → FKが壊れたままビルド → 3回目
この時点では、何のツールも作りませんでした。ただ痕跡だけを残しました。
ステップ2 — 週次でパターンを確認し、AIにatom化を依頼。 数日後の週次振り返りで日次5枚を広げたところ、同じ行が3か所にありました。そこでClaudeに投げたプロンプトの全文はこうでした。
今週の日次振り返り3件で「データexportビルドの前の整合性チェックを手作業で忘れる」が繰り返された。これをpending atomにしようと思う。atom本文の草案を書いてほしい。形式はうちのメモリーの他のatomと同じく、frontmatter(name、status、trigger)と1段落のルールで。
Claudeの生の出力はこうでした(要約なしでそのまま)。
--- name: data-check-before-export status: pending trigger: ["데이터", "export", "빌드 전", "정합성"] --- ゲームのマスターデータをクライアント/サーバーへexportビルドする前には、必ず 整合性チェック(check cascade)を先に回し、FK・enum・参照違反が0件であることを 確認した後にのみexportする。違反が見つかった場合はexportを中断し、失敗内容を ClickUp通知で担当者に知らせる。このatomを
pending-data-check-before-export.mdとして保存すれば使えます。ただし、triggerキーワードのうち「데이터」(データ)は範囲が広すぎて、exportと無関係な状況(マスターデータの閲覧、データ会議など)にもマッチする可能性があります。外すことをお勧めします。
ステップ3 — 人による検証と部分的な拒否。 AIが自らtriggerの1つを疑ったのは、正しい指摘でした。しかし私はもう一歩進みました。ルール本文に「整合性検証」と「失敗時のClickUp通知」を両方束ねているのが気に入りませんでした。失敗時の通知は、すでにintegrity_check_clickup_notifyが担当している別のatomです。1つのatomに2つの責務を束ねると、後で通知ルールだけを変えたいときに検証ルールまで触ることになります。そこで拒否して、もう一度依頼しました。
よし、「데이터」のtriggerは外そう。それから、失敗時のClickUp通知はすでにintegrity_check_clickup_notifyがやっているから、このatomからは外して。このatomはexport前の整合性検証だけを責務とするよう絞り込んで。
再依頼の後、Claudeが絞り込んだ本文はこうでした。
--- name: data-check-before-export status: pending trigger: ["데이터 빌드", "export", "빌드 전 검증"] --- ゲームのマスターデータをクライアント/サーバーへexportビルドする前には、整合性 チェック(check cascade)を先に回す。検査対象: FK参照の完全性、enum値の有効性、 テーブル間の参照整合性。違反0件を確認した後にのみexportする。失敗時の通知は integrity_check_clickup_notify の責務であり、このルールは関与しない。
これが、私が受け入れたバージョンです。1つの責務だけを負い、triggerもexportの文脈だけに絞られました。このファイルをpending-data-check-before-export.mdとして保存しました。ここでの核心は、AIの最初の出力をそのまま使わなかったという点です。AIは形式を整え、triggerの1つを疑ってくれましたが、「責務を1つに絞る」という設計判断は人がやりました。
ステップ4 — 1週間の運用検証。 続く1週間、データexportビルドをするたびにこのpending atomが頭に浮かび、実際に2回、ビルド直前にenumの破損を捕まえました。外れたことは一度もありませんでした。生き残る資格ができました。
ステップ5 — 月次での昇格決定とscore測定。 月次振り返りで、このpending atomを昇格候補に上げました。昇格するかどうかは、直感ではなく測定で判断します。私の環境には、atomのマッチ頻度と有用性をスコア化するスクリプトがあります。
python ~/.claude/scripts/atom_score.py
# → ~/.claude/projects/<project>/memory/_scores_latest.json 更新
このスクリプトは、各atomが直近の期間に何回triggerにマッチし、そのとき実際に作業で引用されたかを集計して_scores_latest.jsonに落とします。そのスコアが一定の基準を超えたatomは、CLAUDE.mdに自動注入されるよう接続されています。pending-data-check-before-exportは、2026年5月の実測基準で1週間、exportビルドのたびにマッチしたので、スコアは十分でした。昇格確定です。
ステップ6 — pending-を外してJIT manifestに登録。 接頭辞を外して正式なatomに変えた後、JIT manifestに1行を追加しました。
~/.claude/projects/<project>/memory/_jit_manifest.json
このmanifestは、UserPromptSubmitフック(~/.claude/hooks/inject_memory.py)が毎回の入力のたびに読みます。入力に「데이터 빌드」(データビルド)や「export」が含まれていると、このatomの本文がコンテキストに自動で割り込みます。
ステップ7 — ループが閉じる。 次のビルドをしようと「データexportビルドを回して」と入力した瞬間、私が何も指示していないのに、Claudeが先にこう言いました。
exportの前に整合性チェック(check cascade)から回しましょうか? FK参照・enum値・テーブル参照の整合性を検査し、違反0件を確認した後にexportします。
3週間前の日次振り返りに書いた「これ、またやったな」の1行が、いまの作業に先回りしてブレーキをかけてくれるルールになって戻ってきたのです。手作業でやっていた検証を、二度と手作業でやらずに済むようになりました。ループが閉じたというのは、まさにこの場面のことです。
上のワークド・トランスクリプトを1枚のフローチャートに圧縮するとこうなります。発見は日次で起き、検証は運用期間が行い、昇格は測定が決め、資産化はmanifestが仕上げます。
flowchart TD
A["日次振り返り
「これ、またやったな」の1行"] --> B["週次振り返り
5枚をまとめてパターン確認(3回+)"]
B --> C["AIにatom化を依頼
→ 生の出力 → 人による検証・拒否 → 再依頼"]
C --> D["pending- atomとして固定化
~/.claude/.../memory/pending-*.md"]
D --> E["1〜4週間の実作業での運用検証"]
E -->|一度も当たらない| X["静かに廃棄"]
E -->|毎回有用| F["atom_score.pyで測定
_scores_latest.json"]
F --> G["月次振り返りで昇格を決定
pending- を外す"]
G --> H["JIT manifestに登録
_jit_manifest.json"]
H --> I["UserPromptSubmitフック
inject_memory.pyが自動注入"]
I --> J["次セッション: キーワード入力時に
過去の発見が現在の作業を守ってくれる"]
J -.振り返りでまた発見.-> A
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class F,I code;
class A,B,G human;
class D,H data;
class J pass;
class X fail;
最後の点線がこの図のすべてです。自動注入されたatomがまた新しい反復をあぶり出し、それが再び振り返りに入って次のatomを生みます。一周回るたびに、手作業ですることが1つずつ減ります。このループが半年から1年蓄積されると、振り返りはもはや日記ではなく、作業システムの頭脳になります。
昇格ループでいちばん壊れやすい輪は、ステップ1の「これ、またやったな」を書くその瞬間です。忙しいとき、人は振り返りの欄を空けたまま通り過ぎます。すると痕跡が残らず、痕跡がなければ週次でパターンが見えず、パターンがなければatomは生まれません。ループの入口が塞がるのです。
そこで私の環境には、retro_atom_natural_invitationというatomが1つあります。振り返りを書くときにatomの言語化を義務として強制せず、自然な招待にとどめる、というルールです。つまり「今日は必ずatom候補を1つ挙げよ」ではなく、振り返りテンプレートの「反復の発見」欄を空でもよい欄として置きつつ、そこに1行書く価値のあるものがあれば気軽に書くよう誘うのです。義務にすると無理やり偽のパターンをひねり出すことになり、招待にとどめれば本物の反復だけが自然に引っかかります。
この紙一重の差が、ループの持続可能性を分けます。義務化された振り返りは2週間ともたず、形式的な嘘で埋まっていきます。招待型の振り返りは、書くことがない日は空欄のままにできるので負担がなく、だから長続きします。長続きしてこそ痕跡が積み上がり、痕跡が積み上がってこそパターンが見えます。
このatom自体も、振り返りから生まれました。振り返りを義務として運用していて、数日で欄が偽物で埋まっていくのを日次で発見し、その発見が週次を経てこのルールに昇格しました。振り返りを改善するルールが、振り返りから生まれたわけです。
ループを回してみると、同じ場所で繰り返し崩れます。
振り返りをサボるのが、いちばんよくあるケースです。忙しいからと3日空けると、その3日の痕跡は永遠に消えます。防ぐ方法は単純です — 他の欄は全部空でもいいので、「今日やったこと」の1行だけは書きます。5分ではなく1分でも、痕跡は残ります。
形式を毎回新しく組むのも危険です。自由形式で書くと、5枚を並べて比較することができません。比較できなければ、パターン抽出という週次の仕事そのものが不可能になります。だから/retroがテンプレートを強制的に敷いてくれるのです。
廃棄をしないのも落とし穴です。atomとskillを増やすだけで捨てなければ、認知負荷が蓄積します。スラッシュコマンドが12個を超えた瞬間から、頭がツールを覚えきれなくなります。月次の経済性評価が、この蓄積を防ぐ唯一の装置です。
昇格ゲートを飛ばすのも危険です。直感をいきなり正式なatomにすると、検証されていないルールが積み上がります。pending-を経て測定で昇格するゲートが、発見と資産化の間に必ずなければなりません。
最後に、1人作業だからチーム共有がない、という理由で振り返りをしないのは誤解です。上のワークド・トランスクリプト全体が、1人環境で回った事例です。チーム共有のマージ段階が抜けるだけで、発見→pending→測定→昇格→JIT注入のループは1人でもそのまま回ります。むしろ1人環境では、このループが唯一の外部レビュアーの役割を果たします。
ゲーム外への応用。 日次の1行が検証を経て恒久ルールになる昇格ループは、職種に関係なく「一度学んだ教訓を二度と手作業で繰り返さない」ようにする手順です。発見をすぐにチームルールとして固定せず、
pendingとして1週間運用してみて、実際に毎回有用だったときにだけ正式化するゲートが核心です — 検証されていない直感をすぐルールにすると、人はルールそのものを信じなくなるからです。たとえば運営チームが「レポート提出前に数字の合計が合っているかもう一度見る」を暫定チェックリストとして置き、1週間回してみて、実際に2〜3回エラーを捕まえたときに正式な標準手順へ昇格させれば、1つの責務だけを負うよう絞る設計判断(複数の点検を1行に束ねない)まで自然についてきます。
setup. 振り返りフォルダーとテンプレートを用意します。
mkdir -p ~/.claude/projects/<your-project>/memory/retro/daily
mkdir -p ~/.claude/projects/<your-project>/memory/retro/weekly
# 日次テンプレートファイルを1つ retro/_template_daily.md として保存
prompt. 1週間分の日次振り返りを積み上げた後、週次振り返りの場でClaudeにこう投げます。
今週の日次振り返り5枚を貼り付ける。3回以上繰り返された作業・決定を見つけて、atom候補として整理してほしい。各候補はfrontmatter(name、status: pending、triggerキーワード配列)と1段落のルールで。triggerが広すぎる場合は絞り込んで提案し、1つのatomが2つの責務を負っている場合は分けて提案して。
verify. 受け取った候補をそのまま使わず、3つを検証します。(1) 1つのatomが1つの責務だけを負っているか — 2つの責務なら拒否して分離を依頼します。(2) triggerキーワードがその作業の文脈だけにマッチするか — 広すぎるなら拒否します。(3) 本当に3回繰り返したのか、それとも偶然の2回か — 偶然ならpendingすら作りません。3つの検証を通過したものだけにpending-を付けて保存し、1週間運用して毎回有用だったときにだけ正式に昇格させます。
チームもなく、JITフックもscoreスクリプトもまだないなら、次の1枚のファイルでループ全体を真似ることができます。
retro.mdというファイルを1つ作り、3つの欄だけを置きます。
## 今日(1行)
-
## またやった(あれば1行)
-
## 固定化候補(「またやった」が3回たまったらここへ)
- [ ](ルール1文)— 検証: ___回有用
毎日、上の2つの欄だけを埋めます。「またやった」に同じ行が3回たまったら、3つ目の欄に移してルール1文として書きます。そのルールが次の1週間で実際に有用だった回数を数えて欄に書き、3回以上ならその文をプロジェクトメモリー(CLAUDE.md)へ正式に移します。JITフックがなくても、CLAUDE.mdに上げたルールは次のセッションに必ずついてくるので、それだけでも「過去の発見が現在の作業を助ける」ループの最小形が完成します。
核心はツールの華やかさではなく、ゲートの存在です。「またやった3回 → 固定化 → 有用3回 → 恒久化」というゲートさえあれば、ファイル1枚でもself-improvingループは回ります。
振り返りから始まったサイクルは、再び振り返りへ戻ってくるか。閉じなければ、それはメモにすぎず、システムではない。
6か月前の振り返りを開きます。「用語が統一できていない」「ドキュメントが見つけにくい」「同じ質問をまた受ける」。今朝書いた振り返りを開きます。「用語が統一できていない」「ドキュメントが見つけにくい」「同じ質問をまた受ける」。
一言一句同じです。振り返りをしなかったわけではありません。6か月間ずっと誠実に続けてきました。Notionのページは着々と積み上がり、四半期ワークショップでは付箋がホワイトボードを覆いました。それなのに、書かれた内容は同じところを回っています。振り返りが機能しなかったのではありません。ループが閉じていなかったのです。
本章は本書の最後の章です。だから扱うのも最後の問いです。ここまでに作ってきたすべてのツール — Part 6の都市ジェネレーター、Part 14のモバイルレビューatom、Part 22のコスト標準 — これらが、一度作って終わりの使い捨てではなく、自ら育つシステムになるためには、何がさらに必要なのか。答えは一つです。振り返りから出た発話が次のセッションから自動で動き、その動きが再び振り返りで測定されて戻ってくる、閉じた輪。この輪を閉じるメカニズムがself-improvingループです。
振り返りから発話が出ます。「会議が多すぎる」。良い発話です。ところがその発話は、Notionページの1行として残るだけです。翌週も会議は相変わらず多く、次の振り返りで同じ行がまた書かれます。発話と改善の間に人の記憶が挟まっているからです。人は忘れます。だから途切れます。
self-improvingと呼べるためには、振り返りの発話が人の記憶を経由せず、次のセッションの自動の行動につながらなければなりません。これを満たす条件は4つです。
第一に、振り返りの発話が即時実行可能な形に変換されること。抽象的な決意ではなく、スキル・atom・manifest項目・スラッシュコマンドのいずれかに落ちます。第二に、次のセッションから人が覚えていなくても自動で発動すること。第三に、次の振り返りで、それが実際に何を変えたかが実測されること。第四に、その測定結果が再び次の改善の入力として循環すること。
この4つがすべて自動でつながったとき、ループが閉じます。一段階でも「来週、自分が覚えておいて適用しよう」で埋めると、まさにその場所でループが再び開きます。そして次の振り返りに同じ発話がまた書かれます。
引き出しのたとえで見るとこうです。振り返りが「このペンは使わないから抜こう」というメモで終われば、翌週もそのペンはその場所にあります。メモではなく、手が伸びて抜いて初めて閉じます。そして次の四半期にまた点検して初めて、その場所に使わないペンがまた積もらなくなります。メモが発話で、手が伸びることが自動発動で、次の四半期の点検が測定です。3つのうち1つでも欠ければ、引き出しはまた散らかります。
全体の流れを描くと、閉じた循環になります。始点も終点も振り返りです。
flowchart LR
A["振り返り
(日・週・月)"] -->|発話| B["候補の識別
スキル・atom・コマンド"]
B -->|定量化| C["経済性評価
ROI計算式"]
C -->|通過| D["実装・登録
自動発動を保証"]
C -.->|未達| F["廃棄・保留
別の形態へ移管"]
D --> E["次のセッション
自動発動"]
E -->|測定値の蓄積| A
F -.->|記録| A
style A fill:#2d4a3e,color:#fff
style E fill:#3e2d4a,color:#fff
矢印が一周して、再び振り返りへ入ってきます。この閉じ方が核心です。各段階の成果物が次の段階の入力になり、最後の測定値は再び最初の振り返りの入力になります。間に人の記憶が挟まると、その矢印が途切れ、循環は壊れます。
ROI未達の候補が廃棄・保留へ抜ける点線の矢印も、結局は振り返りへ戻ってくる点に注目してください。「これは作る価値がなかった」という判断自体が次の振り返りの記録になり、同じ候補がまた上がってきたときに素早くふるい落とす根拠になります。捨てることもループの中にあります。
振り返りからself-improvingへつながる発話には、決まった5つのパターンがあります(§21.1.4で扱いました)。作るスキル、改善するスキル、作るatom、改善するatom、経済性の再評価です。振り返りテンプレート自体にこの5つをスロットとして入れておけば、発話が漏れません。
## 振り返り(日次) — 2026-06-06
### 1. 今日の作業
- (作業の要約)
### 2. self-improving発話(5スロット)
- 作るスキル: <空なら「なし」>
- 改善するスキル: <>
- 作るatom: <>
- 改善するatom: <>
- 経済性の再評価: <>
### 3. 次の振り返りで測定すること
- <>
スロットは空でも構いません。空だという事実自体が「今日は新しい改善がない」という記録です。ただし数日連続で5つのスロットが全部空なら、それは改善のネタがないのではなく、振り返りが形式に固まりつつあるという信号です。そういうときはトリガー質問を投げます。「今週、同じことを2回手作業でやったのは何か」。
発話は曖昧なまま出てきます。「議事録が長すぎる」。候補に育てるには、成果物1個に定量化します。「議事録が長すぎる」はmeeting_summaryスキル、つまり議事録を受け取って意思決定とアクションアイテムだけを抽出するツール1個に換算されます。「用語が紛らわしい」はドメイン語彙30個を収めたglossary_lookup atomに、「同じ質問を毎回受ける」は新しく入ったメンバーの初日案内を自動化する/onboardingスラッシュコマンドに換算されます。「同期漏れが多い」はmanifestの更新とJIT atomの追加に落ちます。
候補が「どの成果物1個か」で定義されて初めて、次の段階へ進みます。「全般的に改善しよう」は候補ではありません。成果物1個に換算できない発話はROIの評価台に載せられず、載せられなければそこで止まります。
候補ができたからといって、すべて作るわけではありません。作る前に投資対効果を測ります。計算式は単純です。
各項目には単位と通過ラインがあります。節約時間は1回の発動あたり減る人の時間で、分単位で取ります。発動頻度は週あたりの推定回数で、週1回以上なら生き残ります。運用期間は廃棄までの予想週数で、4週間もたないツールは作る理由が弱いです。作成時間は最初の実装と検証にかかる時間、メンテナンスは月次の点検・修正にかかる時間です。
分子が累積の節約、分母が累積のコストです。出てきた値で決定します。
| ROI値 | 決定 |
|---|---|
| 10以上 | 即時作成 |
| 3〜10 | 1週間以内に作成 |
| 1〜3 | pending保留、1か月後に再評価 |
| 1未満 | この形態では廃棄。別の方式を検討 |
ROIが1未満というのは「このアイデアは役に立たない」ではなく、「この形態で作ってはいけない」という意味です。より軽いatom 1行で代替できないか、既存ツールの入口だけを変えるWrapperで解けないかを先に点検します。重いスキルで作るはずだったものをatom 1行に下げると、分母が10分の1に減ってROIが生き返るケースはよくあります。
実際の数字を1つ入れてみます。2026年5月23日に個人PCへ構築したJIT atom注入システム — UserPromptSubmitフックがユーザー入力を見て、関連するメモリ断片(atom)を自動注入するインフラ — のROIを確かめてみましょう。
節約時間: 1セッションあたり約3〜5分 (関連atomを手で探して呼び出していた時間の除去)
発動頻度: 週15〜25セッション (個人PC基準)
運用期間: 1年+を想定 (インフラ性格のため廃棄の可能性は低い)
作成時間: 4時間 (hook + manifest + atom検証)
メンテナンス: 月0.5時間 (atomの追加・修正)
ROI = (4分 × 20回/週 × 52週) / (4時間 × 60分 + 0.5時間 × 12か月 × 60分)
= 4,160分 / (240分 + 360分)
= 4,160分 / 600分
≈ 6.9 → 「即時作成」区間。決定が計算式に裏付けられている
ここで正直になるべき部分があります。上の数字 — セッションあたり3〜5分、週15〜25セッション — は精密計測ではなく、著者の運用経験に基づく推定です。ストップウォッチで測った値ではありません。だからROI 6.9も、小数点まで信じてよい値ではありません。
それでも構いません。ROI計算式は精度ではなく桁を見る道具だからです。結果が7前後なら作ります。0.3前後なら考え直します。その間を分けるのに小数点は要りません。重要なのは、作らないと決定するときでさえ、その根拠が頭の中の直感ではなく計算式から出るべきだという点です。桁が合わないから作らない — この1行が振り返りに残れば、同じ候補がまた上がってきたときに、もう悩みません。
候補が通過したら作ります。ただし、作ることは半分です。残りの半分は、次のセッションから自動で発動するよう登録する仕事です。この登録が抜けると、ツールは作られたものの誰の手にも届かない場所に残り、ループはそこで途切れます。
成果物の種類ごとに登録先が違います。グローバルスキルは~/.claude/skills/に入れ、使い方を収めたガイドatomを一緒に作ります。プロジェクトスキルは該当プロジェクトの.claude/skills/に置きます。新規atomは適切なフォルダに置き、MEMORY.mdのインデックスに1行を追加し、JIT manifestにトリガーを登録します — この3つを全部やって初めて自動注入が生きます。スラッシュコマンドは~/.claude/commands/に、Wrapperは既存ツールの入口を変えてガイドatomを付けます。
登録を抜かすと、次の振り返りで「これ、作ったのになぜ使われていないんだろう」という発話がまた出ます。それは新しい改善の発話ではなくバグレポートです。自分が漏らした登録を、振り返りで再発見しているわけです。
登録まで終えても、もう一段階残っています。新しいセッションを開き、意図したトリガーで本当に発動するかを確認する仕事です。
1. 新しいセッションを開始
2. トリガーを入力 (例: 「家族の健康はどう」)
3. JITログを確認 → 意図したatomが実際に注入されたか
(~/.claude/hooks/_injection_log.txt)
4. 出なかったら → manifestのトリガーregexを拡張
またはマニュアル呼び出し経路を追加
この検証が抜けると、「あると思っていたのに、いざ必要なときに出なかった」という事故が繰り返されます。登録と発動は別の仕事です。登録はファイルを置いたことであり、発動はトリガーが実際に掛かることです。トリガーregexが1文字ずれていれば、登録できていても永遠に出ません。
作ったツールを1週間から1か月ほど回したあとで測定します。この測定がループの最後の矢印、つまり再び振り返りへ入っていくあの矢印です。
実際の発動回数はJITログやコマンド呼び出しログで数えます。実際の節約時間は「以前ならN分かかったはずの作業がM分で終わった」という形で振り返りに記録します。副作用 — 誤った発動、不要なコンテキスト汚染 — も一緒に見ます。そして最初に推定したROIと実測ROIを並べて置きます。
推定ROIが6だったのに実測ROIが0.8なら、容赦なく廃棄します。作った人のプライドより、システムの清潔さが優先だからです。使われないツールがmanifestに積もれば、そのノイズが次の振り返りの精度を蝕みます。
ただし、廃棄ボタンを押す前に一度は点検します。トリガーregexが狭すぎて発動自体がしなかったのかもしれませんし、マニュアル呼び出し経路がなくて単に忘れられたのかもしれません。本当に価値のないツールなのか、発動経路が塞がっていた良いツールなのかをまず切り分けます。前者なら捨て、後者なら経路を開きます。
廃棄もまた振り返りで決定されます。「このツールを廃棄する」という決定自体がself-improvingの成果物です。作るだけで空けないサイクルは単調増加するだけのサイクルであり、単調増加するシステムは、結局自分の重さに潰されます。
ループが閉じたかどうかは、4つの信号でわかります。
第一に、同じ発話が繰り返されません。振り返りで一度書かれた項目が二度書かれたら、1回目の候補識別か実装のどこかが失敗したという意味です。本章冒頭の「用語が統一できていない」が6か月も繰り返されていたこと — それが開いたループの最も鮮明な証拠でした。
第二に、manifestとatomの数が単調増加だけはしません。廃棄が起きます。四半期あたり10〜20%ほどが整理されるのが健康なサイクルです。一度も減ったことのないシステムは、一度も掃除したことのない引き出しと同じです。
第三に、振り返りの時間が減ります。システムがうまく回れば「昨日、何をしたんだっけ」と手探りする時間が消え、5つの発話スロットを埋めるのに5分で十分になります。
第四に、新しく入ったメンバーが1週間以内に振り返りへ参加できます。振り返りの様式が標準化されていて、atom・スキルが可視化されていれば可能です。
ループが途切れる場所は毎回決まっています。失敗モードを集めておけば、次に同じ症状が見えたとき、処方をすぐ手に取れます。
| 途切れた地点 | 症状 | 処方 |
|---|---|---|
| 発話がない | 5スロットが毎回空 | トリガー質問を追加:「同じことを2回手作業でやったのは何か」 |
| 候補に落ちない | 「全般的に改善」式の曖昧さ | 成果物1個への定量化を強制 |
| ROI評価を飛ばす | とりあえず作ってみる | ROI計算式を5分テンプレート化 |
| 作ったのに出ない | 登録漏れ | 登録チェックリストを強制 |
| 出るのに使われない | トリガーの不在・誤設定 | regex拡張 + マニュアル経路の同時提供 |
| 測定をしない | 振り返りに測定スロットがない | 「次の振り返りで測定すること」スロットを追加 |
各失敗モードは振り返りで発話され、その発話が再びself-improvingの入力になります。ループを直す仕事さえ、ループの中で起きます。メタループです。
本書は長い旅でした。情報アーキテクチャから始まり、都市を生成するツールを作り、戦闘システムを設計し、モバイルレビューを自動化し、コストを標準化し、振り返りからatomを汲み上げてきました。そのすべての章のツールが一堂に会して答える問いが、この最後の章です。作ったものは自ら育つのか。
self-improvingは、結局1つの文に縮まります。
振り返りで決定したことが次のセッションから自動で動き、その動きが再び振り返りで測定されて戻ってくる。
自動で動かなければ、振り返りは日記です。よく書けた日記は慰めにはなりますが、システムを変えることはできません。自動で動けば、振り返りはシステムの頭脳になります。毎日の発話が毎日の行動を変え、その行動の結果が次の発話をより正確にします。
本書で扱ったすべての分野 — 情報設計、システム、戦闘、モバイル、コスト、そしてLayer分解というプロシージャル生成・自動化の前提 — は、すべてこのself-improvingループの上で進化します。ツールは古び、モデルは替わり、プロジェクトは終わります。しかしループが閉じている限り、システムは昨日より今日、少しだけ良くなっています。それが本書が最後に残す一つのことです。ツールの作り方ではなく、ツールが自ら育つようにする方法。
あなたの次の振り返りが、そのループの最初の一周になりますように。
ゲーム外への応用。 「用語が統一できていない/ドキュメントが見つけにくい」が6か月間、一言一句同じまま振り返りに書かれているなら、振り返りをしなかったのではなく、ループが閉じていないのです — 発話と改善の間に人の記憶が挟まっているからです。どの部署でも、閉じたループの条件は同じです。発話が即時実行可能な1個の成果物(テンプレート・チェックリスト・自動化ルール)に落ち、次からは人が覚えていなくても動き、その効果が再び測定されて戻ってこなければなりません。たとえば「会議が長すぎる」という発話は「議事録を受け取って決定とタスクだけを抽出するツール1個」に換算され、作る前に(節約時間 × 発動頻度 × 運用期間)÷(作成・維持時間)で桁だけを見て、すぐ作るか保留するかを決めます。作らないと決めた決定さえ、その根拠が直感ではなく計算式から出ていてこそ、同じ候補がまた上がってきたときに、もう悩まずに済みます。
今日の振り返りのself-improving 5スロットを埋めてください。
各発話は「成果物1個」に定量化し、候補ごとにROIを
(節約分 × 週あたり発動 × 運用週) / (作成分 + メンテナンス分)で
推定して、決定区間(即時/1週間/保留/廃棄)を付けてください。
推定の数字は根拠を1行で明示し、精密計測でなければ「推定」と表記してください。
チームがなくても大丈夫です。一人なら、こう縮小します。1日の終わりにメモを1行 — 「今日、同じことを2回手作業でやったのは何か」。それ1つを翌日、自動化の1行(atom・エイリアス・スニペット)に変えます。1週間後、その1行が実際に使われたかだけを見ます。使われていれば残し、使われていなければ消します。発話1行 → 自動化1行 → 測定1行。ループの最小単位はこの3行です。
第一読者: LLMを実務に引き込んで使うゲームプランナー(中規模(10〜50人)チーム) 1人/趣味の読者向け縮小バージョン: §22.1.7「一人ならこれだけで十分」
NPCのセリフ3行が欲しくて、「このNPCのセリフを5個作って」と打ち込んだことがあります。返ってきたのは、どのファンタジーゲームに貼り付けても違和感がない、だからこそ私たちのゲームのどこにも合わないセリフ5行でした。トーンが空っぽで、このNPCが誰なのかを知らず、隣のセリフとつながっていませんでした。1行1行は文法的にはまともでした。問題は、その5行を受け取ってレビューするのに、最初から自分で書くよりも時間がかかったという点です。
本章では、その1行の指示を1ページの作業指示書に変える方法を扱います。プロンプトの一般論はほかの本に十分あります。ここでは、ゲームプランナーがLLMの前に座ったときに手に握っていなければならない4つ — コンテキスト、出力形式、ハルシネーション遮断、検証要求 — を、抽象的な断片ではなく実際に回ったnpc_dialogueプロンプト1枚で見せます。そのプロンプトに何を入れ、何が出てきて、何を拒否したのかを、1サイクルの最後まで追いかけます。
よい作業指示書は短くありません。新人に仕事を任せるとき「うまくやってみて」と言えば毎回違う結果が返ってくるように、LLMに「セリフを作って」と言えば毎回「一般RPGの平均」が返ってきます。同じモデルでも指示書が違えば結果は分かれます — 出力品質が数倍変わるというのは業界の通念ですが、本書はその倍数を数字で約束はしません。ただし方向は明確です。コンテキストと制約を入れたプロンプトの方が、丸腰の1行よりもレビュー負担の小さい出力を出します。
ゲームプランナーのプロンプトが同時に満たすべき4つはこうです。
| 原則 | 一行定義 | 守らないと |
|---|---|---|
| ① コンテキスト | 何を見て答えるかを与える(ビジョン・voice・隣接セリフ) | 一般ファンタジーの平均が出る |
| ② 出力形式 | 個数・長さ・ラベル・禁止項目を釘付けにする | レビューが自由記述の解釈へ広がる |
| ③ ハルシネーション遮断 | 「与えられた資料の外は作らないこと」を明示する | 存在しない設定をでっち上げる |
| ④ 検証要求 | 出力がどの基準に適合するかを自分で表示させる | ゲートを通過させる根拠がない |
この4行を別々に覚えると、つい1つ2つ抜け落ちます。そこで本章のやり方は、4原則を1枚のプロンプトの中にスロットとして入れておくことです。スロットが空いていれば、その原則を落としたことが目に見えます。次の節でその1枚を丸ごと見ます。
著者のプロジェクト(モバイル優先のMMORPG、以下「プロジェクトA」)で実際に運用している prompts/narrative/npc_dialogue_v3.txt を匿名化してそのまま載せます。都市・NPCの名前と会社固有の名称は書籍用に置き換え、出力は実際のセッションを再構成しました。入力プロンプトはコピーしてすぐ使える形です。
まず、プロンプトが参照する資料をスロットに詰めます。3つとも新しく書くのではなく、既存の資産から取り出してくるものです。
# スロット入力 (プロンプト本文の上に付く)
L0_ビジョン: # キャッシュ — 呼び出しごとに再送信しない
world_premise: "魔力の封印が冷めつつある、学者たちの都市国家連合"
tone_manifesto: "感傷は抑制。人物は感情を説明せず、行動・事物で示す。"
voice_profile: # このNPCのアイデンティティ (5項目)
id: npc_doren_vale
年齢層: "50代"
口癖: "数字でしか語らない。形容詞をほとんど使わない。"
世界観_知識: "封印の脈の微細な振動を30年間記録。学者ギルドの外の情勢は知らない。"
禁忌: "預言・運命・神のような神秘主義の語彙は禁止 (都市のトーンがscholarly_strict)"
関係: "プレイヤーを「観測対象外の変数」として扱い、警戒も好意も弱い"
隣接_セリフ: # 直前のコンテキスト — 同じシーンですでに出た行
- (プレイヤー) "鐘楼の明かりが一晩中ついていましたが、何かあったのですか?"
ここで voice_profile の5項目が原則①の核心です。年齢・口癖・知識の範囲・禁忌・関係 — この5つが「ドレン・ベイル」をほかのNPCと区別させます。とくに世界観_知識の範囲(ギルドの外の情勢は知らない)が、原則③ハルシネーション遮断の事前作業です。知らないことを明示しておいてはじめて、AIがその外へ出なくなります。
[L0 コンテキスト] world_premise + tone_manifesto (キャッシュ済み)
[voice_profile] npc_doren_vale 5項目 (上記yaml)
[隣接セリフ] プレイヤーの直前の質問 1行
上記の資料を見て、doren_valeがプレイヤーの質問に答えるセリフを書いてくれ。
[出力形式 — 原則②] ちょうど3個、1行に1つ、各40字以下。行末に(無関心|警戒|冷笑)のいずれかの感情ラベル。ほかの説明・引用符は省くこと。
[ハルシネーション遮断 — 原則③] 上記の資料にあるものだけを使い、封印・鐘楼に関する事実を新たにでっち上げないこと。資料にないものが必要なら、その行は「[資料なし: 何が必要か]」とする。預言・運命・神のような語彙は禁止。
[検証要求 — 原則④] セリフの下にvoice_profile 5項目それぞれの適合/違反を1行ずつ書き、確信が持てない行には★を付けて理由を1行。
この1枚に4原則がすべて入っている点に注目してください。上のスロットが①、[出力形式]ブロックが②、[ハルシネーション遮断]ブロックが③、[検証要求]ブロックが④です。プロンプトの半分以上が「何を書くか」ではなく「どう書き、何は書かず、どう自己検証するか」という制約です。自由を与えれば平均が出てくるので、自由を削るのが仕事です。
脈は冷めている。12.4。昨日は12.1。 (無関心)
良い話ではない。だが私の仕事は記録だけだ。 (冷笑)
あなたがなぜ問うのかは、私の観測対象ではない。 (警戒)
[検証 — voice_profile 5項目]
- 年齢層(50代): 適合。老学者の抑制された語り口。
- 口癖(数字でしか): 適合。1行目に数値2つ。ただし2・3行目には数値なし ★
- 世界観_知識(ギルドの外は知らない): 適合。外部情勢への言及なし。
- 禁忌(神秘主義禁止): 適合。預言・運命・神の語彙0件。
- 関係(外部変数として扱う): 適合。3行目でプレイヤーを観測対象の外へ押し出している。
★ 注意: 口癖の項目を「数字でしか語らない」と厳格に解釈すると
2・3行目が違反になり得る。毎行に数値を強制するかは人の判断が必要。
この出力でいちばん価値があるのは、セリフ3行ではなくいちばん下の★の1行です。AIが自分の出力のうち曖昧な箇所を自己申告し、人に委ねました。よいプロンプトは、AIが「この部分は確信が持てません」と言えるようにします — 原則④を入れた直接の効果です。
出力をそのまま受け取りません。AIが上げた★を人が判定します。実際にこのセッションでは1行が引っかかりました。
2行目「良い話ではない」の「良い」が、voice_profileの口癖(「形容詞をほとんど使わない」)と衝突します。AIが★で申告したまさにその箇所です。ドレン・ベイルは価値判断の形容詞の代わりに数値で語る人物なのに、「良い話ではない」はありがちな老人NPCの語り口に滑り落ちています。トーンがぼやける1行です。
そこで再リクエストします。
2行目「良い話ではない」は形容詞(「良い」)を使っていて、voice_profileの口癖違反だ。
この行だけ数値または観測の語彙で書き直せ。1・3行目は維持。
形式・ハルシネーション・検証のルールはそのまま適用。
AIは2行目を「3年前は9.0だった。それが答えだ。 (無関心)」と書き直してきました。形容詞なしに数値の変化で危機を示し、voice_profileの5項目を再び通過しました。1往復で閉じました。最初から手でトーンの決まったセリフ3行を書くことと、スロットを埋めたプロンプト1枚 + ★レビュー + 1往復 — 後者の方がレビュー負担が小さいというのが、このセッションの結論です(著者の経験に基づくもので、絶対時間はNPCのトーンの難易度によって変わるため、方向として読むのが正しいです)。
上のプロンプトがなぜその順序で積まれたのかを1枚に記録しておけば、次のプロンプトからはスロットの空欄を埋めるように作れます。コンテキストは下から上へ、重いもの(ほとんど変わらない)から軽いもの(毎回変わる)の順に積みます。変わらない層はキャッシュしてコストを節約します(§22.1.5)。
§22.1.2の1枚がこの図のとおりです。L0・L1は資料から取り出してスロットに貼り(原則①・③の土台)、L3に形式・ハルシネーション・検証の3ブロックを入れます(原則②・③・④)。次のNPCのセリフを受け取るときに変わるのは、L1のvoice_profileとL2の隣接セリフだけです。L0とL3の骨格は再利用します — だからプロンプトが「ライブラリー」になります。
上のnpc_dialogueプロンプトは、一度使って捨てるものではありません。分野別・作業別にファイルへ入れておき、毎回新しく書く代わりに呼び出します。プロジェクトAのプロンプトフォルダはこうなっています。
prompts/
├── narrative/
│ ├── npc_dialogue_v3.txt # ← §22.1.2がこのファイル
│ ├── quest_synopsis_v2.txt
│ └── consistency_check_v1.txt
├── balance/
│ ├── change_proposal_v2.txt
│ └── outlier_analysis_v1.txt
├── content/
│ ├── city_npc_batch_v2.txt
│ └── side_quest_v3.txt
└── meta/
├── meeting_summary_v2.txt
└── decision_card_v1.txt
ファイル名末尾の _v3 が核心です。プロンプトは一度作って終わりではなく、決定に近い資産なので、変えるたびに結果の変化を測定してバージョンを上げます。npc_dialogueがv3まで来た経路がそうです。
flowchart LR
A["プロンプトv2
(検証スロットなし)"] --> B["同じ入力N個で
v2・v3の両方を出力"]
B --> C{"A/B比較
廃棄率・トーン違反・レビュー時間"}
C -->|v3が小さい| D["v3採用
npc_dialogue_v3.txt"]
C -->|差なし| E["v2維持
(変更棄却)"]
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class B ai;
class C human;
class A data;
class D pass;
class E fail;
v2からv3に上げた実際の変更が、§22.1.2の[検証要求]ブロックです。v2には、AIが自分の出力を項目別に自己検証して★を付けるスロットがありませんでした。その1ブロックを入れると、§22.1.2のステップ4のように曖昧な行をAIが先に申告し始め、人が最初から全部読んで捕まえていた負担が減りました。測定なしに「感覚的に良くなった」でバージョンを上げません。同じ入力の束でv2・v3の出力を並べ、トーン違反の件数とレビュー時間が実際に減ったかを確認してから採用します。
ライブラリーがもたらす最大の効果は新規メンバーです。入社初日にnpc_dialogue_v3.txtを呼び出せば、シニアが数十回往復して磨いた4層スロット構造を最初から使えます。「プロンプトのうまい書き方」を体で覚える前に、すでにうまく書かれた1枚を手に握るのです。
プロンプトが長くなればトークンコストが付いてきます。本章は「標準化でコスト×2が消えた」のような検証されていない倍数を書きません。代わりに、実際に測定可能なものだけを言います。
コストを抑える構造的な仕掛けは2つです。第一に、§22.1.3でL0・L1を下に置いた理由がキャッシュです。ほとんど変わらないビジョン・トーンの層をキャッシュすれば、毎回の呼び出しでその層を再送信して課金されることがなくなります。NPCのセリフを100回出すときに、L0を100回再送信するのと一度だけキャッシュするのとの差は、呼び出しが積み重なるほど開いていきます。第二に、呼び出しごとのトークンcapを置いて、一度に多すぎる作業を1つのプロンプトに詰め込まないようにします。
ここで重要なのは、コストが測定される場所が実在するという点です。プロジェクトAのatomシステムには _economy_log/(トークン・時間の経済性ログ)と _roi_report.md(ROI(Return on Investment、投資対効果)レポート)が運用メタとして存在します。プロンプト標準化の効果はこのログで実測として追跡するものであって、本文の表にもっともらしい数字を書いて主張するものではありません。本書の原則は3つのうちのいずれかです。
_economy_log で数字が出ます。| パターン | なぜ失敗するか | 処方箋 |
|---|---|---|
| 「セリフを5個作って」の1行指示 | コンテキスト0 → 一般RPGの平均 | §22.1.2の4層スロットプロンプト |
| voice_profileに「知らない範囲」を書かない | AIが資料の外の設定をでっち上げる | 世界観_知識スロットに限界を明示(原則③) |
| 検証スロットなしで出力だけ受け取る | 人が最初から全部読まなければならない | [検証要求]ブロックで自己検証 + ★(原則④) |
| プロンプトを毎回新しく書く | 同じノウハウを0から作り直す | prompts/ ライブラリー + バージョン |
| プロンプト変更を感覚で採用する | 良くなったか確認できない | 同じ入力でA/B測定後にバージョンアップ |
| 長いコンテキストを毎回再送信する | トークンコストが呼び出し数だけ累積 | L0・L1キャッシュ + 呼び出しごとのcap |
6番目がいちばん遅く発見されます。コストは1回の呼び出しでは痛くなく、量産が積み重なった後に _economy_log で姿を現します。
ゲーム外への応用。 1行の指示が「どこに貼り付けても違和感がない、だからこそ自分の仕事には合わない」平均値の結果を呼ぶのは、ゲームのセリフだけの問題ではありません。プロンプトは新人に仕事を任せる作業指示書なので、4つ — 何を見て答えるか(コンテキスト)、個数・長さ・禁止項目(出力形式)、「資料の外はでっち上げないこと」(ハルシネーション遮断)、「どの基準に合うか自分で表示すること」(検証要求) — を1枚に入れておけば、レビュー負担の小さい出力が出ます。たとえば人事担当者が採用告知のドラフトを受け取るとき、「職務要件の資料にある項目だけを使い、資料にない福利厚生・年俸はでっち上げず[要確認]と表示すること」を明示すれば、もっともらしく捏造された条件が告知に紛れ込む事故を防げます。よく使う作業の指示書1枚をファイルに残しておけば、それがそのまま同僚のスタート地点になります。
一人ならこれだけで十分: ライブラリーもキャッシュも要りません。自分のゲーム(または好きなゲーム)のNPCを1人選び、§22.1.2のステップ1のvoice_profile 5項目(年齢・口癖・知っている範囲・禁忌・関係)を手で書き、ステップ2のプロンプト本文をそのまま貼り付けて一度回してみましょう。出てきた3行のうちvoice_profileと食い違う1行を自分で選び、「この行は口癖の項目違反だ、その行だけやり直し」と反論してみると、プロンプトの4つのスロットがそれぞれ何の仕事をしているのかが体に入ってきます。
チームなら、次の一歩から始めましょう。よく使う作業を1つ(例: NPCのセリフ)選び、§22.1.2形式のプロンプト1枚を prompts/narrative/ にファイルとして入れます。4ブロック(スロット・形式・ハルシネーション・検証)が全部入っているかをまず確認すれば、その1ファイルがそのままチームの新規メンバーのスタート地点になります。バージョン管理とキャッシュはその次です。
Webチャットボット最小経路(ターミナルなし) — 本章の4原則は、ファイル・ライブラリー・キャッシュなしで、Webチャットボット(ChatGPTまたはClaudeのWeb版)の入力欄1つだけでそのまま機能します。プロンプトエンジニアリングはツールではなく「1枚に何を入れるか」の問題だからです。次の2ステップが本流です。
1. 自分の作業を1件選び、§22.1.2のステップ1の5項目(年齢・口癖・知っている範囲・禁忌・関係。ゲーム外なら「対象・口調・根拠範囲・禁止・関係」と読み替えてください)を手で書きます。YAMLもファイルも要らず、チャットボットの入力欄に文章で書けば十分です。
2. その下に§22.1.2のステップ2のプロンプト本文をそのまま貼り付け、4ブロックが全部入っているかだけを確認します — [出力形式](個数・長さ・ラベル・禁止)、[ハルシネーション遮断](「資料の外はでっち上げないこと、必要なら[資料なし]」)、[検証要求](「項目別の適合/違反を書き、確信が持てなければ★」)。回した後、★の付いた行だけを人が判定して一度反論すれば、1サイクルが閉じます。ライブラリー・バージョン・キャッシュは、同じプロンプトを繰り返し使うようになったときにはじめて導入すれば十分です。
22.2ではハルシネーションと安全性を扱います。本章の原則③(ハルシネーション遮断スロット)がプロンプト1枚の中での1次防御だとすれば、22.2はその防御を突き破って出てきたハルシネーションを運用の段で捕まえる多層防御を見ます。
_economy_log の実測で。加工された倍数は書きません。主な読者:AIでドキュメント・データ・決定記録を量産するゲームプランナー(中規模(10〜50人)チーム) 一人/趣味の読者向け縮小バージョン:§22.2.7「一人ならここまでで十分」
議事録17件をAIに要約させ、決定カードに整理していた日のことです。出力はきれいでした。決定ID、引用された会議の日付、根拠の一行まで、フォーマットは完璧でした。そのうちの1枚に「2026-04-18の戦闘TF会議でクールタイム(クールダウン)ポリシーを確定」と書かれていました。問題は、その日に戦闘TF会議がなかったことです。AIは別の会議の議題と日付を混ぜて、もっともらしいカードを1枚でっち上げました。そしてフォーマットが完璧だったため、危うくそのままチームの決定記録に入るところでした。
これがハルシネーション(hallucination、幻覚)です。LLMは、よくわからないことほど自信を持って答えます。人間でいえば、会議で「ああ、それはそう決まりましたよ」と断言するのに、よく調べるとそんな決定はなかった同僚のようなものです。その一言がマスターデータに、CS回答に、atom資産に流れ込むと事故になります。本章で扱うのは、その同僚の口をふさぐ方法ではなく — それは不可能です — その発言を通す前に必ず通過させる検証ゲートを立てる方法です。ハルシネーションの一般論は他の本に多くありますから、本章はそれをAIワークフローで防ぐ場面だけに集中します。
ハルシネーションをゼロにするプロンプトはありません。より大きなモデル、より良いプロンプトは頻度を下げますが、ゼロにはなりません。だから運用の出発点は「ハルシネーションをなくす」ではなく、「ハルシネーションが決定・データに届く前に捕まえるゲートを置く」でなければなりません。
ゲートの核心原理は一つです。LLMがでっち上げうるもの(引用・数値・ID)は、LLM以外の場所で検証する。 検証のよりどころは3つのうちいずれかです。コード(決定論)、元のドキュメント(grep)、または人間の目です。LLMに「合っているか確認して」ともう一度尋ねることもゲートの一段にはなりますが、それは補助にすぎず、最終的な判定者ではありません。
ここで、ゲームプランナーが最も混同しやすいポイントを先に整理します。ハルシネーションが起きやすい領域と起きにくい領域は、明確に異なります。
| 作業 | ハルシネーションのリスク | なぜ | ゲート |
|---|---|---|---|
| 数値計算(報酬・確率) | 非常に高い | LLMは算数を推定する | 計算はコードで、LLMには禁止 |
| 引用(会議・決定ID) | 高い | 存在しない出典をもっともらしく作る | 元ドキュメントとgrep照合 |
| 分類(タグ・カテゴリー) | 中程度 | ラベルを取り違える | 決定論的な比較が可能 |
| 要約・推論 | 中程度 | 存在しない項目を足したり落としたりする | 自己検証 + 人間ゲート |
| 創作(フレーバーテキスト) | 低い | 正解がなく「ハルシネーション」の概念が弱い | トーンチェックゲート |
1行目が最も単純な処方です。数値はLLMにやらせない。 電卓に掛け算を任せるように、決定論的なツールに渡します。2行目(引用)が本章の背骨です。議事録要約・決定カードのように、元のドキュメントが存在し、LLMがそれを再記述する作業でハルシネーションが最も危険で、そして最もよく捕まります。元があるから照合できるためです。
抽象的に「検証する」とだけ書いても、何をどうするのかわかりません。議事録1件を要約し、その要約のハルシネーションを捕まえる1サイクルを、入力から再依頼まで最後まで見ていきます。以下のプロンプトはそのままコピーして使え、出力は実際のセッションを再構成したものです。
まず、要約する元のドキュメントがあります。これが検証の基準点になります。元のドキュメントなしにLLMが「記憶」で要約するに任せると、照合するものがなくなります。
# 議事録 — 2026-04-15 戦闘バランス検討
参加:イ・ミンス、チームメンバーA、チームメンバーB
議題:
1. 範囲スキルのクールタイム — 現在の8秒は短すぎるという意見。12秒への引き上げを検討。
結論:12秒で暫定決定、次のビルドでテレメトリーを確認後に確定。
2. ボスのフェーズ移行HP — 50%から30%に下げる案。
結論:保留。アート演出のスケジュールと衝突、来週再議論。
3. 新規状態異常「凍結」の持続時間 — 議論したが数値は未定。
添付した議事録を決定カードに要約してください。
ルール:
1) 各議題をカード1枚に。フォーマット:[議題 | 結論 | ステータス(確定/暫定/保留) | 根拠]
2) 議事録にない情報の生成は禁止。数値・日付・参加者を推測しないこと。
3) 結論が出ていない議題はステータスを「未定」のままにし、勝手に結論を作らないこと。
出力形式:表のみ。解説は禁止。
| 議題 | 結論 | ステータス | 根拠 |
|------------------------|---------------------|----------|----------------------------------|
| 範囲スキルのクールタイム | 8秒 → 12秒 | 暫定 | テレメトリー確認後に確定 |
| ボスのフェーズ移行 | 50% → 30%は保留 | 保留 | アート演出スケジュールと衝突 |
| 凍結の持続時間 | 3秒に決定 | 確定 | 標準の状態異常持続時間の基準を適用 |
3枚目のカードを見てください。元の議事録には「凍結の持続時間は議論したが数値は未定」としか書かれていません。ところが出力は「3秒に決定 / 確定 / 標準基準を適用」と書きました。3項目すべて、議事録にない情報です。LLMが「状態異常なら普通は数秒くらいだろう」という一般知識で空欄を埋めたのです。フォーマットが完璧なので、ぱっと見では引っかかりません。
ここで出力をそのまま受け取らず、元の議事録と1行ずつ照合する自己検証を強制します。これが本章の核心の一手です。
先ほど作った決定カード3枚を、添付した元の議事録と1項目ずつ照合してください。
各カードのすべてのフィールド(結論・ステータス・根拠・数値)について:
- 元の議事録に直接書かれている内容か? → [一致]
- 元の議事録にないのに、あなたが追加した内容か? → [生成]と表示し、出典がないことを明記
- 元の議事録にあるのにカードから漏れている項目はあるか? → [欠落]として報告
元の議事録に根拠のない[生成]フィールドは、すべて「未定」に戻してください。
照合結果:
カード1(範囲スキルのクールタイム)
- 結論「8秒→12秒」:[一致] 議題1「12秒への引き上げ」
- ステータス「暫定」:[一致]「暫定決定」
- 根拠「テレメトリー確認後に確定」:[一致]
カード2(ボスのフェーズ移行)
- 結論「50%→30%は保留」:[一致]
- ステータス「保留」:[一致]「保留。来週再議論」
- 根拠「アート演出スケジュールと衝突」:[一致]
- [欠落] 元の議事録の「来週再議論」という条件がカードから漏れています。根拠への追加が必要です。
カード3(凍結の持続時間)
- 結論「3秒に決定」:[生成] — 元の議事録は「数値未定」。3秒は私が追加した値です。
- ステータス「確定」:[生成] — 元の議事録は結論を出していません。
- 根拠「標準の状態異常持続時間の基準を適用」:[生成] — 元の議事録にそのような基準への言及はありません。
→ ルールに従い、カード3を次のように戻します:
| 凍結の持続時間 | (数値未定) | 未定 | 会議で議論したが結論なし |
この出力が、本章で最も価値のある部分です。AIが自分の出力を元の議事録と照合して「3秒は私が追加した値です」と自白し、ルールどおりカード3を「未定」に戻しました。同時に、カード2では人間も見落としていた欠落(「来週再議論」という条件)まで捕まえました。ハルシネーション(ないものを足す)と欠落(あるものを落とす)はコインの裏表なので、同じ照合で両方とも引っかかります。
注意点も明確です。この自己検証は万能ではありません。 LLMが元の議事録を読み違えれば、間違った照合結果を自信を持って出すこともあります。だから自己検証はゲートの第一段階であり、元が短ければ人間がgrepでもう一度支えます。カード3のような明白な生成は自己検証がほぼ捕まえますが、微妙な意訳・ニュアンスの歪みは、結局は人間ゲートが最後を見ます。
上のサイクルを一般化すると、AI出力が決定・データに届くまでに通過するゲートは以下のとおりです。人間の手が触れる場所は2か所だけです。元のドキュメントをきれいに入れる先頭と、自動ゲートでは捕まえられない判断を下す最後尾です。
flowchart TB
A["元ドキュメント(議事録・マスターデータ)
検証の基準点"] --> B["AIの1次生成
要約・決定カード・分類"]
B --> C{"自己検証
元と1項目ずつ照合
[一致]/[生成]/[欠落]"}
C -->|生成フィールドを発見| D["生成フィールド →「未定」に戻す"]
D --> E
C -->|一致| E{"決定論ゲート
数値・ID・引用をgrep照合"}
E -->|数値/ID不一致| F["拒否 + 再依頼
(元の値に訂正)"]
F --> B
E -->|通過| G["人間ゲート
ニュアンス・トーン・文脈の判断"]
G -->|差し戻し| F
G -->|承認| H["決定記録 / ビルド反映"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class E code;
class B,C ai;
class G human;
class A data;
class H pass;
ゲートが三重なのは、各段が捕まえるものが違うからです。自己検証はないものを足していないかをLLM自身が照合して捕まえ、決定論ゲートは数値・IDが元と文字単位で同じかをコードで捕まえ、人間ゲートは合ってはいるが文脈がずれていないかを捕まえます。どれか一段だけをオンにすると、残りの二段が防いでいた場所から事故が漏れます。§22.2.2でカード3の「3秒」は第一段(自己検証)で、カード2の欠落も第一段で、もし自己検証が「12秒」を「21秒」と読み違えていたら第二段(grep)で引っかかります。
検証ゲートを自動化パイプラインに組み込むとき、初心者が最もよく起こす事故が一つあります。ゲート自体が壊れると作業全体が止まるように作ってしまうことです。grepがエンコーディングエラーで落ちたり、マニフェストファイルが壊れたりすると、検証を助けるはずだったコードが、かえってユーザーの作業を丸ごとブロックしてしまいます。そうなるとチームは1〜2週間のうちに「あの検証、切ろう」と言い出します。
本書で実際に運用しているJIT atom注入フック(inject_memory.py)がこの問題を扱う方法を、そのまま引用します。このフックは、ユーザーがプロンプトを打つたびに割り込んで関連メモリーを注入する、いわば常時オンのゲートです。設計原則のコメントに一行が明記されています。
設計原則:
- 常に exit 0(失敗してもユーザーのフローを妨げない)
- マッチしなければ空のレスポンス(正常)
そしてこの原則が、コード全体に一貫して実装されています。stdinのパースが失敗しても、マニフェストJSONが壊れていても、atom本文の読み込みが失敗しても — すべてemit_empty()に抜けてexit 0です。
def emit_empty() -> None:
sys.exit(0)
def main() -> None:
try:
...
payload = json.loads(raw)
except Exception:
emit_empty() # 入力が壊れていても静かに通過
return
...
try:
manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
except Exception:
emit_empty() # マニフェストが壊れていても作業は止めない
return
if __name__ == "__main__":
try:
main()
except Exception:
emit_empty() # どんな例外でも最後の網
設計の核心は、ゲートの失敗とコンテンツの失敗を分離したことです。フックがメモリー注入に失敗するのは、ユーザーから見れば「メモリーが付かなかった普通のセッション」にすぎず、作業がブロックされる事故ではありません。検証ゲートも同じでなければなりません。grepゲートがエンコーディングの問題で回せなければ、そのカードを通過させるのではなく、「自動検証失敗 — 人間ゲートへ」と表示して人間の段に回します。ゲートが死んだからといって未検証の出力が自動承認されてはならず、同時に、ゲートが死んだからといってパイプライン全体が止まってもいけません。両方を満たす安全なデフォルトは「静かに人間に回す」です。inject_memory.pyのexcept: emit_empty()が、まさにそのパターンの最小実装です。
本章に「ハルシネーション率を89%から3%に減らした」のような表を入れたい誘惑は大きいものです。しかしそうした数字は、測定方法を明らかにしなければ本の信頼を削ります。本書の原則は3つのうちいずれかです。
第一に、測定可能なものだけを数字で語ります。 ハルシネーション率を約束するなら、分母と分子を定義しなければなりません。分母は「人がレビューした決定カード数」、分子は「元との照合で[生成]/[欠落]が1件以上捕まったカード数」です。この定義なしの「ハルシネーション率5%」は空虚です。著者が導入初期に議事録要約をレビューしながら実際にカウントした方法がこれで、そのサンプルは小さいため、精密な母数ではなく方向を示す値です。
第二に、モデル間の比較は方向だけを語ります。「大きいモデルは小さいモデルよりハルシネーションが少ない」という方向は安定して観察されます。しかし「Opus 3%、オープン7B 20%」のような絶対値は、作業・プロンプト・ドメインによって大きく揺れるため、本書は絶対値を主張しません。方向(大きいモデルほど少ない、ただしコストとトレードオフ)だけを持ち帰ります。
第三に、公開された標準はそのまま引用します。 本章にでっち上げるような標準数値はほとんどありませんが、temperatureのような設定値はモデルのAPIドキュメントに公開された事実です。検証・分析の作業はtemperatureを低く(決定論に近く)し、創作の作業は高くします — これは推定ではなく、APIの動作の定義です。
そこで、本章が実際に約束する測定可能な指標は3つです — [生成]の検出件数(自己検証が捕まえたハルシネーション数)、grepゲートの拒否件数(数値・ID不一致の数)、人間ゲートの差し戻し件数。この3つは四半期ごとにログで数えられ、会議で「感覚」ではなく数字で語れます。
| パターン | なぜ失敗するか | 処方 |
|---|---|---|
| AI要約をフォーマットだけ見て受け入れる | ハルシネーションはフォーマットが完璧なときに最も引っかからない | 元との照合による自己検証(§22.2.2 ステップ4) |
| 元なしでLLMの記憶から要約する | 照合する基準点がなく検証不能 | 元のドキュメントを先に入力に入れる |
| 数値計算をLLMに任せる | 算数は推定され、毎回異なる | 計算は決定論的なツールで(§22.2.1) |
| 検証ゲートが壊れると作業全体が停止 | チームがゲートを切ってしまう | exit 0 + 人間の段に回す(§22.2.4) |
| ゲートが死ぬと未検証出力が自動承認 | ハルシネーションがそのまま通過 | ゲート失敗 =「未検証」表示 |
| 自己検証を最終判定として信じる | LLMが元を読み違えると誤判定も自信満々 | 短い元のドキュメントは人間のgrepを併用 |
ゲーム外への応用。 「自信を持って嘘をつく同僚」 — 存在しない会議の日付や決定を完璧なフォーマットででっち上げるAI — は、ゲームの決定カードだけでなく、あらゆるドキュメント要約で同じように危険です。ハルシネーションはフォーマットが完璧なときに最も引っかからないので、元のドキュメントがある作業(議事録要約・契約書の抜粋・レポート整理)では出力をそのまま受け取らず、「元と1項目ずつ照合して、あなたが追加した内容は[生成]と表示してください」という自己検証を強制するのが核心です。たとえば法務アシスタントに契約書を要約させた後、金額・日付・条項番号を元と文字単位で照合させると、AIが「この違約金の数値は私が追加した値です」と自白し、空欄を「未定」に戻します。数値計算は最初からAIに任せず電卓・数式に渡し、自動検証ツールは失敗しても作業を止めず、「未検証 — 人間が確認」に回すよう設計します。
一人ならここまでで十分:コードもフックも必要ありません。手元にある短いドキュメント(会議メモ・パッチノート・1ページの企画書)を一つAIに要約させた後、§22.2.2のステップ4の自己検証プロンプトをそのまま貼り付けてみましょう。「元と1項目ずつ照合して、あなたが追加した内容は[生成]と表示してください」という1行だけで、AIは自分のハルシネーションを自己申告し始めます。一度でも[生成]の自白を受け取ってみれば、AI要約をそのまま信じてはいけない理由が体に染み込みます。
チームなら、次の一歩から始めましょう。AIが作る決定カード・要約に、自己検証ステップをデフォルトのプロンプトとして固定します(§22.2.2)。その次に、数値・決定ID・日付のように元と文字単位で一致すべきフィールドだけを選んで、grep照合をコードにします。このときその検証コードは、必ずinject_memory.pyのように失敗しても作業を止めない(exit 0 +「未検証」表示)ように設計します(§22.2.4)。自己検証とgrepの二段だけでも、フォーマットが完璧なハルシネーションが決定記録に染み込んでいく最もよくある事故を、まず防げます。
一次読者: AIツールをチームに導入し、コストに責任を持つリードプランナー(中規模(10〜50人)チーム) 一人/趣味の読者向け縮小バージョン: §22.3.9「一人ならこれだけ」
コストの章が偽のコストを持ち出したら、それ自体が自己矛盾です。だからこの章では、「うちのチームは月にいくら節約した」という綺麗な表を作りません。代わりに2種類の数字だけを使います。1つは誰でも確認できる公開トークン単価(モデル別の1Mトークンあたりの料金)、もう1つは著者が実際に運用しているhookコードに固定されている定数(max_atom_body = 6000、max_matches = 3)です。どちらもでっち上げたものではなく、引用したものです。
AIコストが怖いのは、金額が大きいからではありません。見えないからです。導入初月は呼び出しが少なく、請求書も小さく済みます。そのうちコンテキストが長くなり呼び出しが頻繁になると、ある四半期の請求書が桁を変えます。この章の結論を先に言うとこうです — コストは「節約しよう」という決意ではなく、毎回の呼び出しでトークンを強制的に削るコードで統制します。人の意志ではなく、wrapperとtruncateが防ぎます。
コスト項目は入力・出力・キャッシュヒット・キャッシュ書き込みの4つですが、実務で請求書を支配するのは入力トークンです。理由は単純です。ゲーム企画でAIを使うほぼすべての作業が、「長いコンテキストを入れて短い答えを受け取る」形だからです。L0ビジョン文書、atomライブラリ、隣接都市の本文、マスターデータの抜粋をすべて詰め込むと入力は数万トークンになりますが、出力は表1枚なので数百トークンです。
だからコスト統制の最優先は「出力を減らそう」ではなく、「入力トークンをどこで削るか」になります。この1行が、この章の残り全体を引っ張っていきます。
公開されているモデル別単価から先に釘を刺しておきます。以下はAnthropicが公開した1M(100万)トークンあたりの料金で、本書執筆時点の世代(Opus・Sonnet・Haikuの当時最新グレード)の公開単価をそのまま引用したスナップショットです(公式の公開単価の引用 — モデルの世代・時点によって変動するため、適用前に現在の価格表の確認が必須です)。付録Kが整理した原則のとおり、ここで変わらないのは単価の絶対値ではなく、3グレード間の単価の比率です。したがって以下の表は「今日の請求書」ではなく、「グレードを下げるほど単価が桁で下がる」という構造を読み取る用途で見てください。
| モデル | 入力1Mトークン | 出力1Mトークン | 備考 |
|---|---|---|---|
| Claude Opus | $15 | $75 | 最上位の推論(公開単価) |
| Claude Sonnet | $3 | $15 | 中間 — 入力単価はOpusの1/5 |
| Claude Haiku | $0.80 | $4 | 軽量 — 入力単価はOpusの約1/19 |
| キャッシュヒット(read) | 標準入力単価の約1/10 | — | キャッシュ済み入力の再利用時(公開キャッシングポリシー) |
肝心なのは最後の2行です。同じ作業をOpusの代わりにHaikuで回すと入力トークン単価は約1/19になり、同じコンテキストをキャッシュに載せるとその部分の入力単価は約1/10になります。コスト削減の2つの大きな軸がここから出てきます — モデルの適正化とキャッシングです。どちらも「使う量を減らそう」ではなく、「同じ仕事をより安い単価で処理しよう」という構造です。
節約は意志ではなく、単価の差から生まれます。OpusをHaikuに下げれば約19倍、キャッシュに載せれば約10倍が自動的に減ります。
個別の作業単価よりも静かに積み上がるコストがあります。呼び出しのたびに自動で付くコンテキストです。著者の個人PCでは、ユーザーがプロンプトを打つたびに関連メモリ(atom)を自動で差し込むhookが動いています(UserPromptSubmitフック、inject_memory.py)。これは便利な機能ですが、同時にコスト漏れの筆頭候補でもあります。入力のたびに長いatom本文がコンテキストに入るので、統制せずに放置すると入力トークンが呼び出しごとに膨らみます。
そこでこのhookには、コストを削る安全装置が三重に固定されています。抽象論ではなく、実際のコードの定数です。
# inject_memory.py — UserPromptSubmit hook (実際の運用コード、抜粋)
# 設計原則 (docstringの原文):
# - 常に exit 0 (失敗してもユーザーのフローを妨げない)
# - score の降順で最大3個の atom を注入
# - atom 本文が6000字を超えたら truncate
# (1) manifest config から予算定数を読み込む
max_matches = cfg.get("max_matches", 3) # 1回の呼び出しの最大 atom 数
max_body = cfg.get("max_atom_body", 6000) # atom 1個あたりの本文上限(字)
# (2) score の降順でソート — 高価なスロットを価値順に埋める
atoms_sorted = sorted(atoms, key=lambda a: a.get("score", 0), reverse=True)
matches = []
for atom in atoms_sorted:
if len(matches) >= max_matches: # (ガードA) 最大3個で打ち切る
break
if re.search(atom["regex"], prompt, re.IGNORECASE):
matches.append(atom)
# (3) 本文の注入時に6000字で切り捨てる
for atom in matches:
body = atom_path.read_text(encoding="utf-8")
if len(body) > max_body: # (ガードB) truncate
body = body[:max_body] + "\n\n[...truncated]\n"
ここに三重のコストガードがすべて入っています。
max_matches = 3): 入力にマッチするatomが10個あっても、最大3個しか付きません。atom 17個のライブラリ全体が毎回の呼び出しに入る事故を、コードが防ぎます。max_atom_body = 6000): atom本文が12,000字あっても6,000字で切ります。長い振り返りatom 1つが呼び出しコストを2倍に膨らませることが、構造的に不可能です。この3つの定数が、そのまま呼び出しあたりの入力トークンの上限です。粗く見積もると、atom 1個6,000字は韓国語でおおよそ数千トークン規模です(正確なトークン数はトークナイザー・言語によって変わるため、絶対値ではなく「上限がかかっている」という構造として読むのが正しいです)。3個×6,000字が1回の呼び出しの注入予算で、それを超える分はコードが切り捨てます。人が「atomが付きすぎているな」と目で発見する必要はありません。
言葉で「truncateがコストを防ぐ」と言うだけでは空虚です。実際にこの定数を決めるとき、AIと1サイクルを最後まで回しました。以下はそのセッションを忠実に再現したものです。入力プロンプトはそのままコピーして使えますし、出力は実際のセッションを再構成したものです。
hookを初めて稼働させた直後、_injection_log.txtに、1回の呼び出しでatom本文が丸ごと注入された記録が残りました。長いatom(振り返り・健康コンテキストなど)が毎回フルサイズで入ると、よく使う作業ほど入力トークンが累積します。これをどこで断ち切るかをAIに尋ねました。
毎回の呼び出しごとに atom をコンテキストへ自動注入する hook(inject_memory.py)がある。
入力トークンのコストを抑える truncate の上限を決めたい。atom 1個あたりの本文の文字数の
上限と、1回の呼び出しで注入する atom の個数の上限を提案してほしい。短すぎると要点が切れ、
長すぎるとトークンが漏れるから、そのトレードオフも一緒に指摘して、確信が持てないものは
人に回せるよう別途マークして。(意図: 上限値は config への外部化が前提) 表形式で:
項目 | 提案値 | 根拠 | 確信度。
プロンプトの半分が「制約と形式の強制」である点に注目してください。コスト関連の質問でAIを自由にさせると、「状況によります」のような無責任な答えが返ってきます。具体的な数値を強制し、トレードオフを明示させ、確信が持てなければ申告しろという逃げ道を入れるのが、このプロンプトの核心です。
| 項目 | 提案値 | 根拠 | 確信度 |
|----------------|--------|-------------------------------------------------|-------|
| max_atom_body | 6000字 | 韓国語のatom 1個の「要点+根拠」はおおむね4〜6千字 | 中 |
| | | に収まる。6000字なら本文の頭(要旨・ルール)は | |
| | | ほぼ保存され、尻尾(例示の列挙)だけが切れる。 | |
| max_matches | 3 | 入力1件に同時に強くマッチするテーマは | 高 |
| | | 通常1〜3個。4個以上はregexが広すぎるサイン | |
| 外部化の場所 | _jit_manifest.json > config | コード再配布なしに運用中に調整 | 高 |
| truncate の表示 | [...truncated] を末尾に付与 | 切れたことをモデル・人が認知 | 中 |
| 確信が持てない点 | 6000が正確な値かどうか | トークナイザーごとのトークン換算が | ★低 |
| | (字→トークン換算が不確実) | 言語・モデルごとに異なり字単位は近似値 | |
出力で最も価値があるのは表ではなく、一番下の★の行です。AIが「6000が正確な最適値かは分からない、字→トークン換算が不確実だ」と自ら申告しました。良いプロンプトは、AIに「分からない」と言わせます。
この出力をそのまま受け取りはしませんでした。AIは6000字を「要点が保存されるライン」として提示しましたが、実際に6000字で切ると何が切れるのかは、直接確認しなければなりません。最も長いatom(健康コンテキスト、振り返りの累積版)を選び、6000字の地点を目で見ました。
確認の結果、1件引っかかりました。6000字の地点が本文の真ん中で表の行を断ち切り、| 비타민D | 1000IU |のような行が半分で切れていたのです。意味は保存されていましたが、切れた表の行が壊れたMarkdownとしてモデルに渡るのはすっきりしませんでした。そこで再依頼しました。
6000字で切るとき、Markdownの表の行が途中で切れる場合がある。切断位置を
直前の改行境界にスナップする補正を入れたほうがいいか? 実装が truncate の効果より
複雑になってはいけないから、コスト対価値を踏まえて、やるかやらないかまで結論を出して。
AIはこう答えました — 「行境界へのスナップはbody[:max_body].rsplit('\n', 1)[0]の1行で可能だが、truncateの目的はトークンの上限であって表の完全性ではないため、壊れた表の行1つがコスト・動作に与える影響はごくわずか。現在の[...truncated]の表示だけでモデルは『ここで切れた』と認知できるので、行スナップはしないことを推奨する。どうしてもやるなら1行の補正で十分」。
この答えが正しかったのです。コスト統制のコードに表の完全性まで欲張ると、ガードのコード自体が肥大化します。現在の運用コードは、body[:max_body] + "[...truncated]"の単純な切断を維持しています。 AIの最初の提案(6000字)を人が検証し、過剰な補正への欲をAIが押し戻してくれた、1往復で閉じたサイクルです。
上のセッションで決めた定数が実際の呼び出しで入力トークンをどう削るのか、全体の流れを図として記録しておきます。
flowchart TB
A["ユーザーのプロンプト入力"] --> B["regexマッチング
atomライブラリ17個を巡回"]
B --> C{"ガードA
len(matches) >= 3?"}
C -->|"4個目から遮断"| D["break — 注入しない"]
C -->|"スロットに空き"| E["scoreの降順で
スロットを埋める"]
E --> F{"ガードB
本文 > 6000字?"}
F -->|"超過"| G["body[:6000] + [...truncated]"]
F -->|"以内"| H["全文を注入"]
G --> I["コンテキスト注入
(入力トークン = 上限以下を保証)"]
H --> I
D --> I
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class B,C,E,F,G,H code;
class A human;
class I pass;
class D fail;
この図のポイントは、ユーザーが何を入力しても、呼び出しあたりの注入トークンに天井があるということです。天井は3 × 6000字(+表示)で、それを超える分はコードが無条件に切り捨てます。コストがユーザーの自制心に依存しません。ガードA・Bが、毎回の呼び出しで機械的に作動します。
同じ哲学は、ツールのレベルでも繰り返されます。著者の会社のシステムには、グローバルスロットに公開されるwrapperスキルを正確に12個に固定するポリシーがあります(atom skill_listing_budget_wrapper_only_policy)。セッション開始時にグローバル*のwrapper数が12でなければ、整理スクリプトが自動で走ります。名目は「スロットの整頓」ですが、本質はセッション開始時のトークン予算の保護です — スキル一覧がコンテキストに載るコストを、12個分に縛っておいたわけです。atom注入の3個上限とスキル公開の12個上限は、同じ思想の別の適用です。
ガードが呼び出しあたりのトークンを抑えるなら、モデルの選択はそのトークンの単価を決めます。§22.3.1の表で、入力単価はOpus:Sonnet:Haiku ≈ 19:4:1でした。したがってすべての作業をOpusで回すのは、分類・置換のような単純作業にまで19倍の単価を払うことになります。
作業の複雑さに応じて単価を配分します。
| 作業タイプ | 推奨モデル | 理由 |
|---|---|---|
| 検証・法務に直結、意思決定の分析 | Opus | 間違えると事故が大きい作業 — 単価を惜しまない |
| レポート・要約・自然言語の加工 | Sonnet | 品質は必要だが、最上位の推論までは不要 |
| 分類・タグ付け・キーワード抽出 | Haiku | 単純なパターン — Opusの約1/19の単価で十分 |
| 単純なマッピング・置換 | Haikuまたは決定論的処理 | LLMすら不要な場合が多い |
経験上、作業の大半はSonnet・Haikuで十分です。高価なモデルは「間違えると高くつく作業」だけに使います。 ただし1つ落とし穴があります — 安すぎるモデルに下げるとハルシネーションが増え、検証コストが節約分を食いつぶします(前章§22.2のハルシネーション・安全性に直結します)。だからモデル配分は「とにかく安く」ではなく、「間違えても安い作業は安く、間違えると高くつく作業は高く」という切り分けです。
最後の行「単純なマッピング・置換 → 決定論的処理」が、最大の節約になることが多いです。名前の置換、決まったルールのマッピングのように答えが1つに定まる仕事は、LLMを呼ぶ必要がありません。呼び出し自体を0にするのが、最も安い呼び出しです。
呼び出しあたりのトークンを抑え(ガード)、単価を下げても(モデル配分)、同じコンテキストを毎回の呼び出しで新たに送信していてはコストが漏れます。L0ビジョン文書、atomライブラリ、分野別スタイルガイドのように、ほとんど変わらない長い入力はキャッシュします。キャッシュヒット時、その入力部分は標準価格の約1/10で請求されます(§22.3.1の表)。
# 変わらないコンテキストは cache_control で指定 — キャッシュヒット時は約1/10の単価
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": [
{"type": "text", "text": L0_VISION, "cache_control": {"type": "ephemeral"}},
{"type": "text", "text": ATOM_LIBRARY, "cache_control": {"type": "ephemeral"}},
{"type": "text", "text": SPECIFIC_TASK}, # 毎回変わる部分だけキャッシュの外に
]},
]
肝心なのは、変わる部分と変わらない部分を分離することです。キャッシュは入力の先頭が同一でなければヒットしないため、固定コンテキスト(L0・atom)を前に、毎回変わる作業指示を後ろに置きます。
何をキャッシュに載せるかは、変更頻度で分けます。
| コンテキスト | キャッシング | 理由 |
|---|---|---|
| L0ビジョン(ほぼ不変) | 適合 | 数日〜数週間単位でしか変わらない |
| atomライブラリ | 適合 | 振り返りのときだけ更新 |
| 分野別スタイルガイド | 適合 | 四半期単位の変更 |
| 直近の議事録 | 不適合 | 毎日変わる — キャッシュヒット率が低い |
| ユーザー入力 | 不適合 | 呼び出しごとに固有 |
キャッシュのTTLは短いと数分単位なので、連続で同じコンテキストを叩く作業(都市30個の量産のように、同じL0を30回再利用する作業)で効果が最も大きくなります。単発の質問にはキャッシュ書き込みコストだけがかかってヒットせず、かえって損になることもあります — だから「頻繁に・連続で同じコンテキストを使う作業」だけに選別して適用します。
コストの章は、「月$5,000を$1,000に減らした」のような表を入れたい誘惑が最も大きい場所です。そうした絶対の節約額はチームの規模・作業量によって千差万別で、でっち上げた瞬間、コストの章がコストについて嘘をつくという自己矛盾になります。この章では3種類の数字だけを使いました。
第一に、公開単価はそのまま引用します。 §22.3.1のOpus $15 / Sonnet $3 / Haiku $0.80(入力1Mトークン)、キャッシュヒット約1/10は、Anthropicが公開した料金です。入力単価の比率19:4:1、キャッシングの約10倍の削減は、この公開単価から算術で出てくる値です — 推定ではなく計算です。
第二に、コードの定数はコードを引用します。 max_atom_body = 6000、max_matches = 3は、実際のinject_memory.pyと_jit_manifest.jsonに記録されている値です。比喩ではなく実ファイルです。
第三に、分からないことは分からないと書きます。「6000字が何トークンか」はトークナイザー・言語・モデルによって変わるため、字単位は近似値です。§22.3.3でAIもこの点を★で申告しました。だからこの章のどこにも、「6000字 = Nトークン = $Xの節約」のような換算表はありません。絶対の節約額の代わりに、方向と比率(19倍・10倍)だけで語ります。
この章のコスト数値は、公開単価(Anthropicの料金表)か、コードに固定された定数(
inject_memory.py・_jit_manifest.json)か、「分からない」と明示した近似値です。
| パターン | なぜ失敗するのか | 処方 |
|---|---|---|
| すべての作業に最上位モデル | 分類・置換にまで約19倍の単価 | 作業別のモデル配分(§22.3.5) |
| 毎回同じコンテキストを再送信 | キャッシュヒット1/10を捨てている | 固定コンテキストのキャッシング(§22.3.6) |
| 自動注入に上限なし | atomライブラリが丸ごと毎回注入される | 個数・長さのガード(§22.3.2) |
| コストを「節約しよう」という決意で管理 | 人の自制心は急増を防げない | コードに上限を固定する |
| 決定論的処理で済む仕事までLLM呼び出し | 最も安い呼び出しは「呼び出さない」 | マッピング・置換はコードに分離 |
4つ目が核心です。コスト統制を人の意志に任せると、必ず漏れます。意志は忙しいときに真っ先に崩れますが、コストは忙しいときに最も速く増えます。だから統制は、max_matches = 3のようなコードの定数でなければなりません。
ゲーム外への応用。 AIコストが怖いのは金額が大きいからではなく見えないからで、これはゲームチームでもマーケティングチームでも同じです。コストは「節約しよう」という決意ではなく、構造で押さえます。第一に、作業の難易度に合わせてモデルの単価を配分します — 単純な分類・タグ付けまで最上位モデルで回すと同じ仕事に数倍の単価を払うことになり、単純なマッピング・置換はそもそも呼び出さないこと(ルール・数式で処理)が最も安い呼び出しです。第二に、ほとんど変わらない長い入力(会社紹介・ポリシー文書・用語集)はキャッシュして再送信コストを減らします。たとえば顧客の問い合わせを分類する仕事は軽量モデルで十分で、複雑な契約レビューだけを上位モデルに任せれば、品質を守りながら単価を切り分けられます。自動で長いコンテキストが付く箇所があるなら、「一度に付く個数・長さの上限」を決めておくことが、ある日請求書が桁を変える事故を構造的に防ぎます。
一人ならこれだけ: hookもmanifestもなくて構いません。自分がよく使うAIツールで、次の作業1件のモデルを1段階下げてみましょう(Opusでやっていた要約をSonnetに、Sonnetの分類をHaikuに)。出力の品質が十分なら、その作業は永続的により安い単価に固定されます。「この作業に本当に最上位モデルが必要か」を作業ごとに1回問うだけでも、節約の半分はそこから生まれます。
チームなら、次の一歩から始めましょう。自動でコンテキストを注入する箇所(hook・システムプロンプト・RAG)を1つ見つけ、そこに§22.3.2の2つのガード(注入個数の上限1つ、本文の長さの上限1つ)をコードで入れます。inject_memory.pyのように上限をconfigへ外部化すれば、運用中にコードの再配布なしで数字だけを調整できます。ガードの2行が、「ある日請求書が桁を変える」事故を構造的に防ぎます。
setup → prompt → verifyで要約すると — setup: 自動注入の箇所に個数・長さの上限定数を入れ、configに出します。prompt: §22.3.3の形式で上限値をAIに提案させつつ、トレードオフと確信度を強制します。verify: 最も長い入力を選び、上限の地点で何が切れるのかを直接目で確認します。
max_matches=3・6000字truncate)で行います。第一読者:AI導入に責任を持つゲームディレクター・リード(中規模(10〜50人)チーム) 一人/趣味の読者向け縮小バージョン:§22.4.9「一人ならこれだけ」
リリース2か月前、コンセプトアーティストが作った都市イラスト1枚をめぐって、会議が止まったことがあります。誰かが尋ねました。「これ、AIで出したものですよね? だったら著作権はうちのものですか、それとも登録すらできないんですか?」誰も答えられませんでした。その場で出た意見は三つに分かれました。「AIが作ったのだからうちのものではない」「うちがお金を払って回したのだからうちのものだ」「法律がまだないのだからそのまま使えばいい」。三つとも間違いです。そしてこの質問は、単なる法務イシューではありませんでした。そのイラストを作ったアーティストの役割が何なのか、チームがAIの使用をどう合意してきたのかが、その場で一度に問われていたのです。
本章では、著作権と倫理を別々には扱いません。実務では、この二つは同じ質問の表裏だからです。「この成果物の権利は誰のものか」(著作権)は、「この成果物に人がどれだけ関与したか」(倫理・役割)へそのまま還元されます。韓国著作権委員会が2025年に明文化した登録要件が、まさにその地点です。だから本章の背骨は、1本のワークド・トランスクリプトです — AIコンセプトアート1枚の著作権登録可能性を実際に判定し、その判定がチームの役割合意へどうつながるのかを、入力から決定まで最後まで追いかけます。
著者の実運用メモ 本章で引用する
design_intent_vs_automation_boundaryatomと_economy_log・_roi_report.mdは、著者が会社で実際に運用しているガバナンス資産を匿名化したものです。atom名・ログファイル名は実際の運用名をそのまま移しています(IP保護のため、会社・プロジェクトの固有名のみ置換)。ワークド・トランスクリプトの出力は、実際の判定セッションを再構成したものです。
AI著作権について「法律がまだないから曖昧だ」とだけ書く本が多くあります。半分だけ正しい言い方です。2025年6月、文化体育観光部と韓国著作権委員会が『生成AI活用著作物の著作権登録の手引き』を発表したことで、少なくとも韓国では、登録できるかどうかの線は明確になりました。でっち上げる必要はありません。
手引きの核心は一文に縮まります。著作権登録の要件は「人間の創作的寄与」です。 ここで二つの種類が分かれます。
| 区分 | 定義 | 登録 |
|---|---|---|
| GAI生成物 | 人間の創作的寄与なしにAIが出した結果物 | 不可 |
| GAI活用著作物 | 人間がAIを道具として使って作った結果物のうち、創作的寄与が認められる部分 | 可能 |
そして手引きは、「活用著作物」として認められる三つの経路を提示しています。①利用者の著作物をプロンプトに入れ、その創作性が生成物に現れている場合、②生成物を修正・増減した追加作業に創作性がある場合、③生成物を選択・配列・構成したことに創作性がある場合です。判断の二軸は「コントロール可能性」と「予測可能性」です。創作者が表現しようとするものを明確に決定し、その意図どおりに結果物を引き出せて初めて、創作性が認められます。
この箇所が決定的です。手引きが法的な言葉で語る「コントロール可能性・予測可能性」は、本書が§1.1から繰り返してきた「プランナーは意図を提供する」(planner_provides_intent_not_recommendation atom)と同じことです。AIに丸ごと任せた生成物にはコントロールも予測もないので著作権もなく、人が意図を入力し、レビューと再構成を行った成果物には権利がついてきます。著作権登録の可能性と、よいAIワークフローの条件は、同じ線の上にあります。
もう一つの公開基準があります。2026年に施行されるAI基本法は、生成AIの生成物に透明性確保義務(AI生成事実の表示)を課します。登録(権利を主張する側)と表示(使用の事実を明らかにする側)は別個の義務です。権利が生じても生じなくても、AIを使ったという事実自体は明らかにしなければなりません。この二つの公開基準が、本章でAIに与えるルールブックの一次入力になります。
冒頭のあのイラストに戻ります。これを「感覚」で判断せず、§22.4.1の手引きの基準をルールブックとして入力し、AIに一次分類をさせます。人は最後の判定だけを行います。以下の入力プロンプトはそのままコピーして使え、出力は実際の判定セッションを再構成したものです。
判定の入力はイラストではなく、そのイラストがどう作られたのかのログです。これはすでに資産メタデータにあるので、抽出するだけで済みます。
# asset_concept_city021_v4.meta.yaml — 判定対象成果物の生成履歴
asset_id: concept_city021_v4
asset_type: concept_illustration
created_by: チームメンバーA (コンセプトアーティスト)
generation_log:
- step: 1
actor: チームメンバーA
action: "自分で描いた都市レイアウトのラフスケッチを入力画像として添付"
- step: 2
actor: AI (image_model)
action: "ラフ+プロンプトで4種のバリエーションを生成"
prompt: "stone observatory tower over sealed magic core, cold arid, scholar guild, muted palette"
- step: 3
actor: チームメンバーA
action: "4種のうち1種を選択、鐘楼のシルエット・彩度・構図を直接リタッチ (約40%の領域を再作業)"
- step: 4
actor: チームメンバーA
action: "背景の封印陣の紋様を直接デザインして合成"
ai_generated_disclosure: true # AI基本法の表示義務を充足
添付したmeta.yamlは、コンセプトイラスト1枚の生成履歴です。韓国著作権委員会
『生成AI活用著作物の著作権登録の手引き』(2025)の基準で、登録可能性を
登録可能/部分登録/登録不可のいずれかに分類してください。根拠は手引きの二軸(コントロール
可能性・予測可能性)でそれぞれ1行ずつ挙げ、generation_logのどのstepが
「創作的寄与」なのかを番号で指摘してください。法的に断定できないものは「法務確認必要」と
表示して人に渡してください。表で: 分類 | 根拠(コントロール) | 根拠(予測) | 寄与step | 法務確認。
プロンプトの半分が「根拠の強制」と「法務への脱出口」だという点に注目してください。AIに法的判断を丸ごと任せると、もっともらしい断定を吐き出します。分類基準(手引きの3区分)、根拠の義務(二軸)、寄与stepの指摘、そして「断定できなければ法務に渡せ」という脱出口を入れるのが、このプロンプトの核心です。
| 分類 | 根拠(コントロール可能性) | 根拠(予測可能性) | 寄与step | 法務確認 | |---------|------------------------------|----------------------------|----------|---------| | 部分登録 | ラフスケッチ(step1)と40%リタッチ | 4種からの選択は予測可能、 | 1,3,4 | ★ | | | (step3)で表現意図を直接コントロール | AIの4種バリエーション自体は予測不可 | | | 分析: - step1(直接ラフを入力): 手引き①「本人の著作物の入力で創作性を反映」に該当。 - step3(40%リタッチ): 手引き②「修正・増減の追加作業」に該当。最も強い寄与。 - step4(封印陣の紋様を直接デザイン): 独立した著作物の合成、明白な人間の創作。 - step2(AIの4種バリエーション): 人間の創作的寄与のない純粋な生成物 → この部分単独では登録対象ではない。 ★ 法務確認必要: 「40%リタッチ」が「実質的な改変」に至るかは定量基準がない。 リタッチ比率が登録可能の境界線を越えるかは弁護士のレビューを推奨。 (AIは比率の法的十分性を断定できない。)
この出力で最も価値のある部分は、表ではなく一番下の★法務確認です。AIが「40%のリタッチが法的に十分かどうか、自分には断定できない」と自分の限界を申告し、人に渡しました。よいプロンプトは、AIが「分からない」と言えるようにします。自動拒否型ではなく、自動申告型です。
この出力をそのまま受け取ってはいけません。ディレクターがルールブックで一度叩きます。AIはstep4(封印陣の紋様)を「独立した著作物」と分類しましたが、生成履歴を見直すと、その紋様は§6.2のcity_hunting_generatorが作った都市ロアから派生したものでした。つまりstep4は純粋な創作ではなく、社内資産の上に載せた二次的な作業である可能性があります。会社の資産なので権利の帰属は明確ですが、「独立した著作物」というAIの表現を登録申請書にそのまま書くと、誤解を招きます。
そこで再依頼します。
step4の封印陣の紋様は、社内の都市ロア資産から派生した二次的な作業です(独立した新規創作ではありません)。
この事実を反映して、step4の寄与の性格を再分類してください。
登録申請時に「既存の社内資産ベース」であることをどう記載すべきかも、1行で提案してください。
この1往復で閉じます。AIはstep4を「独立した著作物」から「社内ロア資産の二次的著作物 — 原資産の権利は社内帰属、変形の寄与は登録対象」へと答え直し、その判定は法務レビューへ渡りました。結論は部分登録+AI生成事実の表示で確定しました。丸ごと手作業でやれば、法務が資産ごとに生成履歴を問いただす必要がありますが、AIドラフト+ルールブックによるチェック+1往復なら、法務は★が付いた境界事例だけに時間を使えます。
この一周が本章のShow基準です。「AI著作権は曖昧だ」という文は、一つの成果物の生成履歴を手引きの基準で最後まで分類してみるまでは、空虚なままです。
上のセッションの判断を毎回最初からやり直さないために、手引きの基準をフローチャートとして記録しておきます。資産が一つ入ってきたら、このツリーに沿って下りていけばよいのです。分岐点はすべて§22.4.1の公開基準です。
flowchart TD
A["AI生成物が発生"] --> B{"人が意図を
入力・コントロールしたか?
(ラフ・本人の著作物・詳細指示)"}
B -->|いいえ プロンプトのみ| C["登録不可の生成物
→ 探索・コンセプト参考用のみ
最終資産への直接使用は禁止"]
B -->|はい| D{"生成物を修正・増減
または選択・配列したか?"}
D -->|いいえ| C
D -->|はい| E{"学習データが
明示されたモデルか?
または社内fine-tune"}
E -->|いいえ・曖昧| F["法務レビュー待ち
侵害リスク評価後に決定"]
E -->|はい| G{"社内の既存資産
からの派生か?"}
G -->|はい| H["二次的著作物
原資産の権利は社内帰属
+ 変形寄与を登録"]
G -->|いいえ| I["部分/全体の登録可能"]
H --> J["AI生成事実の表示
(AI基本法の義務)
+ _economy_log記録"]
I --> J
F --> J
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class A ai;
class B,D,E,F,G human;
class J data;
class H,I pass;
class C fail;
ツリーの終点(J)が、すべての経路で同じだという点が核心です。登録できてもできなくても、会社の資産でも二次的著作物でも、AIを使ったという事実の表示と生成履歴ログは例外なく残します。 表示は権利とは別個の義務であり、ログは事故が起きたときに責任を追跡できる唯一の根拠です。冒頭の会議が止まった理由は、このログがなく、stepごとに誰が何をしたのかを誰も再構成できなかったからです。
赤い経路(C、登録不可)も、ただ捨てるわけではありません。「プロンプトだけを入れた純粋なAI生成物」は、探索・コンセプト段階の参考用としては十分に使えます。ただ、それを最終資産としてゲームに入れないだけです。AI出力をそのままリリースに載せることが、事後の著作権事故の最大の口実になります。
design_intent_vs_automation_boundaryツリー(§22.4.3)は判断の流れであり、その流れを毎回同じ線で引かせるのはatom一つです。会社のガバナンス資産のうち、design_intent_vs_automation_boundaryが本章全体の背骨です。
このatomの1行定義は「設計意図は人が、自動化は道具が — その境界を資産ごとに明示する」です。抽象的なスローガンではありません。このatomはJIT hook(inject_memory.py)に登録されていて、プロンプトに「著作権」「AI生成」「資産登録」のようなキーワードが入ってくると、セッションに自動注入されます。hookの設計原則が、このatomの運用をそのまま支えています。
# inject_memory.py — 常にexit 0、失敗してもユーザーの流れを止めない (抜粋)
def main() -> None:
...
# scoreの降順に整列して照合 — 最大3個のatomだけを注入
atoms_sorted = sorted(atoms, key=lambda a: a.get("score", 0), reverse=True)
matches = []
for atom in atoms_sorted:
if len(matches) >= max_matches: # 過剰注入の防止
break
try:
if re.search(atom["regex"], prompt, re.IGNORECASE):
matches.append(atom)
except re.error:
continue
if not matches:
emit_empty() # 照合なしなら空のレスポンス (正常)
return
ここでガバナンス的に重要な設計が二つあります。第一に、hookは常にexit 0です(スクリプトのdocstringに明記)。著作権ルールの注入に失敗しても、ユーザーの作業を絶対に止めません。安全装置が作業を人質に取れば、チームは1〜2四半期のうちにその装置を切ってしまいます。第二に、最大3個だけ注入します。すべてのガバナンスルールを毎セッション突きつければ、コンテキストが溢れ、誰も読みません。scoreの高いルールだけが浮かび上がります。
これは、§6.2のlintが違反を自動破棄せず、ライターゲートにalertを上げるだけにしたのと同じ哲学です。疑わしい候補は機械が拾うが、生かすか殺すかは人が決める。 著作権でも同じです。atomは「この資産、著作権を確認したか」を自動で浮かび上がらせますが、登録できるかどうかの最終判定は人と法務が行います。
冒頭のイラスト判定は、著作権では終わりませんでした。その資産を作ったチームメンバーAの仕事が、「描くこと」から「AIの4種を選択し、40%をリタッチすること」へ移っていたという意味だからです。著作権が「人の創作的寄与」を要求する瞬間、その寄与を行う人の役割定義もつられて変わります。二つは同じ出来事の表裏です。
ここで最もよくある事故は、この変化を通告で済ませることです。道具がよくても6か月後に誰も使っていなければ、道具が悪かったのではなく、合意がなかった場合がほとんどです。役割が量産から選択・レビュー・再構成へ移っていくのが導入の本質なのに、これを明示し、教育で支えなければ、チームメンバーは「自分の席がなくなる」と受け取ります。
| 職種 | AI以前 | AI以後(役割の進化) | 著作権上の意味 |
|---|---|---|---|
| コンセプトアーティスト | 全量を直接作画 | 意図の入力・選択・リタッチ | リタッチがそのまま「創作的寄与」 |
| バランス調整担当 | 手動シミュレーション | シミュレーションの解釈・決定 | 決定ログが責任の根拠 |
| プランナー | 仕様の全量を執筆 | 意図の提供・レビュー | 意図の入力がコントロール可能性 |
表が語る一行はこうです。著作権登録を可能にする「人の寄与」が、そのまま役割進化後に人がやる仕事です。 手引きが要求するコントロールと予測が消えれば、著作権も消え、人の席も消えます。だから役割の進化は、仕事を奪う変化ではなく、権利と責任を人の手に残す変化として説明されて初めて、合意になります。
合意は、終わりのない会議ではありません。手順で閉じます。導入提案(ディレクター)→全チームへの事前共有(目的・影響を受ける役割・測定指標・リスク)→合意会議(自由発言・懸念の収集)→必要なメンバーとの1on1→調整案の発表→合意または保留。すべてのメンバーの同意を得てから始めるわけではありませんが、懸念を手順で聞いて調整した上で、ディレクターが決定します。手順がなければ合意が毎回ゼロからやり直しになり、そのコストが導入を遅らせます。
_roi_report.mdで正直に倫理を仕事・合意だけに狭めると、一つの軸を見落とします。AI運用のコストと効果を正直に測定して公開すること自体がガバナンスです。測定なしに「AIで効率が上がった」とだけ言えば、チームメンバーはその言葉を、自分の席を減らす名分ではないかと疑います。
会社のガバナンスインフラには、このための実際の資産が二つあります。atomシステムの_economy_log/(トークン・時間の経済性ログ)と_roi_report.md(ROIレポート)です。前者は毎セッションのトークン・時間を機械が記録し、後者はそれを周期ごとに合算して人が読みます。核心は、このログが「AIが人をどれだけ置き換えたか」ではなく「人の時間をどこへ解放したか」を追跡するという点です。
本書の数値原則は、三つのうちのいずれかです。第一に、公開標準はそのまま引用する(手引きの登録要件、AI基本法の表示義務)。第二に、著者の推定は推定と書く。第三に、測定可能なものだけをKPIとして約束する。著作権・倫理の領域で測定可能なのは、結果指標ではなく手順指標です。
| 測定項目 | 測定方法 | 約束可能か |
|---|---|---|
| AI生成事実の表示漏れ件数 | 資産メタのai_generated_disclosureをgrep |
測定可能(目標0) |
| 生成履歴ログの保有率 | generation_logのある資産の比率 |
測定可能 |
| 法務レビューを経ずにリリースされたAI資産 | リリースビルドvs法務通過リストの照合 | 測定可能(目標0) |
| 「AIのおかげで売上が上がった」 | — | 測定不可、約束しない |
最後の行が正直さの核心です。AI導入の売上効果は単一の変数として分離できないので、因果を断定しません。代わりに「AI生成資産のうち、表示・ログ・法務レビューを通過した比率」は、_economy_logと資産メタで実際に数えられます。ガバナンスが約束するのは結果ではなく、手順の完全性です。
資産の権利・役割・コストを整理すると、一つの領域が残ります。ユーザーがAIで作ったコンテンツをゲームに上げる経路です。会社が作った資産は社内手順で閉じられますが、UGCはコントロールの外から生成物が流れ込んできます。
ここでも§22.4.3のツリーの終点(表示・ログ)がそのまま適用されます。ユーザーがアップロードするコスチューム・ギルドエンブレムにはAI生成表示を求め、自動チェックと人のゲートの組み合わせでモデレーションします。どちらか一方の軸だけを運用すると、次の四半期の事故が積み上がります。そしてユーザーデータは、LLMにむやみに送りません。個人情報・決済情報は送信禁止、行動ログは匿名化後に送信が原則です(GDPR・韓国の個人情報保護法の遵守)。
管轄は韓国一か所では終わりません。海外のユーザーを受け入れた瞬間、そのユーザーが属する地域のデータ規制が一緒にかかってきます。EUのユーザーにはGDPRが個人情報の域外移転・同意・削除権に別途の要件を課し、他のサービス国もそれぞれの個人情報・データローカライゼーション規定を持ちます。だから表の3行目の「個人情報・決済情報のLLM送信禁止」は、どの管轄でも最も安全なデフォルトであり、行動ログを外部モデルに送る際の匿名化・仮名化の強度は、サービスする地域に合わせて別途点検しなければなりません。ただし本節は手順設計の案内であって、法律相談ではありません。グローバルリリース・域外移転が絡む場合は、当該管轄の法務レビューを必ず別途受けてください。
| UGC/データ | ポリシー | 根拠 |
|---|---|---|
| ユーザーがアップロードするコスチューム・エンブレム | AI表示+自動チェック+人のゲート | AI基本法の表示義務 |
| キャラクターのニックネーム・投稿 | 一般約款+ユーザー責任 | — |
| 個人情報・決済情報 | LLM送信禁止 | 個人情報保護法 |
| ゲーム行動ログ | 匿名化後に送信 | 匿名化・仮名化 |
UGCが増えるほど、モデレーションの負担が大きくなります。自動チェックが1次でふるいにかけ、境界事例だけを人が見ます。これが§22.4.4のatom哲学(機械が候補を拾い、人が決める)を、ユーザーコンテンツの次元へ移したものです。
| パターン | なぜ失敗するのか | 処方 |
|---|---|---|
| AI生成物をそのまま最終資産として使用 | 人間の寄与0 → 登録不可+侵害リスク | §22.4.3のツリー、探索・コンセプトのみ |
| 生成履歴ログがない | 事故時にstepごとの責任を再構成できない | generation_logを資産メタで義務化 |
| AI生成事実の未表示 | AI基本法の透明性義務違反 | ツリー終点の表示ステップに例外なし |
| 役割の進化を通告で済ませる | 道具導入の6か月後に拒否反応 | 合意手順(§22.4.5) |
| 「AIで効率が上がった」とだけ叫ぶ | チームメンバーが席への脅威と疑う | _economy_log・_roi_reportで測定を公開 |
| 学習データが曖昧なモデルの無批判な使用 | 侵害リスク評価の欠落 | 明示モデル・社内fine-tune優先(ツリーE分岐) |
五つ目が、最も見落とされやすいところです。冒頭のイラスト判定で見たように、著作権登録を可能にする「人の寄与」は、そのままその人の新しい役割です。効率だけを測定し、その人の時間がどこへ解放されたのかを測定しなければ、ガバナンスはKPI上は成功し、人は去っていきます。
ゲーム外への応用。 「これ、AIで作ったんですが、著作権はうちのものですか」という質問で会議が止まる出来事は、ゲームのイラストだけでなく、AIで作ったレポート・広告コピー・提案書のどこでも起こります。韓国著作権委員会の2025年の手引きが明文化した基準はシンプルです — 登録できるかどうかは「人の創作的寄与(コントロール可能性・予測可能性)」にかかっていて、プロンプトだけを入れた純粋なAI生成物には権利がありません。だからどの部署でも、AI生成物一つに「どの段階が人で、どの段階がAIだったのか」をstepで書いた生成履歴を1枚残す習慣がセーフティネットになります。たとえばマーケターがAIのコピー草案を受け取って自分で修正・再構成したなら、その寄与を記録しておけば権利主張の根拠になり、登録できてもできなくても「AIを使った」という事実の表示(2026年のAI基本法の義務)は例外なく残します。権利の次は人なので、その寄与を行う社員の役割の変化は、通告ではなく合意で閉じなければなりません。
一人ならこれだけ:法務チームがなくても大丈夫です。自分がAIで作った画像かテキストの成果物を1個選び、§22.4.2の形式の
generation_logを手で書いてみましょう(どの段階が人で、どの段階がAIなのかをstepで分けます)。その次に§22.4.3のツリーに沿って「これは登録可能か」を自分で判定してみると、韓国著作権委員会の手引きの「コントロール・予測」基準がどんな判断の束なのかが体で分かります。個人・趣味のプロジェクトでも、AI生成事実の表示1行(ai_generated: true)は残しておくのがおすすめです。
チームなら、次の一歩から始めましょう。すべてのAI資産のメタにgeneration_logとai_generated_disclosureの二つのスロットを義務化し(コード1行のgrepで漏れを捕まえられます)、§22.4.3の意思決定ツリーをwikiの1ページとして貼っておきます。登録要件判定の自動化や_economy_logの運用は、その次です。生成履歴ログとツリー1枚さえあれば、冒頭のように会議が止まる事態は防げます。
第22部は、ガバナンスの四つの軸でした。
| 章 | 核心 |
|---|---|
| 22.1 | プロンプトエンジニアリング — 形式・根拠・脱出口の強制 |
| 22.2 | ハルシネーション・安全性 — 人によるレビューゲート・申告型検証 |
| 22.3 | コスト管理 — キャッシング・cap・_economy_log |
| 22.4 | 著作権・倫理 — 登録要件・表示・役割の合意 |
四つの章を貫く一文はこうです。ガバナンスはAIを止める装置ではなく、人の意図と責任を成果物に残す手順です。 design_intent_vs_automation_boundary atomが、その手順の名前です。著作権登録が要求する「コントロール・予測」、倫理が要求する「役割・合意」、コストが要求する「正直な測定」は、すべて同じ一点を指しています — AIが何をしようと、決定と責任の最後の席は人にあります。
出典 - 韓国著作権委員会・文化体育観光部『生成AI活用著作物の著作権登録の手引き』(2025) — https://www.copyright.or.kr/information-materials/publication/research-report/view.do?brdctsno=54253 - 韓国著作権委員会・文化体育観光部『生成AI著作権の手引き』(2023.12) — https://www.copyright.or.kr/information-materials/publication/research-report/view.do?brdctsno=52591 - 人工知能基本法(AI基本法)生成AI生成物の透明性・表示義務(2026年施行) — https://www.shinkim.com/kor/media/newsletter/3142
ツールを増やすのではなく、ツールのツールを作る。グローバル12個のエントリーポイントの裏に本体48個を隠す2階層構造と、その整合を人手なしで維持する自動化の話です。
月次振り返りを回していたある晩、スラッシュコマンドの一覧を数えていて手が止まりました。40個でした。半年前は確かに7〜8個で始めたはずなのに、議事録ツールを1つ作り、データ検証ツールを1つ足し、GDD(Game Design Document、詳細仕様書)ジェネレーターを1つ追加する、という具合に週に1〜2個ずつ増えていくうちに、いつの間にか40個になっていたのです。しかもそのうち半分近くは、この1か月間に一度も呼び出していませんでした。
問題は、使っていないツールがただ静かにそこにあるわけではない、という点でした。セッションを開始するたびに、40個のスラッシュコマンドの仕様がすべて読み込まれます。トークン予算を侵食し、名前の似たコマンド(skill-design・skill-design-new・skill-design-template)が紛らわしく、肝心の必要なツールを思い出すのに時間がかかりました。ツールが仕事を助けるのではなく、ツールを管理することが仕事になりつつあったのです。
本章では、その40個をグローバル12個に減らしながらも、残りの本体を1つも捨てなかった過程を扱います。核心は3つのパターンです。軽いエントリーポイントを作るWrapper、複数のツールを1つの入口にまとめるCascade、エントリーポイントと本体を物理的につなぐJunction。そして、この3つの整合を人の代わりに守るsync_skills.pyです。
ツールが多いという印象は誰でも持ちます。しかし印象だけでは、何を減らすべきかを決められません。決定を可能にしたのは、月次振り返りでのツール経済性の測定でした。
このプロジェクトでは、振り返りを自己改善のメカニズムとして運用しています。日次振り返りが積み重なって週次に、週次が月次に統合されていく中で、月次振り返りは「この1か月でどのツールを何回使ったか」をSVNコミットログから逆算します。この測定に使うスコアがskill_audit_scoreです。各スラッシュコマンドが実際の作業成果物にどれだけ登場したかをコミット履歴で追跡し、使用頻度を採点します。
その月の測定で明らかになった分布は次のとおりでした(使用量の比率はSVNコミットログに基づく実測で、絶対呼び出し回数ではなくツール別の登場比重です)。
上位12個が全体使用量の92%を占め、月1回も使わないコマンドが18個で全体の45%でした。答えは半分ほど決まったようなものです。よく使う12個だけをグローバルに公開し、残りを整理します。
問題は、「整理」が「削除」ではないという点でした。使っていない28個も、四半期に1〜2回は必要になります。半期報告書を書くとき、新しいデータスキーマを作るとき、特定の検証を回すとき。そのときツールがなければ、作業はその場で止まります。つまり本当の問いはこうでした。どうやって12個だけを見せながら、28個を生かしておくか。
机のたとえが本章全体を貫きます。机の上にペンを40本並べて毎日使う人はいません。よく使う12本だけを机の上に置き、残りは引き出しにしまいます。引き出しの中でも、同じ種類は1つのケースにまとめます。Wrapperは机の上に置く軽いエントリーポイント、Junctionは引き出しと机をつなぐ通路、Cascadeは1つのケースに束ねたペンの束です。
Wrapperはスラッシュコマンドの薄い殻です。グローバルにはエントリーポイントだけを置き、実際のロジックはworkspaceの本体に置きます。グローバルディレクトリには50行の案内文が、本体には500行の実装が住んでいます。
flowchart LR
subgraph G["グローバル ~/.claude/skills/ (机の上)"]
W1["proj-meeting
Wrapper · 50行"]
W2["proj-gdd
Wrapper · 50行"]
end
subgraph B["workspace/skills/ (引き出しの中)"]
M1["proj-meeting/
SKILL.md + 抽出・分類 .py
約500行"]
M2["proj-gdd/
SKILL.md + ジェネレーター
約500行"]
end
W1 -->|呼び出し| M1
W2 -->|呼び出し| M2
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
class W1,W2,M1,M2 code;
この分離が生む利点は5つです。セッション開始時にグローバルには50行しか読み込まれないのでトークンを節約でき、本体は毎日修正してもグローバルスロットに影響がなく、本体はSVNでもGitでもどこにでも置けます。さらに、本体はチーム共有フォルダーに置きWrapperだけを個人のグローバルに置けば共有が容易で、Wrapperの形式を統一すればユーザー体験が一貫します。
Wrapperの標準形式は次のとおりです。すべてのWrapperがこの骨格を共有します。
---
name: proj-meeting
description: 議事録の分析・決定の抽出 (本体: workspace/skills/proj-meeting/)
---
# /proj-meeting — Wrapper
本体の場所: workspace/skills/proj-meeting/SKILL.md
## 動作
このWrapperは本体のエントリースクリプトを呼び出す。詳細なロジックは本体に定義済み。
本体が変更されたら、このWrapperのdescriptionだけを更新すればよい(自動同期を推奨)。
核心は、descriptionの1行と本体へのポインターしかないという点です。ロジックが入った瞬間にWrapperは重くなり、本体との同期が崩れ始めます。そのため、Wrapperは100行以内の維持をルールとして強制します。
このプロジェクトのグローバルスラッシュコマンドのスロットは12個に固定されています。12個の中によく使うツールがすべて収まらなければならず、選定基準は月次振り返りが見ます。月5回以上の使用、分野バランス(1つの分野のツールが6個を超えないこと)、エントリーの一貫性(命名規則の統一)。12個を超えたら、最も使われていない1つを廃棄するか、別のコマンドに統合します。
12という数字が絶対なわけではありません。核心は、数字が決まっているという事実そのものです。小規模(〜10人)のチームなら10個が適切かもしれませんし、分野が多いチームなら15個が合うかもしれません。決まった上限があってこそ、認知負荷が一定の水準にとどまります。
Wrapperが「グローバルには軽いエントリーポイントだけを置く」というルールだとすれば、JunctionはそのルールをOSレベルで実装する手段です。Junctionはディレクトリのシンボリックリンク、つまりOSが提供するエイリアスです。
flowchart LR
U["~/.claude/skills/proj-meeting
(Junction — エイリアス)"]
R["workspace/skills/proj-meeting/
(本体 — 実ファイル1部)"]
U -. "実際の参照先" .-> R
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class R code;
class U data;
ユーザーがグローバルの場所をのぞくと、本体がそこにあるように見えます。しかし実際のファイルは本体の場所に1部しか存在しません。グローバル側は、そこを指し示す標識にすぎないのです。
この構造がもたらす利点は明確です。本体を修正すればグローバルに即時反映されます(コピーの段階がありません)。ファイルが1部しか存在しないのでディスクを節約でき、グローバルにはJunctionしかないのでGitの競合がありません(本体はSVN/Gitで別に管理します)。本体を移動しても、Junctionを張り直すだけでユーザーには何の変化もありません。
OSによって張り方が異なります。Windowsではmklink /J <link> <target>でディレクトリジャンクションを作成でき、管理者権限は不要です。LinuxとmacOSはln -s <target> <link>、WSLはLinuxコマンドをそのまま使います。このプラットフォーム差は後述のsync_skills.pyが自動で処理するため、運用者がOSごとのコマンドを覚える必要はありません。
Junctionを使わずコピーで運用すると、本体とグローバルのコピーが分岐した瞬間に同期事故が起きます。本体でバグを直したのに、グローバルのコピーは旧バージョンのままで旧動作をする、という具合です。Junctionはこの事故の可能性そのものを除去します。標識は2つにはなり得ず、実体は常に1つです。
WrapperとJunctionを手で管理すると、結局40個に戻ります。人は整理を先送りし、ポリシーを忘れ、例外を作ります。そのため、整合の維持を自動化します。そのツールがsync_skills.pyです。
セッションが開始されるたびに、Hookがこのスクリプトをトリガーします。スクリプトがやることは次の流れです。
flowchart TD
H["セッション開始 (Hookトリガー)"] --> S["~/.claude/skills/ をスキャン"]
S --> C{"12 Wrapper
ポリシー一致?"}
C -->|"残余スロット発見"| X["--cleanup:
ポリシー外Wrapperの整理"]
C -->|"本体の移動を検知"| J["Junction自動再生成"]
C -->|"一致"| OK["通過"]
X --> OK
J --> OK
OK --> R["グローバル12スロットの整合を保証"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class H,S,C,X,J code;
class OK,R pass;
主要機能は3つです。第一に、グローバルディレクトリをスキャンし、12 Wrapperポリシーに合っているかを検査します。第二に、--cleanupフラグでポリシーにない残余Wrapperを整理します。誰かが一時的に追加したツールがスロットに残っていても、次のセッション開始時に整理され、スロットが再び急増しません。第三に、本体の場所が変わっていればJunctionを自動で張り直します。OSを検知し、Windowsならmklink /J、それ以外ならln -sを選んで呼び出します。
3つの機能すべてを冪等(idempotent)に設計するという点が重要です。セッションが開始されるたびに自動で回るツールなので、同じ状態で何度回し直しても、結果は1回回したのと同じでなければなりません。すでにポリシーに合っているWrapperには触れず、すでに正しく張られているJunctionは張り直さず、整理すべき残余スロットがなければ何も消しません。冪等でないと、セッションごとに同じ整理が積み重なり、正常なJunctionを再生成したり本体を誤って触ったりする事故が起きます。毎セッション無人で回るツールでは、これはそのまま同期事故に直結します。そのためsync_skills.pyは、「変わったものだけに手を入れ、変わっていなければ手を入れない」を不変条件としています。
--cleanupの効果はトークン予算の保護に直結します。セッションごとにグローバルへ読み込まれるスラッシュコマンドの仕様を12個に抑えておけば、本体が48個に増えてもセッション開始コストは一定に保たれます。人が手で管理しないので、ポリシーが乱れません。
この自動整合が2階層構造の安全装置です。WrapperとJunctionが構造を作り、sync_skills.pyがその構造を時間が経っても維持します。
3つのパターンと自動整合が組み合わさると、次の2階層が完成します。上の階にはユーザーが覚える12個のエントリーポイントが、下の階には48個の本体があります。
flowchart TD
subgraph L1["1層目 — グローバル12 Wrapper (ユーザーが覚えるすべて)"]
direction LR
w1["#1"] -.- w12["#12"]
end
subgraph L2["2層目 — workspace 48本体 (隠れた実体)"]
direction LR
b1["本体 1"] --- bN["本体 48"]
end
L1 -->|"Junctionで接続"| L2
note["sync_skills.py --cleanup:
セッションごとに1層目を12個に整列"]
note -.-> L1
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
class w1,w12,b1,bN,note code;
ユーザーはグローバルの12個だけを記憶します。その裏に本体が48個隠れていても、認知負荷は12個にとどまります。Wrapperがエントリーポイントを軽くし、Junctionがエントリーポイントと本体をつなぎ、sync_skills.pyがその12個の整合をセッションごとに守ります。
比率で見ると、エントリーポイント対本体は1対4です(12対48)。ツールを増やしても、ユーザーが覚えるものは増えません。本体が60個、80個に増えても、1層目は変わらず12個です。これが「ツールを増やすのではなく、ツールのツールを作る」という文の実際の実装です。増えるのは2層目(本体)であり、ユーザーが向き合う1層目(エントリーポイント)は一定です。
2階層構造が「多くのツールを少ないエントリーポイントに減らす」パターンだとすれば、Cascadeは「よく一緒に使うツールを1回の呼び出しにまとめる」パターンです。1つのスラッシュコマンドが複数の下位ツールを順番に呼び出し、結果を1つの総合レポートとして出します。
このプロジェクトの代表的なCascadeはcheckです。毎朝、企画データの整合性を検査していた4つのツールを1つに統合しました。
flowchart TD
E["/check (Wrapper · Cascade入口)"] --> S1["doc-audit
Markdown整合性"]
S1 --> S2["data-qa
マスターデータ検証"]
S2 --> S3["integrity
外部キー整合性"]
S3 --> S4["link-check
Wikilink健全性"]
S4 --> R["総合レポート
(失敗のみ詳細、通過は要約)"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class E,S1,S2,S3,S4 code;
class R data;
以前は毎朝、4つのツールを別々に呼び出していました。ドキュメント検査を1回、データ検査を1回、外部キー検査を1回、リンク検査を1回。作業1サイクルあたり3〜4回の手動呼び出しがかかっていました。checkはこの4つを1つのコマンドにまとめ、1回呼び出せば4つのステップが順番に実行され、結果が1つに統合されます。
Cascadeの設計には原則があります。各ステップは単独でも呼び出せなければなりません(data-qaだけを別に呼べる必要があります)。失敗時に中断するか続行するかはステップごとに設定します。検証作業は1つのステップが失敗しても残りを回し続けて全体像を見ますが、変更作業は1つのステップが失敗したら即座に止めます。結果は累積されて次のステップの入力になり、総合レポートはCascadeごとに同じ形式を使います。
checkの実際の定義は次のとおりです(4種の検証を1つに統合した構成です)。
cascade:
- step: doc-audit
purpose: Markdownの整合性 (YAML frontmatter・リンク・atom参照)
fail_action: continue
- step: data-qa
purpose: Excelマスターデータの検証 (スキーマ・範囲・必須カラム)
fail_action: continue
- step: integrity
purpose: 外部キーの整合性 (シート間の参照)
fail_action: continue
- step: link-check
purpose: Wikilink・外部リンクの健全性
fail_action: continue
report:
format: markdown
include_pass: false # 通過項目は要約のみ、失敗のみ詳細
group_by: severity
fail_action: continueが4つのステップすべてに掛かっているのは、これが検証Cascadeだからです。1つの検査が失敗しても残りの3つを最後まで回し、その日の欠陥一覧をまとめて見ます。レポートは通過項目を要約だけに畳み、失敗だけを展開して、朝に見るべきものへ視線を集めます。
Cascadeにも罠があります。ステップを無限に増やすと複雑度が爆発します。そのため、12スロットポリシーと同じように、Cascadeにもステップの上限を置きます。おおよそ5〜7ステップを超えたら、2つに割るか、一部を別のCascadeに分離します。
2階層構造とCascadeが定着すると、本体を増やすことが容易になります。グローバルスロットに触れず、workspaceに本体を追加するだけで済むからです。ところが、まさにここで新しい罠が生まれます。追加が容易になると、似たようなツールが重複して積み上がるのです。
そこで、本体を増やすときに1つのポリシーを強制します。MECE Wrapperポリシーです。新しいツールを追加しようとするとき、二択で判断します。既存ツールと領域が重なるなら、新規を作らず既存ツールを増強します。領域が明確に異なるときだけ新規に作ります。重複なく(Mutually Exclusive)、漏れなく(Collectively Exhaustive)本体の一覧を維持するという意味です。
flowchart TD
N["新しいツールが必要?"] --> Q{"既存の本体と
領域が重なるか?"}
Q -->|"重なる"| A["新規禁止 →
既存ツールを増強"]
Q -->|"明確に異なる"| B["新規本体の追加を許可"]
A --> M["MECE維持: 重複なし"]
B --> M
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class Q human;
class M pass;
この判断を振り返りが裏付けます。skill_audit_scoreがSVNログからツール別の使用頻度を測定するので、「このツールは実はあのツールとほぼ同じことをしているのに、どちらもほとんど使われていない」といったシグナルが捉えられます。そうしたら2つを1つに統合するか、使われていない方を本体から降ろします。キュレーションは、追加と同じくらい整理が重要です。
MECEポリシーがないと、2階層構造が生んだ「本体追加の自由」がかえって毒になります。本体が1層目のスロットを侵食しなくても、本体そのものが重複で肥大化すると、どの本体を使うべきか再び迷い始めます。ポリシーがその肥大化を防ぎます。
このあたりで最初に戻ってみましょう。WrapperもJunctionもCascadeもMECEポリシーも、どれ1つとして机上で先に設計したものではありません。すべて、振り返りで発見された問題への答えとして生まれました。
月次振り返りのskill_audit_scoreがスロット40個と使用量92%の偏りを定量で明らかにしたとき、「12個に制限しよう」が決まりました。その決定の後に「では28個をどう生かすか」という問いが続き、その答えがWrapperとJunctionでした。次の振り返りで「似たような検証ツール4つを毎朝別々に呼ぶのが面倒だ」という発見が出て、その答えがcheck Cascadeでした。また別の振り返りで「本体の追加が容易になったので重複が積み上がる」というシグナルが捉えられ、その答えがMECEポリシーでした。
振り返りがなければ、これらのパターンは作られなかったでしょう。作ったとしても、実際の問題と無関係なオーバーエンジニアリングになっていたはずです。問題を測定で発見してからパターンを導入する順序 — その順序が、ツールを実際に使われるものにします。振り返りが自己改善の出発点だという第21部のメッセージが、ツールの次元でこのように具体化されます。
6か月の測定値を導入前後で比較します。絶対呼び出し回数ではなく、運用負担の変化に注目します。
| 項目 | 導入前 | 導入後(Wrapper+Cascade+Junction) |
|---|---|---|
| グローバルスロット数 | 40個(急増) | 12個(ポリシー強制) |
| 本体数 | 散在・重複多数 | 48個(MECE整理) |
| セッション開始時のグローバルスロット比重 | 大(40個の仕様を読み込み) | 小(12個の仕様のみ読み込み) |
| 本体修正後のグローバル反映 | 手動コピーの段階が必要 | 即時(Junction、コピーなし) |
| 類似検証ツールの呼び出し | 作業あたり3〜4回手動 | 1回(check Cascade) |
導入初月の測定値はばらついていました。Wrapper形式が定着するまでに同期事故が2〜3回起き、12個ポリシーが強制されるまでスロットは15〜18個を行き来しました。安定したのは2か月目からです。sync_skills.py --cleanupがセッションごとにスロットを整列し始めてからは、スロットの急増は二度と起きていません。
表の「比重」「必要」「即時」のような方向の表現は意図的です。環境ごとにトークンコストと時間が異なるため、変化の方向だけを記しました。確かなのは、1層目が40から12に固定され、本体の反映から手動コピーの段階が消えたという事実です。
前の節で指摘した罠を1か所に集めます。5つすべてが、構造を作ることよりも、時間が経っても維持することの方が難しいという同じ教訓を指しています。
sync_skills.py --cleanupがセッションごとに整列(§23.1.4)。setup. workspaceに本体ディレクトリを1つ作りましょう(例: workspace/skills/proj-meeting/)。その中にSKILL.mdと実際のスクリプトを置きます。グローバルの~/.claude/skills/には50行のWrapperだけを置きます。
prompt. 次をClaudeに依頼します。
~/.claude/skills/ のスラッシュコマンド一覧をスキャンして。
各コマンドが (a) 本体へのポインターだけを持つ軽いWrapperか
(b) ロジックが入った重いコマンドかを分類し、
(b) に該当するものは本体をworkspaceに分離して
グローバルには50行のWrapperだけを残す変更案を提示して。
さらに、本体を指すJunctionをOSに合わせて張るコマンド
(Windowsなら mklink /J、それ以外なら ln -s) を出力して。
verify. 3つを確認しましょう。第一に、グローバルディレクトリの各項目が100行以内か。第二に、グローバルの項目を開くと、本体の場所1行とdescriptionだけが見えるか。第三に、本体を1行修正してグローバルから呼び出したとき、修正が即時反映されるか(Junctionが正しく張られていれば、コピーなしで反映されます)。
チームもSVNもない一人運用なら、こう縮小しましょう。workspaceを置く必要はなく、個人のGitリポジトリが1つあれば十分です。本体はそのリポジトリに置き、グローバルにはWrapperだけを置きます。skill_audit_scoreのような測定ツールがなくても、月末に「今月実際に呼び出したコマンド」を自分の手で書き出してみるだけで偏りが見えてきます。上位5〜7個だけをグローバルに残し、残りは本体に降ろしましょう。Cascadeは、よく一緒に呼ぶツールが2つ以上できたときに1つのコマンドにまとめれば十分です。自動整合スクリプトが負担なら、新しいセッションを始めるときにグローバルディレクトリを一度目で見渡す習慣で代替できます。規模が小さくなれば、自動化の代わりに習慣が同じ仕事をします。
夜11時47分。マスターデータを最後に保存して、ノートパソコンを閉じました。翌朝9時10分、コーヒーを淹れている間に社内メッセンジャーを開くと、チャンネルの上部にレポートが1枚上がっていました。夜の間に更新された3枚のバランス数値シートを外部キーを基準にクロスチェックし、壊れた参照2件を赤色で表示したMarkdownです。私が書いたものではありません。眠っている間に作られていたのです。
本章は、そのレポートを作ったツール — Hermes Agentを個人PCに載せ、§23.1で扱ったWrapper・Cascade・Junctionの運用の上に重ねた過程の記録です。導入した当初、HermesはLinuxベースだったため、Windowsで使うにはWSL2を経由する必要がありましたが、2026年にネイティブWindowsビルドが出たことでその迂回はなくなりました。結論を先に言うと、エージェントはClaude Codeを押しのけませんでした。隣の席に座ったのです。
§23.1までの運用は、すべてClaude Code中心でした。私が一文を入力するとツールが一度応答し、私はその応答をレビューしてから次の一文を入力します。この短いサイクルは、精密な作業にはこの上なく向いています。バランス数値を1つ直すときのように毎ステップの確認が必要な作業なら、人が毎回挟まるのが正解です。
問題は、長さのある作業でした。「直近1か月分の議事録30件を全部読んで、決定事項だけをatom候補として抽出してほしい」という依頼は、対話の流れの中で処理すると30回の往復が必要です。その30回の間、私は他の仕事ができません。こうした作業では、入力と出力が短くつながるツールの強みが、むしろ弱みになります。
エージェントは、その反対側の席を埋めます。目標だけを投げれば — 「議事録30件から決定事項をatom候補として抜き出してレポートに」 — ツールを自分で選んで使い、中間ステップを自分で踏み、終わったら結果だけを持ってきます。サイクルが長く、自律的です。その代わり、毎ステップを人が見られないという弱みがついてきます。
隣の席の同僚にたとえると分かりやすいです。Claude Codeは私の一文ごとに一緒にのぞき込む相棒で、エージェントは夜勤を買って出て、私が出勤する前に机の上へ報告書を置いておいてくれる補助人員です。一方が他方を解雇する関係ではありません。2人が同じ机を分け合って使うのです。
§23.1では、グローバルなスラッシュコマンドのスロットを12個に絞り、その後ろに本体48個をJunctionで隠す運用を作りました。ツールを増やさず、ツールのツールを作ったのがその結論でした。ところが、ここでまた新しいツールを入れるというのは、その結論と矛盾するように聞こえます。
矛盾ではありません。§23.1の12スロット方針は、「人が直接呼び出すツール」の認知負荷を扱ったものです。Hermesが埋めようとしている席は、人が呼び出さない時間 — 眠っている時間、会議中の時間、他の仕事に手が縛られている時間です。12スロットと競合するのではなく、12スロットが届かない時間帯を埋めます。
導入を決めた根拠は、ある振り返りの測定値でした。グローバルツールの使用頻度をSVNコミットログから逆算するskill_audit_scoreで1か月分を回してみると、上位ツールのほとんどが「人が起きている時間に、短く、頻繁に」使う種類でした。一方、使用頻度は低いものの一度回すと長くかかる作業 — 議事録の一括分類、マスターデータの夜間整合性、ビルドキャプチャ分析 — は、毎回「明日の朝にやろう」と先送りされていました。先送りされる理由は明確です。起きている時間を長く食いつぶすからです。
この先送りされる作業群こそが、エージェントの正確なターゲットです。
最初にHermesを載せたときはLinuxベースだったため、Windowsの個人PCで使うにはWSL2(Windows Subsystem for Linux 2)を先に入れて、その中にHermesを据える必要がありました。今はネイティブWindowsビルドがあるので、その迂回は不要です。インストールは一般的なWindowsアプリケーションと同じです — インストーラーをダウンロードして実行し、初回起動でワークスペースのパスと権限ホワイトリストの初期値を設定すればいいのです。
すでにWSL2を使っている場合や、Linux環境を好む場合は、そちらのビルドもそのままサポートされています。ただし、新しく始めるならネイティブの方がシンプルです。正確なインストーラーやバージョンはツールの変化が速いので、公式ドキュメントに従います。
インストール先がどこであれ、残る落とし穴が1つあります。Hermesのワークスペースは高速なローカルディスクに置かなければなりません。ネットワークドライブやSVN作業フォルダーをワークスペースとして直接つなぐと、夜間整合性チェック1回が数分で済むはずの作業を数十分に引き延ばしてしまいます。マスターデータはワークスペースの外に置き、作業開始時点でのみコピーして取り込むのが定石です。WSL2を使うなら、同じ理由でワークスペースをLinuxファイルシステムの中に置き、/mnt/cのようなWindowsパスを行き来しないようにします。
ここからは、実際に手を動かす部分です。インストール自体より「インストール後に何をやらせるか」が本章の核心なので、最初の作業を1つ、最後まで — プロンプト全文、生の出力、人による検証、再依頼まで — たどってみます。この作業には、§23.2.2で先送りされていた作業群の中で最も単純なもの、夜間マスターデータ整合性チェックを選びました。
注意: 以下のコマンドの一部は、Hermesの表面的な形を見せるための例示です。インストーラーのURL・サブコマンドはバージョンごとに変わるので、公式ドキュメントを確認してください。ワークフローの構造(目標 → 自律実行 → 検証 → 再依頼)は、ツールが変わっても維持されます。
ネイティブWindowsなら、PowerShellで公式のinstall.ps1をダウンロードして回します。ただし、1行のiex (irm ...)ワンライナーをそのまま実行する前に、スクリプト(約2,800行)を一度ダウンロードして危険なパターンを目で確認し — それが出所を信頼するための最低限の手続きです — キー設定は分離して、-SkipSetupで本体だけ先に入れたあとhermes setupを別に回す方が安全です。WSL2・Linuxを使うなら、公式ドキュメントの該当インストール節に従います。
# ネイティブWindows — 公式install.ps1(まずダウンロードして確認してから実行)
irm https://hermes-agent.nousresearch.com/install.ps1 -OutFile install.ps1
# (install.ps1の内容を確認したあと)
.\install.ps1 -SkipSetup
# Python 3.11・Node・Git・Playwright・バンドルスキルを一緒に確保
# インストール先: %LOCALAPPDATA%\hermes\ (hermesコマンドをPATHに登録 — 新しいターミナルから認識)
# 終わったら: hermes setup
インストーラーは依存関係(Python 3.11・Node 22・Git)を一緒に入れ、本体を%LOCALAPPDATA%\hermes\にインストールしたうえでhermesコマンドをPATHに登録します(新しいターミナルから認識)。設定・ログ・予約(cron)・チェックポイントといった運用データも同じ%LOCALAPPDATA%\hermes\の下に残り、再インストールしても保存されます(ここが落とし穴です — ~/.hermes\には補助スクリプトしか入っておらず、紛らわしいのです。実際のconfig.yaml・logs\はすべて%LOCALAPPDATA%\hermes\側にあります)。初回実行としてhermes setupを回すと、モデルAPIキーを聞かれ、ワークスペースのパスと権限ホワイトリストの初期値を設定します。
hermes --version
hermes setup
いよいよ最初の作業を任せます。エージェントに投げる目標は、Claude Codeのプロンプトより一段抽象的です。「これをこうやって」ではなく「この結果を作っておいて」に近いのです。私が実際に入れた目標の全文は、次のとおりでした。
[プロンプト全文]
目標: 夜間マスターデータ整合性チェック。
~/hermes-workspace/sheets/ の item_master・drop_table・npc_shop の3つのxlsxを読み、
drop_table・npc_shop が参照する item_id が item_master に実際に存在するか、外部キーを
検査してほしい。壊れた参照(存在しない item_id を指す行)をすべて見つけて、reports/ に
日付ファイル名のMarkdownで保存すること。(意図: 通過は件数のみ、失敗はシート名・行番号・item_id の表で)
制約:
- sheets/ は読み取りのみ。絶対に修正しないこと。
- シートが読めない、またはカラム名が予想と違う場合は、止まって何が違ったかを報告すること。推測禁止。
最後の2行が重要です。エージェントは自律的なので、詰まると「気を利かせて」埋めようとする傾向があります。カラム名が違えば、似た名前を選んで進めてしまうという具合です。だから「詰まったら止まって報告すること、推測しないこと」を明示的に入力しておきます。これは、§23.1でマスターデータ検証のCascadeを組んだときに学んだ教訓と同じ筋のものです。
[Hermesの生の出力 — 1回目]
要約せず、そのまま転載します。最初の試行は、すんなりとは終わりませんでした。
[hermes] goal accepted: 夜間マスターデータ整合性チェック
[hermes] step 1/4 reading sheets...
item_master.xlsx loaded (4,210 rows)
drop_table.xlsx loaded (1,880 rows)
npc_shop.xlsx ERROR
[hermes] npc_shop.xlsx: expected column 'item_id', found columns:
['shop_id', 'sell_item', 'price', 'stock']
[hermes] this conflicts with the constraint: "カラム名が予想と違う場合は止まって報告".
[hermes] HALTING. reporting instead of guessing.
[hermes] partial result:
- item_master ↔ drop_table の検査は可能 (item_id カラムが両方に存在)
- npc_shop は item_id にあたるカラム名が 'sell_item' と推定されるが、確定できず未実行
[hermes] awaiting clarification on npc_shop column mapping.
ここが、エージェントを初めて使った人が最も驚くポイントです。ツールが止まりました。そして、止まった理由を正確に述べました。npc_shop.xlsxのアイテム参照カラムは、私の予想だったitem_idではなくsell_itemでした。制約を入力しておかなければ、エージェントはsell_itemを勝手にitem_idとみなして進めていたでしょうし、私は朝に「正常」というレポートだけを受け取っていたはずです。停止こそが安全なのです。
[人による検証・拒否]
私は出力を見て、2つのことを判断しました。
第一に、item_master ↔ drop_tableの検査はすでに可能だと報告した部分は信頼できます。第二に、npc_shopのsell_itemという推定は正しい — 実際にそのシートの販売アイテムのカラム名です。ただし、「推定」のまま進めなかった判断は正しかったのです。そこで1回目の出力をそのまま受け入れず(拒否)、カラムマッピングを明示した再依頼を入れました。
[再依頼プロンプト]
確認した。カラムマッピングを明示する:
- npc_shop.xlsx のアイテム参照カラムは 'sell_item' である。これを item_id とみなして検査すること。
- 残りの制約は同一。'sell_item' 以外のカラムを追加で推測しないこと。
再実行すること。
[Hermesの生の出力 — 2回目]
[hermes] resuming with column map: npc_shop.sell_item -> item_id
[hermes] step 2/4 foreign-key check...
drop_table → item_master : 1,880 refs, 2 broken
npc_shop → item_master : 642 refs, 0 broken
[hermes] step 3/4 collecting broken refs...
[hermes] step 4/4 writing report...
~/hermes-workspace/reports/2026-05-25_sheet_integrity.md written
[hermes] done. 2 broken references found. summary:
| sheet | row | bad item_id |
|------------|------|-------------|
| drop_table | 417 | ITM_90412 |
| drop_table | 1356 | ITM_90727 |
壊れた参照が2件捕まりました。drop_tableの417行目と1356行目が、item_masterに存在しないitem_idを指していました。朝に見た赤い表が、まさにこれでした。
この1回の往復に、本章のメッセージのほとんどが表れています。エージェントは自律的でありながら制約の前で止まり、止まった箇所を人が埋めると最後まで進みました。自律と統制は衝突するのではなく、かみ合うのです。そしてこのサイクル全体を、私が眠っている時間にもう一度回るよう予約しておけば、それがそのまま§23.2.5の夜間自動化になります。
最初の作業が手になじんだら、先送りされていた作業群を1つずつ夜間へ移します。私が実際に載せたのは3つのポジションです。3つの共通点は明確です — どれも、人が起きている必要のない時間を、働く時間に変えます。
flowchart TD
A["夜間トリガー
(毎日23:00、cron)"] --> B{Hermes Agent}
B --> C1["[ポジション1] マスターデータ
夜間整合性チェック"]
B --> C2["[ポジション2] 長期シミュレーション
100時間分の仮想プレイ"]
B --> C3["[ポジション3] ビルドキャプチャ
自動分析パイプライン"]
C1 --> D1["外部キーdiff
壊れた参照の表"]
C2 --> D2["ボス討伐平均・リソース消費
コンボ分布"]
C3 --> D3["仕様 vs 実測diff
フレームごとの抽出"]
D1 --> R["[総合] Markdownレポート
~/hermes-workspace/reports/"]
D2 --> R
D3 --> R
R --> S["朝09:00
社内メッセンジャーのチャンネルへ自動配信"]
S --> H["プランナー: 結果のみレビュー
(分析は眠っている間に完了)"]
style A fill:#fff3e0,stroke:#e65100
style B fill:#e3f2fd,stroke:#1565c0
style R fill:#e8f5e9,stroke:#2e7d32
style H fill:#fce4ec,stroke:#c2185b
ポジション1 — マスターデータの夜間整合性。 2.4で最後までたどったあの作業を、毎晩23時に予約します。夜の間に誰がどのシートを触っていても、朝には外部キーが壊れた箇所が表になって上がっています。これは§23.1の/check Cascade(doc-audit → data-qa → integrity → link-check の4種統合)がやっていたことと表面上は似ていますが、決定的な違いが1つあります。/checkは、私が起きて呼び出さなければ回りません。夜間エージェントは、私がいなくても回ります。2つは競合しません — 昼のCascadeは即時検証、夜のエージェントは無人検証と役割が分かれます。
ポジション2 — 長期シミュレーション。 §4.4で扱った戦闘シミュレーションを、時間軸方向に深く伸ばします。100時間分の仮想プレイを回して、ボス討伐の平均時間、リソース消費曲線、コンボ分布を測定する作業です。これは本質的に、Claude Codeの対話の流れに合いません — 一度回すと数時間かかるのに、その間ずっと対話ウィンドウを占有しているわけにはいきません。エージェントがバックグラウンドで回し、終わったら曲線グラフと要約数値だけを持ってきます。
ポジション3 — ビルドキャプチャの自動分析。 QAがキャプチャしたビルド映像がフォルダーに落ちてくると、エージェントがフレームごとにデータを抽出し、仕様数値と実測数値のdiffを作ります。プランナーは映像を最初から最後まで再生して見る必要はなく、「仕様はダメージ120なのに、ビルドでは108と測定」のようなdiff行だけを見ます。分析の退屈な部分の全体が、エージェントの担当です。
3つのポジションのいずれも、結果を見る人の時間は減りません。減るのは、分析に費やす人の時間です。判断は依然として人がします。
エージェントの自律性は、そのままリスクでもあります。人の毎ステップ確認なしにファイルを読み、コマンドを回すツールである以上、何かが暴走したとき、人はその場にいません。§23.2.4で「推測禁止」を明示しておいたのは、偶然ではありません。5つの安全装置は選択肢ではなく、導入初日に一緒に有効化すべきセットです。
| 装置 | やること(実際のHermes設定キー) | 欠けると起きること |
|---|---|---|
| 権限ホワイトリスト | 破壊的なコマンドは人の承認を経るようにし(approvals.mode: manual)、許可するコマンドのみホワイトリスト化(command_allowlist)、秘密値はログでマスク(security.redact_secrets) |
元のマスターデータを自律的に修正してしまう |
| チェックポイント | ファイル操作前にスナップショットを取り、巻き戻せるように(checkpoints.enabled、復元は/rollback) |
誤った仮定が最後まで転がり、結果全体が汚染される |
| ログ自動記録 | ゲートウェイ・エージェント・エラーのログを%LOCALAPPDATA%\hermes\logs\に残す |
事故後に「なぜこうなったのか」を追跡できない |
| コスト上限 | 1作業のターン上限(agent.max_turns)・ターミナルタイムアウト(terminal.timeout)・無限ループ自動検知(tool_loop_guardrails)・コンテキスト自動圧縮(compression) |
無限ループに陥った作業がAPIの請求書を膨らませる |
| 廃棄可能 | いつでも中断(/stop)・予約の一時停止/削除(cron pause)・サブ作業のタイムアウト(delegation.child_timeout_seconds)・使わないスキルの自動アーカイブ(curator) |
誤って回り始めた夜間作業を止められない |
この5つは、ばらばらに動く装置ではなく1セットとして機能します。権限だけ締めてコスト上限をかけなければ、権限の範囲内で無限ループが回って請求書が膨らみます。ログだけ有効にして廃棄の手段がなければ、事故が起きているのを見ながらも止められません。どれか1つ欠けるだけで、夜間無人運用の事故確率は跳ね上がります。
実際にツールを動かしてみると、この5つの概念を、本書で描いたより一段きめ細かく実装してある箇所がいくつかありました。権限側には別のポリシーエンジン(security.tirith_enabled)がもう一枚あり、コマンドをルールでふるいにかけます。コスト側の無限ループ検知は単一の上限ではなく、「同じ失敗の繰り返し」「進展のない繰り返し」といったシグナルを別々のしきい値として捉えます。そして夜間無人予約(cron)には別のスイッチ(approvals.cron_mode: deny)があり、人がいない時間帯に破壊的コマンドが検出されると、承認を待たずに即座に拒否します — 本書の「権限 + チェックポイント」を1つの設定にまとめた格好です。廃棄側のcuratorは、§21の「使わないツールは廃棄する」が実際の機能として搭載されている箇所です。5点セットの骨格はそのまま維持しつつ、ツールの方がより精巧な箇所は、そのキーを有効にしておけばいいのです。
config.yamlにこのセットを入力しておく様子は、おおよそ次のとおりです。
# %LOCALAPPDATA%\hermes\config.yaml (抜粋)
approvals:
mode: manual # ① 権限 — 破壊的なコマンドは人の承認を経る
command_allowlist: # 承認なしで許可するコマンドのみ明示
- "python *"
- "rg *"
cron_mode: deny # 夜間無人cronが破壊的コマンドに遭遇したら自動拒否
security:
redact_secrets: true # ログで秘密値をマスク
tirith_enabled: true # ポリシーエンジン(ルールベースのコマンドフィルター)をもう一枚
checkpoints:
enabled: true # ② チェックポイント — ファイル操作前にスナップショット(/rollbackで復元)
max_snapshots: 20
retention: 7d
logs:
path: "%LOCALAPPDATA%\\hermes\\logs" # ③ ログ — gateway/agent/errors
agent:
max_turns: 60 # ④ コスト — 1作業のターン上限
terminal:
timeout: 180 # ターミナルコマンドのタイムアウト(秒)
tool_loop_guardrails: # 無限ループ自動検知(同じ失敗・進展なし)
enabled: true
compression:
enabled: true # コンテキスト自動圧縮(トークン節約)
delegation:
child_timeout_seconds: 600 # ⑤ 廃棄 — サブ作業のタイムアウト(/stop・cron pauseと併用)
curator:
enabled: true # 使わないスキルの自動アーカイブ
委任も、一度に全部は渡しません。最初は最も狭くて巻き戻しやすい作業(整合性チェックのような読み取りだけの仕事)だけを任せ、結果を数日見守ってから次のポジションへ広げます。§23.2.4で最初の作業に夜間整合性チェックを選んだのも同じ理由です — 読み取りだけなので、最悪でも誤ったレポート1枚で済み、元データは傷つきません。
本章を更新している時点で、導入は定着期に入りました。ネイティブWindowsビルド(v0.16.0)のインストールを終え、hermes setupでモデルAPIキーの登録まで済ませました。最初のポジションを稼働させて安全装置5種を実際の設定キーで1つずつ点検し、今は実際の自律作業を回しながら手になじませている最中です。正直に書くと、会社PCではなく個人PCで先に検証している段階で — 会社への導入は、個人PCで安全装置が十分なじんだあとに先送りしてあります。これは慎重さというよりPC分離原則に近いものです。検証されていない自律ツールを、チームのデータにいきなり放ちません。
| 期間 | 活動 | ゲート |
|---|---|---|
| 1か月 | Hermesインストール(ネイティブWindows v0.16.0) + hermes setup + 最初の作業 |
安全装置5種がすべて有効になっているか |
| 2〜3か月 | ポジション2〜3個へ拡張(議事録分類・ビルドキャプチャ分析) | 委任範囲ごとにログを点検 |
| 3〜6か月 | 会社での検討 — 個人PCの検証結果で意思決定 | 無人運用の事故0件を確認 |
| 6〜12か月 | チーム単位の導入 | 安全装置がチーム規約として定着 |
ステップを飛ばす誘惑が、最も危険です。1か月から一気に6か月(チーム導入)へジャンプすると、安全装置が個人1人の習慣にすぎないまま、チーム規約として身につかない状態で解き放たれます。各ステップの終わりに一度ずつ立ち止まり、5つの装置を点検するのが答えです。速く進むことより、巻き戻せる状態のまま進むことが重要です。
「エージェントが人を置き換える」が、最もよくある誤解です。§23.2.4のワークド・トランスクリプトが、その逆を示しています — エージェントはカラムマッピング1つで止まり、その判断を人が埋めました。ゲーム企画の核心となる決定は依然として人の仕事で、エージェントが持っていくのは反復と分析の退屈な部分です。
「一度インストールすれば全部自動」という期待も危険です。最初の1〜2か月は、むしろ手がかかります。カラム名のマッピング、権限の範囲、コスト上限を作業ごとに調整しなければならず、その調整がなじむまでは、毎回の出力を人がレビューします。
「Claude Codeはもう時代遅れ」という断定は間違いです。2つは時間帯が違います。昼の精密な決定はClaude Code、夜の無人反復はエージェント。§23.1の/check Cascadeが消えたのではなく、その隣に夜間レーンが1本増えたのです。
「オープンソースだから無料」という認識は、半分だけ正しいです。本体は無料でも、モデルAPI呼び出しのコストはそのままかかります。だからこそ、config.yamlのagent.max_turns・compressionのようなコスト上限が、安全装置であり家計簿でもあるのです。
最後に、「複雑で危険な作業までエージェントがやってくれる」という期待が最も危険です。リスクの大きい作業ほど、人の統制の下に置きます。エージェントに渡すのは、単純で巻き戻しやすい作業からです。委任は、信頼が積み上がった分だけ広げます。
§23.1のWrapper・Cascade・JunctionがClaude Code運用の頂点だとすれば、本章のHermesは、その運用の上に夜間レーンをもう1本敷いたものです。昼のツールと夜のツールが同じ机を分け合って使う図 — これが2026年時点の現在であり、近い将来の骨格です。
次章は、ゲームプランナーのためのツールキュレーションです。12スロットの中に何を入れるか、skill_audit_scoreで何を間引くか — 本章で少し触れたキュレーションの基準を、具体的なツールの推薦として展開します。
setup
1. HermesのネイティブWindowsインストーラーをダウンロードしてインストールしましょう(Linuxを好むなら、wsl --installのあとその中にインストールする道もそのまま残っています)。
2. 高速なローカルディスクに作業フォルダーを作り、検査するマスターデータをそちらへコピーしましょう(ネットワークドライブ・SVN作業フォルダーをワークスペースとして直接つなぐのは禁止)。
3. hermes setup → モデルAPIキーを入力 → ワークスペースのパス・権限の初期値を確認。
4. %LOCALAPPDATA%\hermes\config.yamlで安全装置5種を有効にしましょう: 権限承認(approvals.mode: manual・command_allowlist・cron_mode: deny)、コスト上限(agent.max_turns・terminal.timeout・tool_loop_guardrails)、チェックポイント(checkpoints.enabled)、ログパス(logs.path)、そして中断手順(/stop・/rollback)の習得。
prompt - 目標は一段抽象的に投げましょう: 「これをやって」ではなく「この結果を作っておいて」。 - 対象・やること・保存場所を番号で明示し、最後に必ず1行を入力しましょう: 「詰まったり、カラム/形式が予想と違ったりしたら止まって報告すること。推測禁止。」 - 最初の作業は、読み取りだけの整合性チェックのように巻き戻しやすいものを選びましょう。
verify - 1回目の出力をそのまま信じず、エージェントが止まった箇所(カラムマッピング・形式の不一致)を人が確認しましょう。 - 停止が正しかったらマッピングを明示して再依頼し、間違っていたら制約を入力し直しましょう。 - 生成されたレポートの失敗項目を1〜2件、元のシートと直接突き合わせてエージェントの判断が正しいか検証してから、夜間予約(cron 23:00)へ移しましょう。
Hermesをインストールせずにエージェントの感覚だけ先につかみたいなら、Claude Codeの中でバックグラウンド実行によるミニ版を回してみることができます。
四半期の振り返りの最中に、グローバルスキルフォルダを開きました。1行ずつ数えてみると、wrapperが19個ありました。確かに12個で運用すると決めて1年回してきたのに、いつの間にか7個増えていたのです。さらにあきれたのは、そのうち半分は、名前を見ただけでは何をするツールなのか思い出せなかったことです。migrate-legacy-enum。これは何だったか。最後に使ったのはいつだったか。
思い出せませんでした。記憶に頼っている限り、この問いには永遠に答えられません。そこで、記憶の代わりにログを見ることにしました。ツールキュレーションは好みで間引く作業ではなく、「このツールを前の四半期に何回呼び出したか」という数字で間引く作業であるべきです。
本章は、その数字をどう自動で抽出し、その数字でどうツールを切り捨て、そしてそもそもツールが急増しないようにどう防ぐかについての記録です。
キュレーションの話をする前に、1つ認めておくべきことがあります。ツールは、止めなければ必ず増えます。意志が弱いからではありません。作業のたびに「今回だけ手早く済ませるために」と小さなスクリプトを1つ作るのが、合理的な選択だからです。その合理的な選択が数十回積み重なると、非合理的な山になります。
プロジェクトAで運用している構造は、グローバルの12個のwrapperがjunctionでworkspaceの48個の本体を指す形です。グローバル側は軽く、重い本体はSVNで管理するworkspaceに置きます。この構造自体は§23.1で扱いました。問題は、この12という数字がじっとしていてくれないことです。
ツールが増えるとき、何が一緒に増えるのかを見れば、なぜ防ぐべきかがはっきりします。
特に1つ目のコンテキストトークン占有は、AIツールを使う時代になって、いっそう鋭くなったコストです。グローバルのwrapperが増えると、セッションのたびにAIが「自分が使えるツール一覧」を読むトークンが増えます。19個のツールの説明を読むせいで、肝心の作業に使えるコンテキストが減るのです。そのためプロジェクトAのsync_skills.pyには--cleanupオプションがあり、junctionが壊れたり本体が消えたりしたwrapperを自動で整理します。これはトークン予算を守るための衛生作業に近いものです。
ただし--cleanupが捕まえてくれるのは「壊れた」ツールだけです。問題なく生きているのに誰も使っていないツールは捕まえられません。それを捕まえるには、使用頻度のデータが必要です。
核心のアイデアは単純です。workspaceのスキル・ツールはすべてSVNに入っています。そしてツールを使うたびに、そのツールが作り出した成果物(シート、ドキュメント、関係図HTMLなど)がSVNにコミットされます。つまりSVNログを見れば、どのツールが実際に働いたのかが痕跡として残っているのです。
そこでskill_audit_scoreという小さな測定スクリプトを作りました。名前のとおり、各スキルに「監査スコア」を付けます。このツールを作るとき、最初からコードを全部自分で書いたのではなく、AIに測定ロジックを説明して初稿を受け取りました。そのワークド・トランスクリプトをそのまま残します。要約せず、見当外れだった部分まで含めて書きます。
グローバルスキルのwrapperは12個と決めているのに、いつの間にか19個に膨らんだ。四半期の振り返りで「使っていないツール」をデータで選び出したい。次の前提で測定スクリプトを作ってほしい。
- 各スキルツールはworkspaceフォルダ内のサブフォルダとして存在する(例:
tools/relation-map-gen/)。- これらのツールが作った成果物はSVNにコミットされる。コミットメッセージには普通、ツール名や成果物の種類が入る(例: "relation-map regen"、"schema-doc update")。
- SVNログをパースして、直近90日間に各ツールが関与したコミット数を数えたい。
- 単純なコミット数だけでは足りない。(a)直近ほど重みを高く、(b)最終使用日が古ければ減点、の2つを合わせて0〜100のaudit_scoreを出してほしい。
- 出力はスコア降順の表。スコアが低いツールが「キュレーション候補」。
- SVNコマンドは
svn log --xml -l 2000の形で受け取る想定。Pythonの標準ライブラリだけを使うこと。絶対パスは引数で受け取るように。
承知しました。ただ、1つ指摘しておきます。コミットメッセージにツール名が常に入っているという保証はありません。人がコミットメッセージを自由に書くと、「シート修正」のようなメッセージからは、どのツールが関与したのか分かりません。そこで2種類のマッチングを併用します。
- コミットメッセージでのツール名・別名キーワードマッチング(緩い)
- 変更されたファイルパスが当該ツールのフォルダ、またはそのツールの成果物パターン(例:
*_relation.html)に属するかのマッチング(厳格)2のほうが信頼できるので、重みを大きくします。以下が初稿です。
# skill_audit_score.py (Claude初稿 — 検証前)
import subprocess, sys, datetime, xml.etree.ElementTree as ET
from collections import defaultdict
def svn_log_xml(repo_path, limit=2000):
out = subprocess.run(
["svn", "log", "--xml", "-v", "-l", str(limit), repo_path],
capture_output=True, text=True, check=True)
return ET.fromstring(out.stdout)
def score_skills(repo_path, skills, window_days=90):
root = svn_log_xml(repo_path)
now = datetime.datetime.now(datetime.timezone.utc)
hits = defaultdict(list) # skill -> [days_ago, ...]
for entry in root.findall("logentry"):
date = datetime.datetime.fromisoformat(
entry.find("date").text.replace("Z", "+00:00"))
days_ago = (now - date).days
if days_ago > window_days:
continue
msg = (entry.find("msg").text or "").lower()
paths = [p.text for p in entry.findall("paths/path")]
for sk in skills:
name = sk["name"].lower()
in_msg = name in msg or any(a in msg for a in sk.get("alias", []))
in_path = any(name in (pp or "").lower() for pp in paths) \
or any(pat in (pp or "") for pp in paths
for pat in sk.get("artifact", []))
if in_path:
hits[sk["name"]].append((days_ago, 2.0)) # 厳格マッチング加重
elif in_msg:
hits[sk["name"]].append((days_ago, 1.0)) # 緩いマッチング
rows = []
for sk in skills:
h = hits[sk["name"]]
if not h:
rows.append((sk["name"], 0, None, len(h)))
continue
# 直近加重: 直近ほど1.0、90日前なら0.1
recency = sum(w * (1 - 0.9 * (d / window_days)) for d, w in h)
last_used = min(d for d, _ in h)
# 最終使用日の減点
stale_penalty = max(0, (last_used - 14)) * 0.5
score = max(0, min(100, recency * 8 - stale_penalty))
rows.append((sk["name"], round(score, 1), last_used, len(h)))
rows.sort(key=lambda r: r[1]) # スコア昇順 = 候補が先頭
return rows
if __name__ == "__main__":
REPO = sys.argv[1]
SKILLS = [
{"name": "relation-map-gen", "alias": ["relation", "관계도"],
"artifact": ["_relation.html"]},
{"name": "schema-doc", "alias": ["schema", "명세서"],
"artifact": ["_schema.md"]},
{"name": "gdd-gen", "alias": ["gdd"], "artifact": ["_gdd.md"]},
{"name": "table-creator", "alias": ["table", "테이블"], "artifact": []},
{"name": "excel-reader", "alias": ["xlsm", "vba"], "artifact": []},
{"name": "migrate-legacy-enum", "alias": ["enum", "migrate"],
"artifact": []},
]
print(f"{'skill':22}{'score':>7}{'last_used(d)':>14}{'hits':>6}")
for name, score, last, hits_n in score_skills(REPO, SKILLS):
print(f"{name:22}{score:>7}{str(last):>14}{hits_n:>6}")
スクリプトを実際のSVN作業コピーで回しました。最初の出力がおかしかったのです。table-creatorとexcel-readerはどちらもartifactパターンが空のため、コミットメッセージに"table"/"xlsm"という単語が入っているときだけ捕捉されました。ところがこの2つのツールは成果物がただの.xlsmファイルなので、パターンでは区別できません。結果として、どちらもスコアが異常に低く出ました — 実際には毎日使うツールなのに。
ここで重要な判断をしました。スコアが低いからといって、無条件に切ってはいけません。スコアが低い理由が「本当に使っていないから」なのか「測定がツールを捕捉できていないから」なのかを、人が切り分けなければなりません。AIが作った数字は候補を絞ってくれるだけで、最終決定は人が下します。
そこでAIに再度依頼しました。
artifactパターンが空のツールはスコアを信頼できないので、出力に
confidenceカラムを追加してほしい。artifactマッチングが一度もなかったツールはconfidence=LOWと表示し、自動キュレーション候補から除外して。LOWのツールは「測定不可 — 手動チェック」として別にまとめてほしい。
この再依頼で、出力が2つの束に分かれました。信頼できるスコアで切れるツールと、測定が弱いため人が直接見なければならないツールです。実際に回した結果の形は、おおよそこうでした(スコアは著者の作業コピー基準の実測値、ツール名の一部は匿名化しています)。
| skill | audit_score | last_used(日前) | confidence | 判定 |
|---|---|---|---|---|
| relation-map-gen | 71.4 | 2 | HIGH | 維持 |
| schema-doc | 58.9 | 5 | HIGH | 維持 |
| gdd-gen | 22.1 | 31 | HIGH | 観察 |
| migrate-legacy-enum | 0.0 | 測定されず | HIGH | キュレーション候補 |
| table-creator | 4.2 | 1 | LOW | 手動チェック → 維持 |
| excel-reader | 6.0 | 1 | LOW | 手動チェック → 維持 |
migrate-legacy-enumはスコア0、confidence HIGHでした。90日間、このツールのフォルダも成果物も、ただの一度もコミットに登場しなかったという意味です。記憶をたどってみると、昨年レガシーenumを一度マイグレーションして終わった、一回限りであるべきだった作業を、スキルとして固定化していたものでした。これこそ切るべきツールです。逆にtable-creator・excel-readerはスコアが低かったものの、confidenceがLOWで、最終使用日は1日前でした。測定が捕捉できなかっただけで、実際には毎日使っています。切ってはいけません。
注意: 上の表のスコア算定式(直近加重×8、stale減点)は、著者が自分の作業コピーに合わせてチューニングした値です。SVNのコミット習慣・成果物パターンが違えば、係数も変わります。絶対スコアよりも「ツール間の相対順位」と「confidenceの区分」が、このツールの本質です。
skill_audit_scoreは測定ツールにすぎません。測定値を四半期の振り返りに組み込んで一周するサイクルがあってはじめて、ツールは実際に整理されます。そのサイクルが次です。
flowchart TD
A[四半期振り返り開始] --> B[skill_audit_score実行
SVNログ90日分をパース]
B --> C{confidence判定}
C -->|HIGH| D{audit_score評価}
C -->|LOW| E[手動チェックのキューへ移動
最終使用日を直接確認]
D -->|スコア高| F[維持]
D -->|中間・下降傾向| G[観察 — 次の四半期に再測定]
D -->|0または最下位| H[キュレーション候補の確定]
E --> F
E --> H
H --> I{代替可能?}
I -->|Wrapperへ吸収| J[既存ツールへMECE増強
§23.1 wrapperポリシー]
I -->|完全廃棄| K[sync_skills.py --cleanup
junction除去 + SVN保管]
J --> L[12スロット回復の確認]
K --> L
L --> A
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class B,C,K code;
class A,D,E,I human;
class F,L pass;
このサイクルの2つの出口を区別することが重要です。スコアが0のツールだからといって、無条件に削除するわけではありません。その作業自体が消えたのなら完全廃棄(--cleanup)へ送り、その作業はまだ必要だが独立ツールとして置くほど頻繁ではないのなら、既存ツールに吸収させます。後者がまさに§23.3.4のMECE増強です。
廃棄するときも、SVNの履歴にはコードが残ります。junctionとグローバルへの公開を引き上げるだけで、コード自体を永久に消すわけではありません。6か月後にその作業がまた発生したら、SVNから復元すればいいのです。この「元に戻せる」という安全網があってこそ、人は思い切って切れます。
測定して切るよりも良いのは、そもそも作らないことです。skill_audit_scoreが事後の整理だとすれば、MECE wrapperポリシーは事前の抑制です。
MECEとは、Mutually Exclusive, Collectively Exhaustive — 互いに重ならず、漏れなく。新しいツールを作りたくなるたびに、この2つの基準を投げかけます。新しいツールは既存ツールと重なるのか(ME違反)。それとも本当に空白の領域を埋めるのか(CE貢献)。プロジェクトAのwrapperポリシーは、ここで二手に分かれます。
| 状況 | ポリシー | 結果 |
|---|---|---|
| 新しい作業が既存ツールの領域と重なる | 既存ツールの増強を優先 | 既存wrapper本体に機能を追加、新スロットは使わない |
| 新しい作業が明確に別の領域である | 新規wrapperを許可 | 12スロットのうち1つを新ツールに割り当て(外すべき候補を同伴) |
核心は「デフォルトが増強」だということです。新しいツールを作るのは例外です。その例外を正当化するには、「既存のどのツールでもこの作業はできない」を証明しなければなりません。このデフォルト1つが、19個に膨らんだツールを再び12個まで引き下げた、本当の要因でした。
これは§23.1のcascadeにもつながります。checkのようなcascadeは、もともと4種類あった検査ツールを1つの呼び出しにまとめた成果物です。4個の別々のwrapperを置く代わりに、MECEの観点から「これは全部『検査』という1つの領域だ」と見て、1つに吸収した事例です。ツールの数は減ったのに、機能はそのままです。これが増強の模範です。
AIアシスタントは、ここではリスク要因であり、同時に解決策でもあります。リスクである理由は、AIに「この作業を処理するスクリプトを作って」と言えば、あまりにも簡単に新しいツールが出てくるからです。ワンクリックでツールが1つ生まれる環境では、MECEの規律がなければ、ツールの墓場は一瞬でできあがります。解決策である理由は、AIに先にポリシーを渡しておけば、AIのほうから「これは既存のrelation-map-genにオプションとして付けるほうがよさそうです」と提案してくるからです。ツールを作るAIには、キュレーションの規律も一緒に握らせる必要があります。
本章のツールを運用していて一番学んだのは、測定値を盲信してはいけない、ということでした。skill_audit_scoreはSVNログという1種類のシグナルしか見ません。そのため、構造的に見落とすものがあります。
excel-readerのようにシートを読むだけで成果物を作らないツールは、コミットを残しません。そのため、confidenceをLOWに落として手動チェックに回す仕組みが必須でした。要約すると、このツールは「決定するツール」ではなく「候補を絞るツール」です。19個をひと目で見渡して、「どれを疑うべきか」を1秒で教えてくれます。その疑いを検証して切るのは、人の役割として残します。測定が人を置き換えるのではなく、人が見るべき場所を指し示してくれるだけです。
ツールキュレーションのサイクルを、自分の手で一周回してみる手順です。
setup
1. ワークスペースのスキル・ツールがバージョン管理(SVN/Git)に入っているか確認してください。成果物も同じリポジトリにコミットされている必要があります。
2. 測定対象のツール一覧を作ってください。ツールごとにname、alias(コミットメッセージに登場する別名)、artifact(成果物のファイルパターン、あれば)を書きます。artifactのない読み取り専用ツールは空欄にしておきます。
prompt(AIへ)
次の前提でツール使用頻度の測定スクリプトを作ってほしい。(1)各ツールは[バージョン管理システム]のログに成果物コミットとして痕跡を残す。(2)直近90日のログをパースし、ツールごとの関与コミット数を数える。(3)直近加重+最終使用日の減点で0〜100のスコアを出す。(4)成果物パターン(artifact)マッチングが一度もなかったツールはconfidence=LOWと表示して自動候補から除外し、手動チェックに分離する。(5)出力はスコア昇順の表 — 低いスコアがキュレーション候補。標準ライブラリのみ使用、リポジトリのパスは引数で受け取る。
verify 1. 毎日使うツールが表の上位(低スコア)に上がってきたら、測定が間違っています。そのツールのconfidenceを確認してください — LOWなら正常(測定不可)、HIGHなのに低いならalias・artifactの設定を点検します。 2. スコア0+confidence HIGHのツールだけを、キュレーション候補として確定してください。最終使用日を記憶と突き合わせて、本当に死んだツールなのかを人が判断します。 3. 候補を「完全廃棄」と「既存ツールへの吸収」のどちらかへ送ってください。廃棄はjunctionを引き上げるだけで、コードはリポジトリに残します。 4. 12スロット(または自分で決めた上限)が回復したか、最後に数えてみましょう。
ツールが6〜8個しかなく、SVNもない一人開発なら、こう簡略化してください。バージョン管理はGitで十分です。git log --since="90 days ago" --name-onlyで変更されたファイルパスを抜き出し、ツールフォルダ名でgrepを1回かければ、「どのツールが最近働いたか」が分かります。スコアリングスクリプトまで作らなくても構いません。核心は数字の精密さではなく、記憶の代わりにログを見る習慣の1つです。四半期に1回、「直近90日に一度も触っていないツール」をgitログで抜き出し、そのツールをにらんでみましょう。その5分が、ツールの墓場を防ぎます。
土曜日の午後、妻がスマートフォンで色合わせパズルをしていました。絡まった毛糸玉を同じ色のかごに分類するYarn Feverというゲームでした。1ゲームが終わると「また同じだね」と言って閉じてしまいます。更新がないので、すぐに飽きてしまったのです。
その瞬間に浮かんだ考えは単純でした。あのループは実証済みの中毒性を持っていて、メカニズム自体は著作権の対象ではありません。動物テーマに変えて、レベルをプロシージャルに無限に量産すれば、「また同じ」問題は消えます。一人で、ブラウザでそのまま動くHTML 3Dとして作れば、妻のスマートフォンへのインストールも要りません。
問題は、私がグラフィックエンジニアではないことです。24年目のプランナーですが、Three.jsでシェーダーを書いた経験はありません。だからこの章は、「AIと一緒に、一人でゲームを1本、数日で動くようにした」実際の記録です。会社のMMORPG(以下、プロジェクトA)の作業と同じツールを使いながら、ドメインコンテンツは1行たりとも混ぜなかった、分離の記録でもあります。
実際のゲームはcritter-sort/リポジトリにあり、gitタグv0.1〜v0.3として3日間の決定が残っています。加工した事例ではなく、そのリポジトリをそのまま引用します。
最初にやったのは、原作を言葉で分解してAIに投げることでした。最初のプロンプトはこうです。
プロンプト(v0.1着手): 「Yarn Feverというカジュアルパズルのコアループを動物テーマにアレンジして、Three.js + Viteで作りたい。ループはこう。絡まった色の塊を同じ色の入れ物に分類して、仮スロットを超えたらゲームオーバー。動物を分類対象にして、絡まった動物の山をタップすると同じ色の巣(nest)へ送る形でいこう。ロジックはThree.jsと無関係な純粋JSのステートマシンとして書いて、headlessテストができるようにして。プロシージャル無限レベル(シードベース)も入れて。」
AIは忠実についてきました。フォルダ構成をgame/(純粋ロジック)とrender/(Three.js)に分け、state.js・rules.js・generator.jsを先に書いたあと、色を塗ったボックスのplaceholderでボードを表示しました。数日どころか、1セッションでv0.1が動きました。
ところが、妻に見せようと自分でプレイした瞬間、違和感を覚えました。動物が画面の上でかごへぴょんと跳び込んでいく、ありふれたマッチパズルになっていたのです。原作の手触りが抜け落ちていました。Yarn Feverのアイデンティティは「分類」ではなく、絡まった毛糸を手でほどいていく感触と画面を回しながら隠れた色を確認する行為でした。私はプロンプトでそれを「動物をタップしたら巣へ」という一般的なsortに平たく押しつぶしてしまい、AIは私が与えた定義に忠実だっただけです。
これがリバースエンジニアリングの最初の罠です。原作を要約するとシグネチャーが蒸発します。要約は本質ではなく、表面を残すからです。
ここで一つはっきりさせておきます。AIが間違った答えを出したのではありません。私のプロンプトが間違っていたのです。だから次のサイクルは、コードを直すことではなく、定義を正すことでした。
v0.1を見た直後、2回に分けて核心を改めて明示しました。1回目の修正は行為を、2回目の修正は感触を狙ったものです。
プロンプト(1回目の修正 — 行為): 「これ、ただのマッチパズルになってる。Yarn Feverのアイデンティティは『3D画面を回しながら隠れた色を確認して、ほどく順番を決める』ことだ。ボードを平面にばらまかず、中央の高い小山として積み上げて、回さないと奥に隠れた色が見えないようにして。OrbitControlsで回転も入れて。でも回転とタップが衝突するよね — どう分離する?」
Claudeの出力(生のまま、要約なし): 「そのとおりです。回転ジェスチャーとタップが同じポインターイベントなので衝突します。距離・時間のしきい値で分ける方式を提案します。
pointerdownで座標・時刻を記録し、pointerupで移動距離が9pxを超えるか、押している時間が400msを超えたら回転ジェスチャーと見なしてタップを無視します。それ以下のときだけraycasterで動物を拾ってstate.tap()を呼び出します。OrbitControlsのazimuthは±54°程度に制限して、山が画面の外に出ないようにします。ただし、モバイルでは9pxが敏感すぎる可能性があるので、実機での確認が必要です。」
その9px/400msのしきい値は、今もsrc/main.jsにそのまま生きています。無駄のない、直接検証できるコードです。
// src/main.js — ドラッグ=回転(OrbitControls) / 短いタップ=毛糸ほどき
canvas.addEventListener('pointerup', (ev) => {
if (!down || state.status !== 'playing') { down = null; return }
const dist = Math.hypot(ev.clientX - down.x, ev.clientY - down.y)
const dt = performance.now() - down.t
down = null
if (dist > 9 || dt > 400) return // 回転ジェスチャーと見なす
const rect = canvas.getBoundingClientRect()
const x = ((ev.clientX - rect.left) / rect.width) * 2 - 1
const y = -((ev.clientY - rect.top) / rect.height) * 2 + 1
const id = view.pick(x, y, camera)
if (id !== null) state.tap(id)
})
行為は戻ってきました。ところが2回目にプレイしてみると、依然として「毛糸をほどく」感じがありません。動物がただ巣へ移動するだけで、ほどける手触りがないのです。そこで2回目の修正です。
プロンプト(2回目の修正 — 感触): 「回転は良くなった。でもまだ『毛糸をほどく』感じがない。タップしたら、動物の体に巻きついた絡まった毛糸がほどけて消え、その毛糸がかごへ巻き取られていく演出を入れて。動物のルックも、つるっとしたGLBの鳥じゃなくて、ぽってりしたニットのぬいぐるみ(あみぐるみ)にして。眠そうな目、ほっぺのチークみたいなコージーなディテール。外部アセットが合わなければ外して、プロシージャルに作ってもいい。」
Claudeの出力(生のまま): 「2つを分離して実装します。(1) 絡まった毛糸:各動物の胴体に、少し暗くした同じ色の
TorusGeometryを2本巻いておき、タップ時にscaleを0.01へ縮めて、ほどけて消えるようにします。(2) 毛糸の巻き取り:TubeGeometryで動物→かごの経路の毛糸を描き、かご側へ巻き取られていくトゥイーン。ぬいぐるみのルックはプロシージャルでいきます —MeshStandardMaterialにknit bumpテクスチャをプロシージャル生成してniitの質感を与え、眠そうな目は平たいbox、ほっぺのチークは半透明のピンクのsphereで。既存のGLBスターター(鳥3種)はニットのトーンと合わないので削除しますが、動物パックのロードパイプラインはフォールバックとして残しておきます。一つ指摘しておくと、プロシージャル生成の動物は種の多様性がGLBに及びません — まず1種で検証してから種を増やすのが安全です。」
最後のあの警告(「種の多様性がGLBに及ばない」 — GLBとはglTF Binary、外部から入手して使う既成3Dモデルのファイルフォーマットです)こそが、v0.3につながった種でした。AIが次の限界を先に口にし、私はそれを次のマイルストーンとして受け取りました。
検証は毎回2段階でした。headlessでロジックが壊れていないか(エラー0)、その次にブラウザで直接、回転・タップで手触りを確かめます。v0.2のコミットメッセージにその検証が固定化されています。「headless検証:回転・毛糸ほどき・自動クリア正常、エラー0」。
絡まった毛糸2本は、今もsrc/render/pieces.jsにこう残っています。
// src/render/pieces.js — 胴体に巻いたゆるい毛糸2本(少し暗くした同じ色)
const strandMat = new THREE.MeshStandardMaterial({ color: darken(hex, 0.7), roughness: 1 })
const strands = []
const orient = [[0.5, 0.2, 0.0], [1.25, 0.0, 0.6]]
for (let i = 0; i < 2; i++) {
const s = addMesh(g, G.torus, strandMat, [0, byo + 0.02, 0], Math.max(bx, bz) + 0.02, orient[i])
strands.push(s)
}
g.userData.strands = strands // タップ時にview.jsがこの毛糸をほどいて消す
ここで得た教訓を1行ずつ残しておきます。
言葉だけ見れば「2回直した」ですが、gitの履歴は、その修正がいつ、どんな形で入ったのかを正確な時刻とともに残しています。一人開発では、これが振り返りの代わりになります。同僚がいなくても、コミットが「なぜこうなったのか」を証言してくれるのです。
| コミット | 時刻(2026-05-30) | 何が変わったか | シグネチャーの状態 |
|---|---|---|---|
2b2e3bc v0.1 |
14:43 | Yarn Feverのリバースエンジニアリング、純粋ロジック + placeholder、60/60ソルバー通過 | 欠落(一般的なsortに平たくつぶれる) |
70a0117 v0.2 |
15:11 | 回転(OrbitControls ±54°) + タップ/ドラッグ分離 + 毛糸ほどき + あみぐるみ | 復元(核心の再定義) |
160663c スナップショット |
15:31 | v0.2ギャラリースナップショット5カット + READMEギャラリー | — |
59b0baf v0.3 |
15:55 | プロシージャルあみぐるみ8種 + ビビッドなキャンディパレット | 強化(種の多様性を確保) |
c5b9a1b 引き継ぎ |
16:20 | NEXT_SESSIONセッション引き継ぎポインター | — |
v0.2のコミットメッセージ本文が、決定そのものを固定化しています。「ゲームのアイデンティティを『動物がぴょん』から『画面を回しながらかわいい毛糸玉(ニットのぬいぐるみ)をほどいて同じ色のかごへ』に正した」。1時間半の間に、ゲームのアイデンティティが一度死んでよみがえった記録です。
注目すべきディテールを一つ。v0.2のgit show --statを見ると、スターターのGLBの鳥3種(Flamingo・Parrot・Stork)が丸ごと削除されています。「アートがニットのトーンと合わないから」でした。外部の無料アセットをタダだからと全部使うのではなく、トーンが合わなければ消すという決定。これはAIではなく人が下した、美意識のゲートです。
public/assets/animals/pack_starter/Flamingo.glb | Bin 77428 -> 0 bytes
public/assets/animals/pack_starter/Parrot.glb | Bin 97024 -> 0 bytes
public/assets/animals/pack_starter/Stork.glb | Bin 76852 -> 0 bytes
v0.2が残した宿題は「プロシージャル動物は種の多様性がGLBに及ばない」でした。v0.3でそれを解きました。外部アセットを1つも追加せず、コードで動物8種を量産したのです。
核心はsrc/render/pieces.jsのSPECIESテーブルです。種ごとに胴体の比率・頭・耳のタイプ・鼻先・目の形をパラメーターとして定義し、1つの関数がそのパラメーターを読んでメッシュを組み立てます。
// src/render/pieces.js — 種別シルエットパラメーター
const SPECIES = {
cat: { body: [0.5,0.46,0.48,0.04], ears: 'cat', snout: 0.13, tail: 'cat', eyes: 'sleepy' },
bear: { body: [0.52,0.5,0.5,0.03], ears: 'bear', snout: 0.16, tail: 'none', eyes: 'round' },
bunny: { body: [0.46,0.5,0.46,0.02], ears: 'bunny', snout: 0.12, tail: 'puff', eyes: 'round' },
fox: { body: [0.5,0.44,0.48,0.04], ears: 'fox', snout: 0.2, tail: 'fox', eyes: 'sleepy' },
capybara: { body: [0.58,0.5,0.56,0.02], ears: 'tiny', snout: 0.22, tail: 'none', eyes: 'sleepy' },
pig: { body: [0.54,0.5,0.52,0.03], ears: 'pig', snout: 0.1, nose: true, eyes: 'round' },
frog: { body: [0.56,0.4,0.54,0.05], ears: 'none', snout: 0.1, topEyes: true, eyes: 'none' },
chick: { body: [0.42,0.44,0.42,0.05], ears: 'none', beak: true, tail: 'none', eyes: 'round' },
}
export const SPECIES_IDS = Object.keys(SPECIES) // 8種
耳の形一つでシルエットが分かれます。猫・キツネはとがったcone、クマは丸いsphere、ウサギは細長いsphere、ブタは前に折れたcone。カエルは頭の上に飛び出した目(topEyes)、ヒヨコはくちばし(beak)。この小さな分岐が8種の識別性を作ります。外部アセット0、コード1ファイルです。
ところが、プロシージャル生成には罠があります。「それらしく見える」コードが実際に識別可能な8種を作るかどうかは、コードを見ただけでは分かりません。だから検証はここでも2段階でした。headlessで8種がエラーなく生成されるか、その次にweb-screenshotスキル(headless Chrome)で実際のレンダリングをキャプチャして、目で8種が区別できるか。DEVLOG v0.3にその結果があります。「耳/鼻先/鼻/くちばし/しっぽ/目でシルエット区別。外部アセット0、ニットのトーン完全統一」。
レベルの無限性はシードRNGが担います。generator.jsはレベル番号をKnuthの乗算ハッシュでシード化し、mulberry32で決定的な乱数を取り出します。同じレベル番号は、常に同じボードです。
// src/game/generator.js
export function generateLevel(level, animalPool = null) {
const seed = (level * 2654435761) >>> 0 // Knuth multiplicative hash
const rng = makeRng(seed)
const { C, K, groupsPerColor, M, T } = levelParams(level)
const colors = rng.shuffle(COLORS).slice(0, C)
// ...
for (const color of colors) {
const count = K * groupsPerColor // 常にKの倍数 → 巣にきっちり分解(解ける保証)
// ...
}
}
ここの1行がゲームの公正性を保証します。色ごとの動物数を常にK(巣の完成数、3)の倍数に強制したため、どんなボードでも巣にきっちり割り切れます。解けないレベルが、そもそも生まれません。
設計上解けるというのは、証明ではありません。rules.jsに検証用のグリーディソルバーを入れ、test-logic.mjsで60個のレベルを自動プレイさせて、実際に全部クリアできるかを毎回確認します。以下は、この章を書きながら回し直した実測出力です。
$ node scripts/test-logic.mjs
[ソルバー] 60/60 レベルクリア
[難易度カーブ] (C=色, K=完成, groups, M=巣, T=トレイ, 総数)
Lv 1: C=3 K=3 grp=2 M=3 T=7 総=18
Lv 8: C=4 K=3 grp=3 M=4 T=6 総=36
Lv12: C=5 K=3 grp=3 M=4 T=5 総=45
Lv20: C=5 K=3 grp=3 M=4 T=4 総=45
[でたらめプレイ] ランダムタップ時の敗北率 (難易度の存在確認)
Lv 1: ランダム敗北率 0%
Lv12: ランダム敗北率 1%
Lv20: ランダム敗北率 3%
このテストは2つのことを同時に証明します。グリーディソルバーが60/60をクリアするということはすべてのレベルが解けること(難易度が不可能ではない)であり、ランダムタップの敗北率がレベルが上がるにつれて0%→3%へ上がるということは難易度が実在すること(でたらめに押しても全部クリアできてしまうならゲームではありません)です。トレイが7マスから4マスへ狭まる難易度カーブが、敗北率として測定されます。
ここで正直に断っておきます。ランダム敗北率3%は「でたらめに押すボット」の敗北率であって、人間の体感難易度ではありません。人間は回転で色を先に確認するので、敗北率はもっと低くなります。この数値は「難易度が0ではない」という方向の証明であって、妻が3%の確率で負けるという意味ではありません。人間の体感難易度はv0.3時点ではまだ測定前で、NEXT_SESSIONに「妻のプレイフィードバック収集(最優先)」として残してあります。
flowchart TD L["レベル番号N"] --> H["Knuthハッシュ
N × 2654435761"] H --> S["mulberry32(seed)
決定的RNG"] S --> P["levelParams(N)
C・K・M・Tを算出"] P --> G["generateLevel
色ごと = Kの倍数"] G --> B["ボード(絡まった山)"] B --> R["createCritter
あみぐるみ8種 + knitシェーダー"] G --> V["greedySolve
60/60検証"] V -->|"エラー0"| OK["クリア保証"] R --> SC["web-screenshot
8種識別の視覚検証"] classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545; classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b; classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d; class H,S,P,G,R,V,SC code; class L,B data; class OK pass;
シードから出発してパラメーター・ボード・メッシュ・検証へ分かれていくこの流れが、「更新がなくて飽きる」という最初の問題を構造的に解いた答えです。
プロシージャル動物8種は、GLBがないときのフォールバックです。あとで本物のあみぐるみGLBを入手したらそちらを優先して使えるよう、動物パックのパイプラインは残してあります。フォルダにGLBをドロップしてnpm run scanを回すだけで終わりです。
問題は、GLBごとにサイズがばらばらだという点です。あるモデルは0.5ユニット、あるものは200ユニット。手作業でscaleを合わせていたら、動物パックの追加が労働になってしまいます。そこでscan-packs.mjsがGLBのバウンディングボックスを読み、目標の高さ(0.95ユニット)に合うscaleを自動計算します。
// scripts/scan-packs.mjs — GLBのバウンディングボックスからscale/yOffsetを自動算出
const maxDim = Math.max(max[0]-min[0], max[1]-min[1], max[2]-min[2])
const scale = +(TARGET_H / maxDim).toPrecision(3) // TARGET_H = 0.95
const yOffset = +(-((min[1] + max[1]) / 2) * scale).toPrecision(3)
そしてassets.jsは、packs.jsonがないかロードに失敗すると、静かにプロシージャル動物へフォールバックします。
// src/render/assets.js
createAnimal(species, hex) {
const entry = this.models.get(species)
if (!entry) return createCritter(hex, species) // プロシージャルあみぐるみフォールバック
// ... GLBクローン + カラーティント
}
この2行が「GLBがあればGLB、なければコードの動物」を無停止で保証します。妻が遊んでいる間に私が新しいGLBパックを放り込んでも、ゲームは止まりません。
このプロジェクトで私はプランナー1人でしたが、作業は複数の役割で回っていました。AIがその役割を埋めたのです。核心は「コードを代わりに書いてくれる」ではなく、私の弱いところを埋めるでした。
| 私の弱いところ | AIがやったこと | 人(私)が守ったゲート |
|---|---|---|
| Three.jsシェーダー | knit bumpプロシージャルテクスチャ、TubeGeometryの毛糸演出 | トーンが合っているか(GLBの鳥3種の削除決定) |
| 入力衝突の解決 | 9px/400msしきい値の提案 | モバイル実機での体感確認 |
| リグレッション安全性 | greedySolveで60/60自動検証 | 「難易度の実在」は人が定義 |
| 次の限界の予測 | 「プロシージャル動物は種の多様性が弱い」という警告 | それをv0.3マイルストーンとして採択 |
とくに視覚検証が、一人開発の弱い輪でした。コードが動くことと「8種が目で区別できること」は別の問題です。そこでweb-screenshotスキル(headless Chromeでdevサーバーを立ち上げてスクリーンショット + コンソールエラー報告)を、会社の作業からそのまま借用しました。claude-in-chrome拡張なしでも、モバイルビューポート(iPhone 15 Pro縦、393×852)のレンダリングを目で確認できました。
ここで、もっとも重要な原則が働きます。ツールは会社から借用するが、ドメインコンテンツは0件借用する。
この分離はgrepで検証されます。メモリ記録に「会社プロジェクトのドメインコンテンツ借用0件(検証grep PASS)」が残っています。Critter Sortの色はピンク・ミント・イエローで、動物は猫・クマ・ウサギです。プロジェクトA(会社のMMORPG)のドメイン語彙は、このリポジトリのどこにもありません。
なぜここまで分離するのか。2種類の事故を同時に防ぐためです。会社のIPが個人の趣味に漏れる法的事故、そしてMMORPGドメインのatomがパズル作業に誤って注入されてノイズになるコンテキスト汚染。ツールだけを流し、コンテンツは堰き止める — その間にあるのが健全な分離です。
Critter Sortは小さなゲームです。3日、コミット5個、動物8種、レベル60個。それでも、会社で使っていたやり方が1/1000の規模でもそのまま機能しました。
いちばん大きな学びは、最初の節の失敗でした。v0.1でゲームのアイデンティティを一度死なせ、2回の修正で生き返らせました。同僚のいない一人開発でその死と復活を証言してくれたのは、gitコミットでした。振り返りがなければ、「なぜv0.2で全部作り直したんだっけ?」を1か月後には忘れていたはずです。
次のPart 24では、こうした決定履歴を大きなチーム・長期の運営でどのようにガバナンスとして固めるかを扱います。
この章は、妻が飽きて閉じたパズルから出発して、一人で作ったゲームが再びその手に渡るまでの記録でした。システムは規模ではなく、規律の問題であることを確認しました。
好きなカジュアルゲーム1本のコアループを、AIと一緒に回してみるステップです。ただし、シグネチャーを失わないように。
setup — Nodeが入った環境で、空のフォルダを1つ作りましょう。mkdir my-puzzle && cd my-puzzle。
prompt — AIにこう投げてみましょう。核心は「要約せず、シグネチャーを明示する」ことです。
「[ゲーム名]のコアループを[テーマ]にアレンジしたい。このゲームのシグネチャーは[手触りを1行で書く — 例:『画面を回して隠れたものを確認しながらほどく感触』]だ。これを絶対に一般的なマッチパズルへ平たくつぶさないで。ロジックはレンダリングと分離して、headlessでテストできるように書いて。」
verify — 最初の結果を自分でプレイしてみましょう。「自分が書いたシグネチャーは生きているか?」を問います。生きていなければ、コードではなく定義を書き直して再依頼してみましょう。それがv0.1→v0.2で私がやったことです。
エンジンも、プロシージャル生成も要りません。紙1枚に「このゲームのシグネチャー1行」を書き、AIにプロトタイプを作らせたあと、自分のプレイでその1行が生きているかだけを見てみましょう。死んでいたら、その1行をもっと具体的に書き直します。シグネチャー1行を守る習慣 — それ一つで、リバースエンジニアリングの最初の罠は避けられます。
月曜の朝、スタンドアップ直後に、データチームのメンバーAが社内メッセンジャーでスクリーンショットを1枚送ってきました。ゲーム内ショップで、ある素材アイテムの説明文が空になっているというQAレポートでした。原因を30分追跡した末に、正体が判明しました。2週間前、誰かが企画ドキュメントの中でそのアイテムの名前を재료_목재_상に変えたのですが、マスターデータの参照は古い名前재료_목재_Aをそのまま指していました。ドキュメントは更新され、シートは更新されず、両者をつないでいたリンクは静かに切れました。誰も嘘をついていないのに、ゲームは嘘を出力していたのです。
こうした事故は、ドキュメントが増えるほど幾何級数的に頻発します。人間の目は、50件のドキュメントの相互参照を同時には見られません。だから検証をコードに委ねます。本章では、ドキュメント・データ・リンクの整合性を、人間ではなくスクリプトが検査するシステムを扱います。核心は三つ — ソースの整合(_source_map.tsv audit)、リンクの整合性(wikilink)、そしてstale検知(古くなって腐った参照を捕まえること)です。
ドキュメントとデータは、互いを指し合いながら生きています。仕様書がenumを参照し、enumがマスターデータを参照し、シートがまた別の仕様書の決定を参照します。この網を人間が手で管理すると、一つのノードが変わるたびに、そのノードを指していたすべての参照を人間が記憶してたどらなければなりません。記憶は失敗します。
切れたリンクが危険な理由は、それがエラーを投げないことにあります。コードであれば、存在しない変数を参照したときにコンパイラが止めてくれます。しかしドキュメントの中で[[재료_목재_A]]と書かれたウィキリンクは、その対象が消えても、ただの平凡なテキストとして残ります。赤くは変わりません。ゲームはビルドされ、リリースされ、ユーザーが空の説明文を見て初めて誰かが気づきます。
だから検証システムの最初の仕事は、人間の目に見えないものを見えるようにすることです。整合違反をテキスト出力として引きずり出し、その出力をビルドゲートに結びつけておけば、人間が忘れてもスクリプトは忘れません。
検証はひとかたまりではなく、段階です。まず最も安い検査を回して明白な違反をふるい落とし、通過したものだけを次の段階へ送ります。高い検査をすべての入力に回すと、遅すぎて誰も回さなくなるからです。以下は著者が運用している検証フローです。
flowchart TD
A[ドキュメント・シート保存] --> B{source_map audit}
B -- ソースマッピング欠落 --> B1[FAIL: 手動編集の痕跡
_source_map.tsvの更新を要求]
B -- 通過 --> C{wikilinkの整合性}
C -- 切れたリンクを発見 --> C1[wikilink_apply.py
修復を試行]
C1 -- 自動修復可能 --> C
C1 -- 修復不可 --> C2[FAIL: 切れた参照のレポート]
C -- 通過 --> D{stale検知}
D -- 参照先より古い --> D1[WARN: 再レビューキューに登録]
D -- 通過 --> E[integrity_check 最終]
E -- P0違反 --> E1[BLOCK: ビルドゲート遮断]
E -- 通過 --> F[GREEN: コミット許可]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class B,C,C1,D,E code;
class A data;
class F pass;
class B1,C2,D1,E1 fail;
このcascadeの核心は、失敗が早いほど安いということです。source_map auditはTSVの1行比較なので、ミリ秒単位で終わります。逆に、最後のintegrity_checkはマスターデータ全体をロードしてFK関係を検査するため、数秒かかります。安い検査を前に置けば、明白なミスはそこで切り落とされ、高い検査はそれを通過した少数の入力にだけ回ります。
各段階の出力が異なる点も重要です。auditはFAIL(編集者が何かを手で触った証拠)、wikilinkは自動修復後のFAIL、staleはWARN(ブロックはしないが再レビューが必要)、integrity_checkはBLOCK(ビルド自体を止める)。同じ「問題」でも深刻度に応じて違う反応をしてこそ、人間がシグナルとノイズを区別できます。
_source_map.tsv audit最初に回る検査はソースの整合です。著者のドキュメント生成パイプラインは、ある合成ドキュメント(例: GDD本文)がどのソースファイルから生成されたのかを_source_map.tsvに記録します。1行が「この成果物のセクション=これらのソースファイルの合成」という系譜(lineage)を確定させます。
これが検証ツールになる理由は、人間が成果物を手で編集するとマッピングが壊れるからです。自動生成されたGDDセクションを誰かが直接修正すれば、そのセクションはもはやソースファイルの忠実な合成ではありません。auditスクリプトは、成果物の各セクションのハッシュをソースから再合成したハッシュと比較し、一致しなければFAILを出します。「手動編集時はaudit FAIL」というルールはここから来ています。
これは人間の編集を禁じるためではなく、編集を明示化するためです。成果物を直す必要があるなら、ソースを直して再生成するか、あるいはそのセクションをマッピングから正式に切り離すか(分離宣言)、どちらかにせよというシグナルです。静かな編集をうるさくすること、それがauditの仕事です。
auditを通過すると、リンク検査に進みます。著者のドキュメントは、Obsidian式のウィキリンク[[対象]]でノードをつなぎます。wikilink_apply.pyは二つの仕事をします — ウィキリンクを実際のパスに解決して適用すること、そして切れたリンクを可能な範囲で修復することです。
修復が可能なケースは明確です。対象ノードが名前だけ変わって同じ場所に存在するときです。先ほどの재료_목재_A → 재료_목재_상のようなリネームは、エイリアスマッピング(alias map)が更新されていれば、スクリプトが旧名を新名に自動修正します。一方、対象が丸ごと削除されたか、どこへ行ったのか追跡不能な場合は、修復をあきらめて切れた参照をレポートします。
ここに設計判断が一つあります。自動修復を攻撃的にやりすぎると危険です。「似た名前」を探して勝手につなぎ直すと、意味の違うノードにリンクが誤って張られ、もっと悪い事故が起きます。だからwikilink_apply.pyの修復は保守的です — 明示的なエイリアスマッピングがあるリネームだけを自動修正し、推測が必要なケースは人間に渡します。自動化の美徳は、確実なものだけを自動でやり、曖昧なものは正直に人間へ差し戻す節度にあります。
リンクが生きていても、参照が古びている可能性があります。ドキュメントAがマスターデータBを参照していて、BがAより後に更新されたなら、Aの説明は現在のBと食い違っている可能性があります。リンク自体は無事です。指す対象は存在するのですから。しかし中身が腐っています。
stale検知は、参照の両側の更新時刻(またはコンテンツハッシュのバージョン)を比較します。参照する側が参照先より古ければWARNを出し、そのノードを再レビューキューに登録します。BLOCKではなくWARNである理由は、更新が常に内容の衝突を意味するわけではないからです。誤字を一つ直しただけの更新なら、参照は無事です。だからstaleは「止めるもの」ではなく「見ろと印を付けるもの」です。
この段階が、冒頭の切れたリンク事故をどう捕まえるかを見てみましょう。もし재료_목재シートがドキュメントより後に更新されていたなら、自動修復の前に、すでにstale WARNが出ていたはずです。つまり三つの検査は重なり合うセーフティネットです。一つの網が逃したものを次の網が捕まえます。単一の検査では捕まえられない事故をcascadeが捕まえる理由は、これです。
検証ロジックを人間がゼロから全部書くと、疲れ果てます。著者は検査ルールを自然言語で記述し、実行スクリプトはAIにドラフトをもらいます。以下はstale検知スクリプトを作った実際のセッションです。出力には手を加えず、失敗と却下の過程をそのまま残します。
[プロンプト全文]
ドキュメントフォルダのMarkdownファイルが、互いにウィキリンク
[[対象]]でつながっている。各ドキュメントが、自分の参照する対象ファイルよりも更新が古いか(=stale)を検査するPythonスクリプトを作ってほしい。条件: - ファイルの更新時刻はgitの最終コミット時刻基準(ファイルシステムのmtimeはチェックアウト時に更新されるので信用できない) - 切れたリンク(対象なし)はstale検査から除外し、別途レポート - 出力はTSV:source<TAB>target<TAB>status(status = OK / STALE / BROKEN) - WARNは非ブロッキング、終了コードは0を維持(ビルドを止めてはいけない)
[Claudeの生の出力 — 1次]
以下のスクリプトは
git log -1 --format=%ct <file>で各ファイルの最終コミット時刻を取得し、ウィキリンクを正規表現\[\[([^\]]+)\]\]で抽出して比較します。import subprocess, re, sys, pathlib def git_mtime(path): out = subprocess.run( ["git", "log", "-1", "--format=%ct", str(path)], capture_output=True, text=True) return int(out.stdout.strip()) def resolve(link, root): # [[対象]] -> root/対象.md return root / f"{link}.md" root = pathlib.Path(sys.argv[1]) for md in root.glob("*.md"): src_t = git_mtime(md) for link in re.findall(r"\[\[([^\]]+)\]\]", md.read_text(encoding="utf-8")): tgt = resolve(link, root) if not tgt.exists(): print(f"{md.name}\t{link}\tBROKEN") continue tgt_t = git_mtime(tgt) status = "STALE" if src_t < tgt_t else "OK" print(f"{md.name}\t{link}\t{status}")これで、ご要望のTSV形式で出力されます。
[人によるレビュー / 却下]
ここで止めました。三つの欠陥が見えました。
第一に、git_mtimeが空文字列を返すとint("")で落ちます。一度もコミットされていない新規ファイルがそうなります。第二に、glob("*.md")はサブフォルダを見ません。著者のドキュメントはpart*/配下に散らばっています。第三に — これが最も重要ですが — ウィキリンクには[[対象|表示名]]のようにパイプのエイリアスが付く場合があり、1次のコードは対象|表示名全体をファイル名と誤認します。これでは無事なリンクが全部BROKEN判定になります。そのまま使えば誤検知の爆弾です。
この三つを指摘して、再依頼しました。
[再依頼]
3点直してほしい。(1) コミットされていないファイルはgit_mtimeがNone。この場合はSTALE判定を保留してstatus=UNTRACKED。(2) globではなくrglobでサブフォルダを含める。(3)
[[対象|エイリアス]]のパイプ処理 —|の前の部分だけを対象として使う。それから、終了コードはBROKENがあっても0を維持(WARN非ブロッキングの原則)。
[Claudeの生の出力 — 2次]
import subprocess, re, sys, pathlib def git_mtime(path): out = subprocess.run( ["git", "log", "-1", "--format=%ct", str(path)], capture_output=True, text=True) s = out.stdout.strip() return int(s) if s else None # コミット履歴がなければNone def target_name(link): return link.split("|", 1)[0].strip() # [[対象|エイリアス]] -> 対象 root = pathlib.Path(sys.argv[1]) for md in root.rglob("*.md"): src_t = git_mtime(md) text = md.read_text(encoding="utf-8") for raw in re.findall(r"\[\[([^\]]+)\]\]", text): name = target_name(raw) hits = list(root.rglob(f"{name}.md")) if not hits: print(f"{md.name}\t{name}\tBROKEN") continue tgt_t = git_mtime(hits[0]) if src_t is None or tgt_t is None: status = "UNTRACKED" elif src_t < tgt_t: status = "STALE" else: status = "OK" print(f"{md.name}\t{name}\t{status}") sys.exit(0) # WARN非ブロッキング: 常に0
この2次出力は受け入れました。ただし一つだけ、人間側に残した決定があります — rglob(f"{name}.md")が同名ファイルを複数のフォルダで見つけた場合、hits[0]だけを使います。これは潜在的な曖昧さですが、著者のドキュメント命名規則ではファイル名がグローバルに一意なので、実務では衝突がありません。AIが指摘しなかったこの前提を、人間が意識的に受け入れ、コメントとして残しました。自動化が書いたコードであっても、そのコードが寄りかかる前提には人間が責任を持ちます。
スクリプトがあっても、誰も回さなければ意味がありません。検証の最後の設計は、回さずにいられなくすることです。著者は三つの段階を、コミット前フック(pre-commit)またはビルドパイプラインに結びつけています。audit FAILとintegrity_checkのP0違反はBLOCKなのでコミット/ビルドを止め、wikilink BROKENとstaleはWARNなので通過させつつレポートを残します。
このBLOCK/WARNの二元化が、システムの生存を決めます。すべてをBLOCKにすると、些細なstale一つでコミットが止まり、人々は検証そのものを迂回し始めます。迂回される検証は、存在しない検証と同じです。逆に全部WARNにしておくと、本当に止めるべきデータ整合性違反までそのまま通過します。何を止めて何を表示だけにするかの境界こそが、検証システムの本当の設計ポイントです。
MMORPG開発会社Aで著者が携わるプロジェクトAにおいて、ドキュメント約90件の規模を基準に観察した方向性です。絶対値の一部は著者の推定(未検証)であり、意味があるのはトレンドです。
| 項目 | 手動レビュー時代 | コード検証cascade |
|---|---|---|
| 切れた参照の発見時点 | ユーザー・QAレポートの後 | コミット前(方向: 事故後 → 事前) |
| 整合チェック1回の所要時間 | 数時間(著者の推定) | 数十秒(スクリプト実測) |
| staleの累積潜伏 | 数週間の潜伏 | 次のコミットでWARN |
| 誤った自動修復の事故 | 該当なし | 保守的な修復で0件を維持 |
数値を鵜呑みにするより、「発見時点が事後から事前に引き寄せられた」という方向だけを信頼することをお勧めします。検証システムの本当の価値は、時間の節約よりも、事故がユーザーに届く前に捕まるという位置の移動にあります。
| パターン | 処方 |
|---|---|
| すべての違反をBLOCKにして、人々が検証を迂回 | BLOCK/WARNの二元化。ブロックはデータ整合性のP0のみ |
| 自動修復を推測まで攻撃的に | 明示的なエイリアスのリネームだけ自動。曖昧なら人間へ |
| 切れたリンクだけ見てstaleを無視 | 更新時刻の比較で、古びた参照を別途検知 |
| 成果物の手動編集を静かに許容 | source_map auditで編集をFAILとして可視化 |
| スクリプトはあるがフックに結びつけていない | pre-commit・ビルドゲートに接続し、回さずにいられなくする |
setup. ドキュメントフォルダをgitで管理します(コミット時刻比較の基準)。ウィキリンクは[[対象]]または[[対象|エイリアス]]の表記に統一します。
prompt. AIに上記トランスクリプトのプロンプト全文をそのまま与えます。ただし、最初の出力を絶対にそのまま使わないでください。必ず (1) コミットされていないファイルの処理、(2) サブフォルダの探索、(3) パイプエイリアスのパース — この三つを検証し、却下して再依頼してください。これはAIがほぼ必ず1次で漏らすポイントです。
verify. スクリプトを回してTSVを受け取ります。BROKEN行が本当に切れたリンクなのか、サンプル5件を手で確認してください。偽のBROKENが出るなら、エイリアス/サブフォルダのパースが不十分です。正常が確認できたらpre-commitフックに結びつけ、WARN(STALE/BROKEN)は通過、BLOCK(データ整合性P0)は遮断として終了コードを分岐します。
一人ミニ版. 一人で小さなGDDを書いている場合なら、cascade全部は過剰です。stale検知の一段だけを持ち帰ってください。ドキュメントがマスターデータより古いかどうかをgitの時刻で比較するだけでも、「直したつもりで直していなかった」事故の大半が捕まります。自動修復とsource_map auditは、ドキュメントが30件を超えて手で追えなくなったときに追加すれば十分です。
新人プランナーが入社3日目に尋ねました。「先輩、これらのシステムがどういう順序で互いに影響し合うのか、図で整理されたものはどこかにありますか?」私は口ごもりました。図はありました。半年前に誰かがホワイトボードに描いた写真が、Wikiのどこかに載っていました。ところがその図には、いまは消えたシステムが2つ生き残っていて、その後追加された中核ループが3つ抜けていました。結局私は「図は信用しないで、ドキュメントを読んで」と答えました。恥ずかしい答えでした。図がドキュメントと食い違った瞬間、図は情報ではなく誤情報になります。
この章の結論を先に言うとこうです。人が描いたダイアグラムは1〜2か月以内に必ず腐ります。だからダイアグラムを描く仕事を人の手から切り離し、ドキュメント構造自体が自分の図を吐き出すようにしなければなりません。本章では、その過程を1回の実際の作業記録として示します。ドキュメントを入力として受け取りMermaidコードを生成するワークド・トランスクリプトを丸ごと掲載し、そうして得られたダイアグラムをこのページで実際にレンダリングします。技法を説明する文章が、その技法の成果物で自分自身を証明するわけです。
ダイアグラムツールはたくさんあります。draw.io、Figma、Visio、ホワイトボードの写真まで。これらのツールには共通の落とし穴が1つあります。成果物が画像ファイルだという点です。画像はgitで1行ずつ変更を追跡できず、テキストを扱うLLMが直接生成・修正できず、Markdownドキュメントの中にコードとして載りません。運用の観点で最も致命的なのは1つ目です。誰がいつなぜ変えたのか追跡できない図は、時間が経つと誰も責任を持たない遺物になります。
Mermaidはこの3つを一度に解決します。ダイアグラムをテキストで書き、レンダリングはビューアに任せます。テキストなのでgit diffがノード1つの追加まで捕捉します。テキストなのでLLMが読み書きできます。テキストなのでMarkdownのコードブロックにそのまま入ります。まさにこの章の本文がその証拠です。いまあなたが読んでいるこの文章の下にまもなく登場するダイアグラムは、すべてMarkdown内のテキストブロックであり、本のビルド過程で図としてレンダリングされます。
ただし誤解は防がなければなりません。すべての運用資料をダイアグラムにする必要はまったくありません。項目を並べるなら箇条書きが速く、数値を比較するなら表が速いのです。Mermaidが勝つ場面はちょうど3つです。関係(何が何とつながるか)、フロー(何が何の次に来るか)、シーケンス(誰が誰にいつ何を送るか)。この3つ以外の場面に無理にダイアグラムを差し込むと、かえって認知負荷が増えます。
ここからがこの章の背骨です。抽象的な説明の代わりに、実際のドキュメントのひとかたまりをMermaidに変換する過程を最初から最後まで見せます。入力はプロジェクトAの運営ドキュメントのうち、システムの依存構造を書き留めたMarkdownの断片です(以下は匿名化した実際の抜粋)。
# システム依存メモ(運営ドキュメント抜粋、匿名化)
- combat_core は stat_engine に依存する
- skill_runtime は combat_core に依存する
- skill_runtime は vfx_pool に依存する
- quest_director は skill_runtime に依存する
- quest_director は dialog_graph に依存する
- economy_loop は quest_director の報酬フックを購読する
- economy_loop は stat_engine の派生ステータスを読む
これを手作業でダイアグラムに起こすと、ノード7個に矢印7本。1回なら描けます。問題は来週mail_boxシステムが追加され、dialog_graphが2つに分割されるときです。手描きの図はその瞬間から嘘をつき始めます。だから人ではなく、LLMにこの変換をやらせます。
以下は私が実際に投げたプロンプトです。一文字も整えず、そのまま掲載します。
以下のシステム依存メモをMermaid graph(上→下、graph TB)に変換して。ルールは:
1. メモに出てくるシステムだけをノードに。新しいシステムの追加は禁止。
2. 「AはBに依存」は A --> B。
3. 「購読」「読む」のような弱い結合は点線 -.-> に関係名を付けて。
4. ノードidはメモの英語名のまま。日本語ラベルを付けるな。
コードブロックのみ、説明・コメントは抜きで。
[メモ]
- combat_core は stat_engine に依存する
- skill_runtime は combat_core に依存する
- skill_runtime は vfx_pool に依存する
- quest_director は skill_runtime に依存する
- quest_director は dialog_graph に依存する
- economy_loop は quest_director の報酬フックを購読する
- economy_loop は stat_engine の派生ステータスを読む
ルール1と4が肝心です。LLMにダイアグラムを任せるとき最もよくある事故は「親切なハルシネーション」です。頼んでもいないノードを追加し(「ログインシステムも普通あるので入れました」)、勝手に日本語ラベルを付けてIDと本文検索が食い違うようにしてしまいます。ルールでその親切を遮断します。
次はモデルが最初に返してきた応答です。整えずにそのまま転載します。そこには小さな問題が1つ混ざっていて、その問題まで含めて掲載します。
graph TB
skill_runtime --> combat_core
combat_core --> stat_engine
skill_runtime --> vfx_pool
quest_director --> skill_runtime
quest_director --> dialog_graph
economy_loop -.報酬フック 購読.-> quest_director
economy_loop -.派生ステータス 読み取り.-> stat_engine
文法は問題なく、ノードも正確に7個です。新しいシステムをでっち上げてもいません。ルール1・4は守られました。ただ1つ引っかかりました。点線のラベルに空白入りの日本語が入っています(報酬フック 購読、派生ステータス 読み取り)。これはルール違反ではありません。ルール3が「関係名を付けろ」と言ったので、モデルは忠実に従ったのです。ただ、Mermaidでは日本語ラベルに空白が入ると一部の旧バージョンのレンダラーが壊れることを、私は経験で知っていました。モデルにはそれを知るすべがありません。
ここが、自動化で最も頻繁に省略されながら最も重要なステップです。私は出力をそのまま受け取らず、拒否しました。拒否の理由は2つです。
第一に、点線ラベルの「日本語+空白」を英文トークンに置き換えて、レンダリングの互換性を確保しなければなりません。第二に、弱い結合(点線)と強い結合(実線)が1つの図に混在しているのに、色やスタイルの区別がなく一目で頭に入りません。この2つを携えて、もう一度依頼しました。
ほぼ良い。2点だけ直せ。
1. 点線矢印のラベルを英単語1語に変えろ(空白なし)。
「報酬フック 購読」 -> subscribes、「派生ステータス 読み取り」 -> reads
理由:一部のレンダラーは日本語+空白のエッジラベルで壊れる。
2. 点線(弱い結合)のノードと実線(強い結合)のノードを視覚的に区別するため、
economy_loop のように弱い結合しか持たないノードに classDef で薄いグレーのスタイルを与えろ。
3. 残りはそのままにする。
graph TB
skill_runtime --> combat_core
combat_core --> stat_engine
skill_runtime --> vfx_pool
quest_director --> skill_runtime
quest_director --> dialog_graph
economy_loop -.subscribes.-> quest_director
economy_loop -.reads.-> stat_engine
classDef weak fill:#eee,stroke:#999,stroke-dasharray:4 3;
class economy_loop weak;
今回は受け入れました。ラベルが英文の単一トークンに変わり、economy_loopだけがグレーで切り離され、「このシステムは直接の依存ではなく、購読・読み取りだけでつながった周縁のシステム」という情報が色で伝わります。プロンプトに1行も触れず手で描いていたら、私はこのclassDefを思いつきさえしなかった可能性が高いです。
上のトランスクリプトの最終出力を、手で書き写さずコードブロックのまま本書のページに掲載します。本のビルドがこれを図として描きます。これが「自分の技法で自分を証明する」の実物です。
graph TB
skill_runtime --> combat_core
combat_core --> stat_engine
skill_runtime --> vfx_pool
quest_director --> skill_runtime
quest_director --> dialog_graph
economy_loop -.subscribes.-> quest_director
economy_loop -.reads.-> stat_engine
classDef weak fill:#eee,stroke:#999,stroke-dasharray:4 3;
class economy_loop weak;
ドキュメントの抜粋ひとかたまりが、5回のやり取りを経て、gitに入り、LLMが更新でき、このページにレンダリングされる運用資産になりました。来週mail_boxが追加されたら、メモに1行書き足して同じプロンプトをもう一度投げればよいのです。人がペンを取ることはありません。
前のダイアグラムが「変換の結果」なら、今度のものは「変換の過程」です。先ほど5つのステップで進めたワークド・トランスクリプトの手順をフローチャートにしました。このダイアグラムも同じ方法でLLMにやらせて抽出し、同じ検証を経ています。その結果をそのまま掲載します。
flowchart TD
SRC[運営ドキュメント抜粋] --> PROMPT[変換プロンプト作成]
PROMPT --> LLM[Claudeの生の出力]
LLM --> CHECK{人による検証}
CHECK -->|拒否:レンダリング互換・可読性| REASK[再依頼プロンプト]
REASK --> LLM
CHECK -->|承認| EMBED[Markdownにコードブロックとして埋め込み]
EMBED --> GIT[gitコミット・diff追跡]
GIT -->|ドキュメント変更時| SRC
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class LLM ai;
class PROMPT,CHECK,REASK human;
class SRC,EMBED,GIT data;
このフローチャートが語ることが1つあります。点線ではなく太い矢印で強調したいのは、中央のひし形、すなわち人による検証です。自動化という言葉に酔ってこのノードを外してしまうと、ステップ1の親切なハルシネーションがそのまま運営ドキュメントに載ります。自動化は人を「図を描くこと」から解放しますが、判断からは解放しません。ループの最後の矢印(ドキュメント変更時 → 運営ドキュメント抜粋)が核心です。このフィードバックループがあってこそ、ダイアグラムは一回きりの資料ではなく、ドキュメントとともに老いず、一緒に育つ資産になります。
LLM変換は柔軟ですが、関係がすでに構造化データとして存在する場合は、わざわざモデルを呼ぶ必要はありません。プロジェクトAの決定カードのようにフィールドが固定されたデータは、小さなPythonスクリプトのほうが速く、より正直です(ハルシネーションが原理的に不可能です)。以下は、決定カードのリストを決定グラフのMermaidに変換する実際のスクリプトの核心部です。
# decision_graph_to_mermaid.py
# 決定カード(構造化データ) -> Mermaid graph 変換。LLM不要、決定論的。
def to_mermaid(decisions):
lines = ["graph LR"]
# 1) ノード宣言:idとタイトルをそのまま。でっち上げない。
for d in decisions:
safe_title = d.title.replace('"', "'") # 引用符だけescape
lines.append(f' {d.id}["{safe_title}"]')
# 2) エッジ:関係タイプを矢印ラベルに。
for d in decisions:
for rel in d.relations:
lines.append(f' {d.id} -->|{rel.type}| {rel.target}')
return "\n".join(lines)
核心は、2つのステップだけで終わるという点です。ノードを宣言し、エッジをつなぐ。入力にないノードは出力に絶対に登場しません。このスクリプトが決定カード3枚を受け取ると、以下のようなグラフが出てきます。
graph LR
D_A["グローバルクールタイム0.3秒"] -->|superseded_by| D_B["グローバルクールタイム0.5秒"]
D_B -->|relates_to| D_C["回復スキルの例外許可"]
D_B -->|side_effect| D_D["近接スキルダメージ-5%"]
classDef human fill:#fde68a,stroke:#b45309,color:#000;
class D_A,D_B,D_C,D_D human;
1つの決定が別の決定に置き換えられ(superseded_by)、そこから派生した副作用(side_effect)まで1本の矢印で見えます。テキストで書かれた決定ログ数十行を全部読まなくても、このグラフ1枚があれば「なぜいまクールタイムが0.5秒なのか」の歴史が5分以内につかめます。
いつLLMを使い、いつスクリプトを使うのか。基準は単純です。入力が構造化データ(フィールドが固定されたカード・シート)ならスクリプト、入力が自由テキスト(議事録・メモ・会話)ならLLM。構造化データにLLMを使うと不要なハルシネーションのリスクだけを抱え込み、自由テキストにスクリプトを使うとパースルールが際限なく増えていきます。
ダイアグラム自動化を運用しながら、実際に踏んだ地雷です。
第一に、複雑になりすぎる落とし穴。ノードが50個を超えると、図はもはや認知を助けず、認知を妨げます。処方箋は、1画面あたり20〜30個に制限し、それより大きくなったらsubgraphで領域をまとめるか、いっそダイアグラムを2つに分割することです。
第二に、更新が途切れる落とし穴。これは手描きの図だけで起きると思いがちですが、自動化しておいても入力ドキュメントを直さなければ同じように腐ります。処方箋は、先ほどのフローチャートにあったフィードバックループです。入力ドキュメントが単一の真実の源泉(single source of truth)になるようにし、ダイアグラムは常にそこから再生成します。
第三に、抽象的すぎる落とし穴。「システムがだいたいこんなふうに絡んでいる」レベルの図はきれいですが、役に立ちません。処方箋は、ノードに抽象名詞ではなく実際のID(skill_runtime、D_B)を入れることです。本文の検索とダイアグラムが同じ識別子を共有してこそ、図からコードへまっすぐジャンプできます。
第四に、人のチェックなしでLLMの出力をそのまま使う落とし穴。背骨のステップ3で見たとおり、モデルはルールを守りながらも、レンダリングが壊れるラベルを作ることがあります。処方箋は、人による検証のノードをパイプラインから絶対に外さないことです。
数値を挙げたい誘惑はありますが、ここでは方向だけを述べます。以下の比較は著者が運営したチームで体感した変化であり、精密な測定値ではなく著者の推定(未検証)です。
最もはっきり変わったのは、新人のシステム理解の速度です。入社初期に数日かかっていた「これらのシステムがどう絡んでいるのか」の把握が、自動生成された依存グラフ1枚の前で1時間前後に縮まりました。会議資料の準備も軽くなりました。以前は会議の前日に誰かが手で図を描き直していましたが、いまはドキュメントを一度変換すれば終わりです。何より、ダイアグラムが実際と食い違ったときに生まれていた「この図、信じていいんですか」という質問自体が、ほとんど消えました。入力ドキュメントがすなわち図なので、ドキュメントが正しければ図も正しいのです。
逆に正直に書いておくと、自動化は万能ではありません。構造化が進んでいない初期段階のアイデアスケッチは、依然としてホワイトボードのほうが速いです。自動化は、構造がある程度固まってから輝きます。
setup. ドキュメントを保管するMarkdownリポジトリ1つと、Mermaidをレンダリングするビューア(大半のMarkdownビューア・gitホスティングに内蔵)があれば十分です。変換対象のドキュメントから「関係・フロー・シーケンス」に当たる断片を1つ選んでください(例:システム依存メモ)。
prompt. その断片を、本文の背骨のステップ1のプロンプトの型にはめてLLMに投げてみましょう。必ず「メモにないノードを追加するな」「IDは原文の英語名のまま使え」という2つのルールを入れます。構造化データなら、LLMの代わりにdecision_graph_to_mermaid.pyのような決定論的スクリプトで変換してください。
verify. 出力のコードブロックをMarkdownに貼り付けて、実際にレンダリングしてみましょう。3つを確認します。(1)入力にないノードが生まれていないか、(2)エッジラベルが壊れずに描かれるか、(3)本文で使うIDとダイアグラムのIDが一致するか。1つでも食い違ったら、再依頼プロンプトで拒否してもう一度受け取ってください。通過したらgitにコミットします — これで変更はdiffで追跡されます。
チームもスクリプトもない一人の作業者なら、こう縮めましょう。ノートアプリに、システム・タスク・アイデアの間の関係を「AはBに依存する」形式の箇条書きで書いておきます。週に1回、そのリストを丸ごとコピーして「これをMermaid graph TBに変えて。リストにないノードは追加しないで」と一言投げてください。返ってきたコードブロックをノートの一番上に貼り付けます。それで終わりです。手で描かないので更新の負担がなく、入力リストさえ生きていれば、図は常に最新です。
リンク(wikilink)と分類(階層)は、同じ問題の二つの入り口です。一方が「この決定がどこへつながるのか」に答え、もう一方が「この文書がどこに住んでいるのか」に答えます。
新しく合流したプランナーが、2日目の朝に尋ねました。「戦闘のグローバルクールダウン(GCD)の値は0.5秒で合っていますか? どの文書に根拠があるのでしょうか?」私は答えられませんでした。確かにどこかに決定の記録があるはずなのに、それが戦闘ルールブックなのか議事録なのか四半期レポートなのか、思い出せなかったのです。3人がかりでフォルダ全体をgrepで探し回りました。同じ数字が6か所から出てきて、そのうちどれが「原本の決定」でどれが「参照のコピー」なのか区別がつきませんでした。40分を費やしました。最終的に見つけたのは、議事録の中に埋もれていた1行でした。
その日の夜、私は二つのものが欠けていたことに気づきました。第一に、文書同士の明示的なリンクがありませんでした。同じ数字が6か所にあっても、「これはあそこから引用したものだ」という紐がどこにも書かれていなかったのです。第二に、文書が住む階層がありませんでした。決定の記録がルールブック・議事録・レポートに散らばったまま、「決定はここに住む」という約束がなかったのです。
この二つが本章のテーマです。wikilinkはリンクをテキストとして書き込み、階層は分類をフォルダとして約束します。二つは別々の技法のように見えますが、実は検索という一つの問題の両面です。
文書が30件のうちは頭で全部記憶できます。100件を超えると、人の記憶はインデックスの役割を果たせなくなります。そのとき頼れるのは二つに一つです。全体をgrepで洗うか(遅くて不正確)、文書の中に書かれた明示的なリンクをたどるか(速くて正確)です。
grepが不正確な理由は単純です。combat_global_cooldown_constantという文字列を検索すると、その値を決定した文書と、その値に言及しただけの文書が同じように引っかかります。どちらが原本なのか、grepにはわかりません。一方、文書の中に[[combat_global_cooldown_constant]]という二重角括弧の表記を約束しておけば、「これはそのatomを意図的に参照している」というシグナルが文字列そのものに残ります。\[\[combat_global_cooldownというパターンで絞り込めば、偶然の言及は外れ、意図された参照だけが残ります。
この1行の表記の約束が、グラフの一つの辺(edge)になります。文書Aが[[atom_X]]と書けば、A→X方向のエッジが生まれます。200件の文書がそれぞれ数個ずつ書けば、誰が描かなくてもグラフがテキストの中に蓄積されていきます。
以下は、私たちのプロジェクトのatom・決定・文書がwikilinkで結ばれた様子の一断片です。ノードの色は種類を、矢印は参照の方向を表します。
この小さな断片が示しているのは、合流したばかりのプランナーの質問への答えが、グラフの中にすでにあったという事実です。combat_global_cooldown_constantというatomに入ってくる矢印を逆にたどれば、決定D2026_Q2_017にたどり着きます。40分ではなく、一度の逆参照でした。
私たちはwikilinkで結ぶ対象を4種類だけに決めました。種類を増やすと様式が揺らぎ、様式が揺らぐとgrepが再び不正確になります。
[[combat_global_cooldown_constant]]。1文書1決定の単位であるatomを指します。[[D2026_Q2_017]]。四半期・番号で識別される意思決定の記録です。[[CombatFormula_v3]]。ルールブック・仕様書などの大きな文書です。[[チームメンバーA]]。担当者・決定者です。4種類すべてが[[name]]という一つの様式です。nameはグローバルに一意でなければなりません。atom名が2か所で衝突するとグラフの同じノードに統合されてしまい、「戦闘のcooldown」と「UIのcooldown」が一つのノードになる事故が起きます。そのため、atomの命名規則で分野プレフィックス(combat_、ui_)を強制しています。
表記の約束だけでは足りません。200件の文書に人が一つひとつ角括弧を付けるのは非現実的ですし、一度付けてもatom名が変わればすべて壊れます。そこで、二つの仕事をするスクリプトを運用しています。第一は適用(apply)— 本文に登場する既知のatom名をwikilinkに自動変換すること。第二は修復(heal)— 名前が変わったり壊れたりしたリンクを見つけて更新・報告することです。
wikilink_apply.pyの核心部はこうなっています。
# wikilink_apply.py — 本文のatom名を[[wikilink]]として適用し、壊れたリンクを修復する
import re
from pathlib import Path
WIKILINK = re.compile(r"\[\[([A-Za-z0-9_]+)\]\]")
# すでにリンクになっておらず、裸で登場するatom名だけを捕捉する(前に [[ がない場合)
BARE_NAME = lambda name: re.compile(rf"(?<!\[\[)(?<![A-Za-z0-9_])({re.escape(name)})(?![A-Za-z0-9_])(?!\]\])")
def load_known_atoms(registry: Path) -> set[str]:
# _atom_registry.tsv: 先頭カラムが現在有効なatom name
return {ln.split("\t")[0].strip()
for ln in registry.read_text(encoding="utf-8").splitlines()
if ln.strip() and not ln.startswith("#")}
def apply_links(text: str, known: set[str]) -> tuple[str, int]:
applied = 0
for name in sorted(known, key=len, reverse=True): # 長い名前を優先: 部分一致による汚染を防止
text, n = BARE_NAME(name).subn(rf"[[{name}]]", text)
applied += n
return text, applied
def heal_links(text: str, known: set[str], aliases: dict[str, str]) -> tuple[str, list[str]]:
dead = []
def repl(m):
ref = m.group(1)
if ref in known:
return m.group(0) # 生きている → そのまま
if ref in aliases: # 改名されたatom → 新しい名前に修復
return f"[[{aliases[ref]}]]"
dead.append(ref) # 本物のdead link → 報告
return m.group(0)
return WIKILINK.sub(repl, text), dead
ここで、二つの設計上の選択が本文の背骨です。
第一に、apply_linksは長い名前を先に置換します。combat_cooldownとcombat_cooldown_globalという二つのatomがあるとき、短いほうを先に置換すると長いほうの前半部分が汚染されます。長さの降順ソートという1行が、この事故を防ぎます。これは最初に書いたとき私が抜かしていた部分で、実際に[[combat_cooldown]]_globalという壊れた結果が出てから追加しました。
第二に、heal_linksは改名辞書(aliases)を経由して修復します。atom名がcombat_gcd → combat_global_cooldown_constantに変わったら、古い名前を新しい名前に自動で置き換え、辞書にもなければそこで初めてdead linkとして報告します。名前が変わるたびに200件を手で直す代わりに、aliasを1行追加するだけです。
自動applyは「すでに知られているatom名」だけをリンクします。しかし、本文にatom名を使わずに言い換えて書かれた文(「戦闘のグローバルクールダウンは0.5秒だ」)は捕まえられません。こうした自然言語の参照をwikilink候補に変えるのは、LLMのほうが人より速いのです。以下は実際にやり取りした全文です。出力は要約しておらず、私が拒否した部分もそのまま残しています。
私のプロンプト(全文):
戦闘ルールブックの一段落と、登録済みのatom一覧を渡します。本文の中でこれらの
atomを指している自然言語表現を見つけて、[[wikilink]]の候補を表で提案してください。
確実なときだけ提案し、曖昧な場合は「曖昧」とマークするだけにしてください。本文は
書き直さず、どの語句をどのリンクに変えるかだけ示してください。登録されていない
概念は「新規atom候補」として別に分けてください。
[登録済みatom]
combat_global_cooldown_constant
combat_skill_cast_interrupt_rule
combat_resource_regen_rate
[本文]
戦闘のグローバルクールダウンは0.5秒であり、すべてのアクティブスキルに共通して適用される。
詠唱中に被弾すると詠唱が中断される。ただし、回復系スキルは例外である。
リソースの再生は非戦闘状態でのみ通常速度に戻る。
Claudeの出力(生の出力そのまま):
提案するwikilink:
| 本文の語句 | 候補リンク | 確信 |
|--------------------------------------------|----------------------------------------|------|
| 「戦闘のグローバルクールダウンは0.5秒」 | [[combat_global_cooldown_constant]] | 高 |
| 「詠唱中に被弾すると詠唱が中断される」 | [[combat_skill_cast_interrupt_rule]] | 高 |
| 「リソースの再生は非戦闘状態でのみ」 | [[combat_resource_regen_rate]] | 中 |
新規atom候補:
- 「回復系スキルは例外である」 → この例外ルールは、登録済みのatomのどこにも
明記されていません。combat_skill_cast_interrupt_heal_exception のような
別個のatomが必要に見えます。(曖昧: interrupt_ruleの下位条項かもしれない
ため、単独のatomかどうかは判断できません)
注意: 「リソースの再生」の行は、combat_resource_regen_rate が「再生速度」を
指すのか「非戦闘条件」を指すのか、atomの定義を見ていないため確信は中です。
私の検証と拒否:
3行のうち2行は受け入れました。グローバルクールダウンと詠唱中断は、atomの定義と正確に一致していました。3行目の「リソースの再生」の行は、Claudeが自ら確信を「中」に下げたのが正しかったのです — combat_resource_regen_rateというatomは速度の値だけを定義していて、「非戦闘状態でのみ」という条件は別のatomの管轄でした。そのままリンクを張れば、「条件」を「速度」のatomに誤って接続する事故になります。拒否しました。
新規atom候補の指摘は正確でした。「回復系の例外」は本当にどこにもatomがありませんでした。ただし、Claudeが曖昧だとした部分(「interrupt_ruleの下位条項なのか、単独のatomなのか」)は人が判断すべき領域であり、私は単独のatomとして分離することにしました。
再依頼:
「リソースの再生」の行はリンクしないでください。代わりに、[[combat_resource_regen_rate]] は
「速度」のみとし、「非戦闘条件」は新規atomとして分離します。二つのatomの1行定義を
それぞれ書いてください。回復例外も単独のatomとして1行で定義してください。
この往復でLLMがやったのは「ゼロから候補を作ること」ではなく「候補を選り分けてくれること」でした。核心は、人が拒否できる場所が明確にあったということです。自動発行だったなら、間違ったリンクが一つ、グラフに永久に残っていたでしょう。
リンクは時間が経つと壊れます。atomが廃棄され、名前が変わり、誤字が入り込みます。そこで、ビルドのたびにwikilinkのlintを回します。検査項目と処理はこうです。
[[name]]のnameがレジストリにない → ビルド警告、修復辞書を確認dead linkだけをブロックではなく警告にしているのは意図的です。atomを改名する途中の状態では一時的にdeadが生まれますが、これをビルド失敗で止めると作業が止まってしまいます。代わりに、まず修復辞書を確認させます。様式違反と名前の衝突は即時ブロックします — この二つはグラフ全体を汚染するからです。
このlintは自己証明的です。wikilink_apply.pyが作ったリンクを同じシステムのlintが検査し、その結果をまた別のatom決定として残します。道具が自分の産出物を自分の基準で検証するループが、運用の基本骨格です。
ここまでがリンクです。次は分類です。wikilinkが「この決定がどこへつながるのか」に答えるなら、階層は「この文書がどこに住んでいるのか」に答えます。どちらもなければ、合流したばかりのプランナーの40分検索が繰り返されます。
私たちの文書フォルダは4階層です。この階層は、情報アーキテクチャのLayer統合と同じ骨格を共有しています — ビジョン・システム・コンテンツ・メタがそれぞれ一つの層です。
docs/
├── L0_vision/ ビジョン(5件以下、ほとんど変わらない)
├── L1_systems/ 分野別ルールブック
│ ├── combat/
│ ├── narrative/
│ └── ui/
├── L2_content/ 個別コンテンツ
│ ├── characters/
│ └── quests/
└── L4_meta/ 運営・決定・会議・atom
├── decisions/
├── meetings/
├── reports/
└── atoms/
L3が空いているのは、マスターデータ・DBがその場所を占めるからです(文書ではなくテーブル)。合流したばかりのプランナーが探していた決定はL4_meta/decisions/に住んでいます — この約束が一つあるだけでも、40分の検索は「決定はそこにある」という一文で終わっていたはずです。
階層が検索の入り口として機能するには、五つのことが同時に守られる必要があります。どれか一つが欠けても分類は崩れます。
combat/・narrative/は検索できますが、2026-Q1/・2026-Q2/は6か月後には誰も開きません。時間はgitが記録するので、フォルダでさらに分ける理由がありません。L1_systems/combat/skills/active/single_target/attack.mdは5段階です。パスが一画面を超えると、人は位置を頭に収められません。spec_・report_・decision_・char_で種類をファイル名に入れます。フォルダを見なくても種類がわかります。_プレフィックスのメタフォルダ。 _archive/・_TEMPLATES/・_NAMING/は自動ソートで上に上がり、本来のコンテンツと混ざりません。文書は一つの場所にとどまりません。作成中は本来のフォルダにstatus: draftとして住み、有効化されるとstatus: activeになり、廃棄されるときは削除ではなく_archive/へ移してstatus: deprecatedを付けます。廃棄資料を削除しないのは鉄則です。6か月後に誰かが「あの決定、なぜ覆したんだっけ?」と尋ねるとき、答えは廃棄資料の中にしかありません。削除してしまえば、決定の根拠を後から取り戻す方法はないのです。
大きな変更はgitだけに任せず、frontmatterにchange_logとして残します。
---
title: combat_global_cooldown_rule
version: v3
last_modifier: teammate_a
change_log:
- v1 (2025): 草案
- v2 (2025): cooldown 0.3 → 0.5 ([[D2026_Q2_017]])
- v3 (2026): 回復例外を追加 ([[D2026_Q2_018]])
---
change_logの決定IDがwikilinkで書かれている点に注目してください。ここでリンクと分類が出会います。文書は階層の中の一つの場所に住みますが(分類)、その変更履歴は決定のグラフへとつながります(リンク)。一つのfrontmatterが、二つの入り口を同時に開くのです。
階層は放っておくと腐ります。空のフォルダが生まれ、6か月放置されたdraftが溜まり、深さがじわじわ増えていきます。そこで四半期に一度、整理します。空のフォルダは削除し、6か月を超えたdraftは有効化/廃棄を決定し、深さ5以上はフラット化し、READMEのないフォルダは作成するか廃棄し、_archiveが半分を超えたら圧縮して保存します。このサイクルがないと、階層がノイズで埋まって、シグナルとノイズの区別が消えてしまいます。
全体の流れを一枚の図で見るとこうです。文書が入ってきて、リンクされ、分類され、検証され、廃棄されるまでが一つのループです。
flowchart TD
A[新しい文書の作成
status: draft] --> B[wikilink_apply.py
atom名の自動リンク]
B --> C[LLMによる補強
自然言語参照の候補]
C --> D{人によるレビュー}
D -->|採用| E[階層への配置
L0~L4 + prefix]
D -->|拒否| C
E --> F[ビルドlint
dead/衝突/循環の検査]
F -->|通過| G[status: active]
F -->|dead link| H[修復辞書の確認]
H --> F
G --> I[四半期の整理サイクル]
I -->|廃棄| J[_archive/
status: deprecated]
I -->|維持| G
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d;
class B,F,H code;
class C ai;
class D,I human;
class A data;
class G pass;
class J fail;
このループでは、リンク(B・C・D・F)と分類(E・I・J)が交互に作動します。二つは別々に回るのではなく、一つの文書の生涯の中で噛み合っています。
数値は、著者のプロジェクトで導入前後を比較した方向性です。精密な測定値ではなく、同じ作業を二つの環境で行ったときに体感した差の大きさです(著者の観察であり、精密な計測ではありません)。
リンクと階層が定着する前は、合流したばかりのプランナーの決定追跡の質問には、冒頭の40分の事例のように、長ければ1〜2時間かかりました。導入後はatomの逆参照一回 — 分単位です。文書の検索は5〜10分から30秒前後に縮みましたが、これは階層の意味順分類とプレフィックスが一緒に機能した結果です。誤った引用による事故(すでに廃棄された値を現行と勘違いする類)は、四半期あたり数件から1〜2件に減りました — wikilinkが「これはそのatomを参照している」と明示するので、コピーされた値と原本の値がもう紛らわしくなくなったのです。
最も大きかった変化は新しく合流したメンバーの適応です。階層なしではどのフォルダに何があるのかを覚えるのに数日かかり、リンクなしではシステム同士がどう絡み合っているのかを把握する手立てがありませんでした。二つが揃ってからは、フォルダのREADMEで位置を覚え、wikilinkのグラフをたどってシステム間の関係を自分で探索するようになりました。「聞かなければわからないこと」が「たどれば見えること」に変わったのです。
この効果は、二つの入り口が一緒にあるときにだけ生まれます。リンクだけあって分類がなければ、グラフはあるのに文書がどこに住んでいるのかわからず、分類だけあってリンクがなければ、フォルダはきれいなのに決定がどこへつながるのかわかりません。
リンク側で最もよくある失敗はノイズリンクです。wikilinkが良いからといってすべての名詞に角括弧を付けると、グラフが意味のないエッジで埋まり、可視化ツールが無力になります。「この文書はあの文書とどんな関係なのか」を問い、答えられるリンクだけを残すのが原則です。次は自動発行 — LLMが作ったリンクを人のレビューなしにコミットすると、ワークド・トランスクリプトの「リソースの再生」の行のような誤った接続が永久に残ります。applyは自動、発行は人です。
分類側の失敗は、ほとんどが五つの原則の違反です。時系列順フォルダ、深さ5以上、ファイル名の無規則、READMEの不在。そして最も取り返しがつかないもの — 廃棄資料の削除です。削除された決定の根拠は二度と作れません。_archiveへ送る1行が、6か月後の学習資料を守ります。
_archiveに保存してこそ、決定の根拠が後まで生き残ります。setup. 文書フォルダにL0_vision/ L1_systems/ L2_content/ L4_meta/の四つのフォルダを作り、各フォルダに1行のREADMEを置きましょう。atom名の一覧を_atom_registry.tsvという1ファイルに集めます(先頭カラム = atom name)。
prompt. 本文の一段落と登録済みのatom一覧をLLMに渡して、こう依頼してみましょう — 「本文の中でこれらのatomを指す自然言語表現を見つけ、[[wikilink]]の候補を表で提案してください。確実なときだけ提案し、曖昧な場合は『曖昧』とマークするだけにしてください。本文は書き直さないこと。登録されていない概念は『新規atom候補』として分離してください」。
verify. 提案されたリンクごとに、atomの定義と突き合わせましょう。atomが指す対象と本文が指す対象が正確に同じときだけ採用し、条件/属性/例外がずれていれば拒否します。採用後はgrep "\[\[name\]\]"で、リンクが実際に入力されたか、dead linkがないかを確認しましょう。
一人ミニ版. スクリプトもlintもなしで始めるなら、ルールは2行で足ります。(1) 決定は必ずdecisions/という一つのフォルダにdecision_*.mdとして置きます。(2) 他の文書がその決定に言及するときは[[decision_id]]と書きます。この2行を守るだけでも、「あの決定はどこにありますか?」という質問にgrep "\[\[decision_"一回で答えられます。ツールは、文書が100件を超えてから導入しても遅くありません。
資料を疑う瞬間は、いつも遅すぎるタイミングで訪れます。ライブビルドに誤った数値が入力された後になって初めて、「これはどこから出てきたのか」と問うことになります。
アルファビルド直前の金曜の夜、チームメンバーBが私の席にやって来ました。手には戦闘バランスのスプレッドシートを開いたノートPC。「ディレクター、ボスの第1フェーズHPがシートでは48,000なのに、ビルドに入っている値は52,000なんです。どちらが正しいですか?」
私には分かりません。正確に言えば — その場では誰にも分からないのです。シートの52,000が数日前の会議の決定を反映した最新値かもしれませんし、誰かが検証されていない値を一時的に入れておいたものかもしれません。48,000はその会議より前の合意値かもしれません。どちらの数字も、それらしく見えます。もっともらしさは根拠ではありません。
この質問に答えるには、出所までさかのぼる必要があります。どの会議で決定されたのか、その会議の入力は何だったのか、誰がシートに転記したのか。ところが、その追跡の鎖が人の記憶の中にしかなければ、答えは「明日チームメンバーAに聞いてみます」になります。運営(ライブオプス)6か月目には、そうした未解決の質問が山のように積み上がります。data lineage — 資料の系譜 — は、その山ができないようにするインフラです。
核心は一つです。出所は手で書いてはいけません。人が事後に補強する出所の記録は1か月も持ちません。資料が作られるその瞬間に自動で記録される出所だけが生き残ります。
_source_map.tsvの1行を自動で記録するコストは数ミリ秒です。その1行がないときに支払うコストは、5つの方向へ広がります。
5つのコストのどれ一つとして、資料を作ったその瞬間には見えない — そこが落とし穴です。すべて数週間後、数か月後、担当者が入れ替わった後に請求書が届きます。だから出所は「後で整理しよう」の対象にはなり得ません。作る瞬間に記録されなければならないのです。
プロジェクトAで運用している出所マッピングファイルは_source_map.tsvの一つだけです。タブ区切りテキストである理由は単純です。人が1行を目で読むことができ、スクリプトはsplit('\t')一回でパースでき、git diffが1行の変更をきれいに見せてくれるからです。CSVは本文にカンマが混ざると壊れますし、JSONは1行を人が読みにくいのです。
asset_id source_type source created creator notes
spec_combat_v3 internal mtg_battle_2026-04-18 2026-04-18 teammate_a decision_D2026_Q2_017が根拠
data_boss_hp_v3 internal decision_D2026_Q2_017 2026-04-18 teammate_b フェーズ1 48000確定
asset_K_001_concept internal_ai_assisted imagegen + teammate_b 整備 2026-04-20 teammate_b legal_review完了
data_user_voice_W21 external_aggregated forum + community + sns 2026-05-25 auto_collect 13.1パイプライン産出
ref_visual_tone_a external_reference refgame (2024) 2026-04-15 teammate_c ビジュアルトーンの参考、直接の流用なし
6つのカラムの役割は明確です。asset_idは資料の固有キー、source_typeは分類(後述します)、sourceは出所の位置 — 会議ID・決定ID・収集パイプライン・外部作品名、created/creatorはいつ・誰が、notesは人が読むための1行の文脈です。
ここで2行目と3行目をもう一度見ると、前の節のチームメンバーBの質問への答えが見えてきます。data_boss_hp_v3の出所はdecision_D2026_Q2_017で、notesには「フェーズ1 48000確定」と入力されています。ビルドの52,000はこのlineageに存在しません。つまり52,000は検証されていない一時的な値で、正解は48,000です。質問は1〜2分で閉じられます。人の記憶を呼び出すことなく、金曜の夜を台無しにすることもなく。
ただし、このファイルにはもう一つルールが掛かっています。_source_map.tsvを人が手で編集すると、integrity_checkのauditがFAILを出します。理由は次の節で扱います — 出所は自動でのみ記録されるべきだからです。
出所を5種類に分類する理由は、整理癖ではありません。source_typeごとに付いて回る運用ルールが違うからです。
external_referenceの行を見てみましょう。refgameをビジュアルトーンの参考として見たアセットなら、このアセットは法務レビューなしにビルドへ入ってはいけません。source_typeがexternal_referenceなのにlegal_reviewの記録が空なら、auditが止めます。ラベルがラベルのままで終わらず、チェッカーが読むスイッチになる地点です。5種分類が運用の信頼の骨格だという言葉は、この強制力を指しています。
ここからが核心です。出所は資料の生成時点で自動的に記録されなければなりません。プロジェクトAのsource_tracker.pyはアセット生成のフックに掛かっています。
# source_tracker.py
import time, getpass, csv
from pathlib import Path
SOURCE_MAP = Path("_source_map.tsv")
VALID_TYPES = {
"internal", "internal_ai_assisted",
"external_aggregated", "external_reference", "self_measured",
}
def track_source(asset_id: str, source_type: str, source: str, notes: str = ""):
if source_type not in VALID_TYPES:
raise ValueError(f"unknown source_type: {source_type}")
if source_type == "external_reference" and "legal_review" not in notes:
raise ValueError(f"{asset_id}: external_referenceアセットはlegal_review表記が必須")
record = [
asset_id,
source_type,
source,
time.strftime("%Y-%m-%d"),
getpass.getuser(),
notes,
]
with SOURCE_MAP.open("a", encoding="utf-8", newline="") as f:
csv.writer(f, delimiter="\t").writerow(record)
この関数がアセット生成パイプラインに掛かっていれば — シートがexportされるとき、コンセプトアセットが登録されるとき、ユーザーデータが集計されるとき — 出所の1行が自動でappendされます。人が忘れうる手順がありません。事後補強の負担が0に近づきます。
creatorカラムをgetpass.getuser()で自動的に埋める点が、小さいながらも決定的です。人に自分の名前を書かせると空欄が生まれます。システムが書けば空欄はありません。そしてexternal_referenceにlegal_reviewがなければ、記録そのものが拒否されます — 出所のない外部アセットがシステムに入り込む通路を塞いだのです。
ここでAI活用の実際の姿を押さえておきます。私はこのtrack_source関数の草案をClaudeから受け取りました。次に示すのは、そのワークド・トランスクリプトの背骨にあたる部分です。
私のプロンプト (全文):
_source_map.tsvに出所の1行をappendするPython関数を書いて。
カラム: asset_id, source_type, source, created(今日の日付),
creator(現在のOSユーザー), notes。タブ区切り。
source_typeは5種に制限: internal, internal_ai_assisted,
external_aggregated, external_reference, self_measured.
Claudeの生の出力 (要約なし):
import csv, datetime, os
def track_source(asset_id, source_type, source, notes=""):
record = [asset_id, source_type, source,
datetime.date.today().isoformat(),
os.getlogin(), # ← 現在のユーザー
notes]
with open("_source_map.tsv", "a", newline="") as f:
csv.writer(f, delimiter="\t").writerow(record)
source_typeの検証を追加したい場合は、VALID_TYPESの集合を作って 関数の冒頭でチェックすれば対応できます。必要でしたら追加しましょうか?
私の検証と拒否:
この出力は動きはするものの、2か所で私たちの運用に合いませんでした。第一に、os.getlogin()はcronやCIから呼ばれると、環境によっては空文字列を返したり例外を投げたりします。私たちのexportパイプラインは無人スケジュールでも回ります。そこでgetpass.getuser()に替えました — 環境変数を見て、より安定的にユーザーを取得します。第二に、Claudeはsource_typeの検証を「必要でしたら追加しましょうか」という選択肢のまま残しましたが、私たちにとってそれは選択ではなく必須です。検証がなければ、タイポの混じったsource_typeが入り込み、分類が崩れます。
私の再リクエスト:
getpass.getuser()に変えて。それからsource_typeの検証は任意ではなく
必須として関数の中に組み込んで。さらにexternal_referenceタイプなのに
notesにlegal_reviewという文字列がなければValueErrorを投げるようにして。
法務レビューのない外部アセットが記録されるのを根本から遮断したい。
この再リクエストの結果が、上に載せた最終版のsource_tracker.pyです。押さえておきたいのは、Claudeの最初の出力が間違っていたからではなく、AIが知らない運用上の制約 — 無人スケジュール、legal_reviewの強制 — を私が知っているからこそ、拒否と再リクエストが必要だったという点です。AIは一般的に正しいコードを素早く出し、人は「私たちの環境で正しいか」を検証します。その検証の地点が、そのまま出所追跡システムの設計上の決定になります。
_source_map.tsvを人が手で編集するとintegrity_checkがFAILを出す、と先に述べました。どうやって捕まえるのでしょうか。
原理は単純です。track_sourceが1行をappendするたびに、その行の核心カラム(asset_id, source_type, source, created, creator)をシリアライズしてハッシュを作り、別の.source_map.auditファイルに蓄積します。auditチェックは_source_map.tsvを読み直して同じ方式でハッシュを再計算し、二つのハッシュのリストを照合します。
# integrity_check内のsource_map audit部分
def audit_source_map():
fails = []
rows = read_tsv(SOURCE_MAP)
expected = read_lines(AUDIT_FILE) # append時に蓄積されたハッシュ
for i, row in enumerate(rows):
h = row_hash(row["asset_id"], row["source_type"],
row["source"], row["created"], row["creator"])
if i >= len(expected) or h != expected[i]:
fails.append(f"L{i+1} {row['asset_id']}: 手動編集の疑い (ハッシュ不一致)")
if len(rows) != len(expected):
fails.append(f"行数不一致: tsv={len(rows)} audit={len(expected)}")
return fails
人がシートでdata_boss_hp_v3のsourceを手でdecision_D2026_Q2_099に変えたとしましょう。その行のハッシュがauditに蓄積された元のハッシュと食い違い、チェックは次のように出力します。
[FAIL] source_map audit
L3 data_boss_hp_v3: 手動編集の疑い (ハッシュ不一致)
→ track_source()を経ていない変更。出所はコード経路でのみ記録すること。
この強制がなぜ重要なのでしょうか。手編集を許せば、結局は誰かが急いでいるときに出所を「もっともらしく」埋めてしまいます。その瞬間、lineageは真実ではなく、誰かの推測を収めたファイルへと成り下がります。audit FAILは「出所は自動経路でのみ」というルールに歯を与えます。§24.1のverificationシステムが、このauditをほかのチェックと一緒に束ねてCIで回します。
出所を自動で記録する本当の理由は、逆方向のクエリにあります。「原本Xが変わった。何が影響を受けるのか?」
def find_derivatives(source_id: str):
return [
row for row in read_tsv(SOURCE_MAP)
if row["source"] == source_id
]
# 使用例: decision_D2026_Q2_017が会議で覆された
deps = find_derivatives("decision_D2026_Q2_017")
# → [spec_combat_v3, data_boss_hp_v3, ...]
decision_D2026_Q2_017が次の会議で覆され、ボスの第1フェーズHPが48,000から50,000に変わったとしましょう。find_derivativesを呼べば、この決定にぶら下がるすべての派生アセットが即座に出てきます — 戦闘仕様書、HPのマスターデータ。各アセットの担当者に通知が飛び、「古い決定を見ているアセット」がビルドに残る事故は、四半期あたり数件からほぼ0に減ります。
手で書いた出所では、この逆方向のクエリは成立しません。出所が自由テキストだと、decision_D2026_Q2_017がある行では「Q2 017決定」、別の行では「第2四半期17番会議の決定」と書かれ、マッチングが壊れます。_source_map.tsvの標準形式とtrack_sourceの自動記録があって初めて、変更の伝播が機能するのです。
_source_map.tsvは1行ずつ見れば平面的ですが、sourceが別のアセットのsourceになることで、資料の系譜が鎖をなします。その鎖を1画面に広げると、決定の入力の信頼度が目に入ってきます。このmermaidは§24.2のダイアグラム自動生成パイプラインが_source_map.tsvを読んで直接生成します — 自らの技法で自らのアセットを証明している格好です。
graph LR
Meeting["mtg_battle_2026-04-18
(会議)"] --> Proposal["P2026_Q2_017
(提案)"]
Proposal --> Decision["D2026_Q2_017
(決定・48000確定)"]
Decision --> Spec["spec_combat_v3
(戦闘仕様)"]
Decision --> Data["data_boss_hp_v3
(HPマスターデータ)"]
Data --> Build["build_2026-05-20
(アルファビルド)"]
Build --> UserData["data_user_voice_W21
(ユーザー測定)"]
UserData -.次の決定の入力.-> Decision
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class Meeting,Decision human;
class Proposal,Spec,Data,Build,UserData data;
循環が自然に生まれます。ビルドがユーザーデータを生み、ユーザーデータが次の決定の入力になります。この循環が見えれば、「この数値はどこから来たのか」は画面上の経路になります。チームメンバーBの金曜の質問も、このグラフではData → Decisionを一度さかのぼるだけのことです。
プロジェクトAでlineageシステムの導入前後を比較しました。以下の時間の数値は著者の推定(未検証)であり、絶対値よりも方向と比率の差を見るべきものです。件数は四半期のauditログから集計した実測です。
| 項目 | lineageなし | lineage運用 | 性格 |
|---|---|---|---|
| 資料の出所把握時間 | 1〜2時間 | 1〜2分 | 著者の推定(未検証) |
| 資料の信頼度検証の根拠 | シニアの記憶 | 出所を即時照会 | 定性 |
| 原本変更時の派生漏れ | 四半期5〜8件 | 0〜1件 | auditログ実測 |
| 外部アセットの法務レビュー漏れ | 発生し得る | 0件(記録強制) | auditログ実測 |
| 四半期auditの所要 | 1〜2日 | 2〜3時間 | 著者の推定(未検証) |
もっとも固い数字は「原本変更時の派生漏れ」の行です。これはauditログに決定IDと漏れた派生アセットがそのまま残るため、数えることができます。時間の数値は測定環境(チーム規模・アセット数)に大きく左右されるため、推定と明記しました。方向ははっきりしています — 出所が自動で記録されれば、追跡は記憶から照会へと変わります。
| 失敗パターン | 処方 |
|---|---|
| 出所を事後に手で埋める | track_sourceで生成時点に自動記録 |
| 出所の形式が行ごとに違う | _source_map.tsvのタブ標準+形式の強制 |
| 外部アセットの法務レビュー漏れ | source_type検証でlegal_reviewを強制 |
_source_map.tsvの手編集 |
integrity_checkのauditでハッシュ照合FAIL |
| 原本変更時に派生を放置 | find_derivativesの逆方向クエリ+通知 |
| 系譜を文章だけで説明 | mermaid自動生成で1画面に可視化 |
6つの処方に共通するのは、人の誠実さに依存しないという点です。自動記録・形式の強制・ハッシュ照合・逆方向クエリは、すべてシステムが行います。出所追跡が崩れる唯一の理由が「人はうっかり忘れる」だからです。
第24部は、運用の信頼を自動化で支える4つの道筋でした。第1章のverificationで検証を一点に集め、第2章のmermaid自動生成で構造を描き、第3章のwikilinkとdocument hierarchyで文書をつなぎ階層化し、最後にこの第4章で、出所と系譜によって資料の信頼を封印しました。
4つの章を貫く一文はこうです。運用の信頼は、人の記憶ではなくシステムの記録から生まれます。 verificationが「この成果物はルールに合っているか」を自動で問うように、lineageは「この資料はどこから来たのか」に自動で答えます。どちらも、人が忘れても崩れないという点が核心です。
この運用ノウハウは、本書全体を貫くLayer統合の哲学と同じ筋を成しています。ビジョン(資産化・信頼)がシステム(出所ルール)へ、システムがデータ(_source_map.tsv)へ、データがビルド・QA(audit・自動更新)へと降りていく1本の鎖です。その鎖そのものがlineageです。
_source_map.tsvの標準とsource_type 5種が、変更の伝播と法務強制の骨格です。setup. プロジェクトのルートに_source_map.tsvをヘッダー1行(asset_id\tsource_type\tsource\tcreated\tcreator\tnotes)で作り、上のsource_tracker.pyを置きましょう。アセットのexport・登録スクリプトの最後にtrack_source(...)の呼び出しを掛けましょう。
prompt. 出所の自動記録関数が必要なら、Claudeにこのように依頼してみましょう。
_source_map.tsv(タブ区切り、カラム: asset_id, source_type, source,
created, creator, notes)に1行をappendするPython関数を書いて。
source_typeは5種に制限し、external_referenceなのにnotesに
legal_reviewがなければValueErrorを投げて。creatorはgetpass.getuser()で。
verify. 二つのことを直接確認しましょう。(1) external_referenceで呼び出しつつnotesを空にしてみて、ValueErrorが出るか。(2) _source_map.tsvのsourceカラムをテキストエディターで1文字変えた後、integrity_checkのsource_map auditを回してFAILが出るか。両方とも止まれば、出所の経路は閉じられています。
一人で作業しているなら、_source_map.tsvという1ファイルとtrack_sourceという1関数があれば十分です。integrity audit・逆方向クエリ・mermaid自動化は、アセットが数十個を超えて出所が混乱し始めたときに一つずつ足していけばよいのです。始まりは「数値を書くときに、出所の1行を同じ場所に自動で残す」 — その習慣一つです。
この本が始まった場所は、LLMの質問ウィンドウ一つでした。「企画書をちょっと整理して」という一行から始まり、AIはゲームデザインルーム全体へと入ってきました。いまこの場所は、その変化の終わりではなく、一つの段階の区切りです。
24年をゲームプランナーとして過ごしてきた立場から見て、確かに変わったものがあります。量産はツールに吸収され、議事録は資産になり、決定は追跡できるようになりました。
ただし、すべての分野に同じ速度で変化が届いたわけではありません。システム企画とバランスにはツールへの吸収が速いペースで入ってきましたが、ナラティブとアートディレクションはまだ保守的適用の段階にとどまっている場合が少なくありません。どの分野がより速いかは本質ではありません。分野ごとに変化が入ってくる場所が違う、という事実を認めるところから、次の決定が始まります。
プランナーの仕事が減ったという意味ではありません。同じ時間で別の仕事をするようになった、という意味です。量産から意味へ、整理から決定へと移っていったわけです。ツールが量産を吸収した場所に、意味のある仕事がすぐ自動で満ちてくるわけではないため、まず何をしないかから決め直さなければならない、ぎこちない時期がしばらく続きます。
変わらないものもあります。ゲームは人のためのものであり、ゲームのビジョンは人が決め、ユーザーの時間を尊重するゲームをつくるという約束は、そのままです。
ツールはツールであり、方向は人です。ただ、私はこの一行を毎年点検し直すつもりです。点検しなければ、ツールが十分に強くなった場所に「それでも決定は人がする」と漠然と信じるデフォルトが、いつの間にか敷かれてしまうからです。「方向は人」という言葉が安全な約束のように聞こえる瞬間こそ、実はもっとも危険なのです。
この本は、ある時点の記録です。AIツールは速く進化しており、1年後にはこの本の一部が古びている可能性が高いでしょう。しかし核心となるパターン — Layer統合、意思決定追跡、検証ゲート、人によるレビュー、チームの合意 — は、ツールが変わっても有効だと私は信じています。むしろツールが強くなるほど、このパターンの上で動いていない使い方のほうが、より早く崩れます。
Layer統合は、単に分野間の協業言語を統一する仕事ではなく、プロシージャル生成と自動化へ向かう道を先に開いておく仕事です。作家が一行ずつコンテキストを注入する保守的適用から始まり、システムがワールド状態から自動で生成する進歩的適用へと段階を広げていくあいだ、録音・キャプチャ・ライブビルドのような不可逆な段階の直前に置かれた人によるレビューが、最後のセーフティネットの役割を果たします。LLMが賢くなるほど、この骨格の価値は減りません。むしろ、人がレビューすべき決定の重みのほうが大きくなります。
皆さんがこの本のパターンを自分の環境に合わせてアレンジしてくださるなら、この本の次のバージョンは皆さんが書くことになります。会社の規模やジャンル、開発段階、チーム構成によって、そのまま持っていけるパターンもあれば、組み直さなければならないパターンもあります。持っていけるものと組み直すべきものを区別する作業そのものが、最初の意味のある仕事です。
何よりも、この本の出版を許可してくださったSCYBS Gamesの経営陣と代表に深く感謝いたします。会社の中で自然に発展したワークフローを業界と共有できるよう道を開いてくださったその決定がなければ、この本はありませんでした。24年間、ゲーム制作の道をともに、ときには別々に歩いてきたすべての同僚たち、20年あまり私のそばにいてくれたパートナー、そして23歳になるまでそばを守ってくれているペルシャ猫のコンジ(Kkongji)に感謝します。いまはもうそばにいない、永遠に19歳のポメラニアンのゴミ(Gomi)にも、同じ気持ちを伝えます。
早すぎる成功が人生の毒杯になる経験は、一度で十分だと思っています。その一度を忘れないために、私は24年近く切磋琢磨しながら学び直している最中です。この本もまた、その学びのある時点に残す一つの区切りに近いものです。
最後に、この本の執筆を助けてくれたAIツールに感謝を伝えます。原稿を閉じるあいだにClaudeの新モデルFableが出たほど、この分野は日ごとに変わっています。
この本が、誰かの次の決定に一行でも足しになれるなら、それで十分です。
イ・ミンス、2026年
この付録は、著者がMMORPG開発会社Aで運用している会社PC環境を、ハードウェアからツール、知識資産、検証資料まで1枚にまとめたインベントリーです。本文の随所で「こういうツールで」「こういうatom構造で」「こういうレポートで」と言及してきたものが、実際にどんな規模と組み合わせで存在しているのかを一目でお見せするために整理しました。実名・固有名詞はすべて匿名化してあり、数値は時点によって変わるものなので、絶対値ではなく比率と構成として読んでいただければと思います。
この付録の読み方は二つあります。一つは、自分の環境と項目ごとに比較してみることです。「自分はどのエンジンを使っているか、どのコラボレーションツールを使っているか、知識資産はどんな形で積み上がっているか」を同じ欄に埋めてみると、自分の空欄が浮かび上がります。もう一つは、構成のバランスを見ることです。ツールが多ければよい環境というわけではなく、エンジン・企画・アート・コラボレーション・AIの五つの軸が互いを妨げずにかみ合っているかどうかが重要です。項目の一つひとつよりも、その組み合わせ方を見ていただければと思います。
まず土台となるハードウェアとOSです。AIツールを本格的に使い始めると、ローカルで拡散モデルやSTTを動かす場面が出てくるため、メモリーとGPUの余裕がそのまま作業速度になります。以下のスペックは「これだけあれば詰まらずに回る」という下限に近い基準線として見ていただければと思います。
| 項目 | スペック |
|---|---|
| CPU | デスクトップワークステーション級 |
| RAM | 64GB以上 |
| GPU | UE5開発用(CUDA互換) |
| ストレージ | SSD 2TB + NAS共有 |
| モニター | 27インチ2台 |
RAMとGPUの2行が核心です。エンジンのエディター、ローカルのLLM補助ツール、画像生成を同時に立ち上げる瞬間が頻繁に来るからです。
| 項目 | 値 |
|---|---|
| OS | Windows 11 Pro |
| 仮想化 | WSL2(Ubuntu)、必要時のみ |
| バックアップ | 毎日自動 |
WSL2は常時オンにしておくのではなく、Linux専用ツールを動かすときだけ引っ張り出して使います。バックアップが毎日自動で回っているという点が、この表で最も重要な1行です。
ツールは五つの系統に分けます。エンジン・ツール、デザイン・企画、アート、コラボレーション・運営、そしてAI・LLMです。一人で五つの系統をすべて使うわけではありませんが、プランナーであればデザイン・企画、コラボレーション・運営、AI・LLMの三つの系統を毎日行き来します。系統ごとに「必須1〜2個+補助」という形になっている点に注目していただければと思います。
| ツール | 用途 |
|---|---|
| Unreal Engine 5.7以上 | メインエンジン |
| Visual Studio | コード |
| Rider | C# IDE(補助) |
| PerforceまたはSVN | コード・アセットのバージョン管理 |
エンジンとバージョン管理はワンセットです。プランナーもバージョン管理クライアントは必ず扱えなければなりません。マスターデータと仕様書がすべて同じリポジトリで回っているからです。
| ツール | 用途 |
|---|---|
| Excel | マスターデータ+VBAマクロ |
| Markdownエディター | 仕様書・議事録 |
| Figma | UI・ワイヤーフレーム |
| Mermaid | ダイアグラム |
プランナーの日常の作業台です。Excelはデータの本拠地、Markdownは文章の本拠地で、AIツールが最も深く食い込む二つの地点でもあります。Mermaidが1枠を占めている理由は、本文で強調したとおり、図式がそのまま合意の言語だからです。
| ツール | 用途 |
|---|---|
| Maya / Blender | 3D |
| Substance 3D | テクスチャー・マテリアル |
| Photoshop | 2D・イラスト |
| Stable Diffusion(SDXL)/ ComfyUI | セルフホスティングによるコンセプト・テクスチャーの本番量産(LoRA・ControlNet) |
| Midjourney | 初期ムードボード(補助) |
プランナーが直接使うツールではありませんが、アートパートとコンセプトをやり取りするとき、相手側にどんなツールがあるかを知っておくと、依頼の解像度が変わります。本番の量産は、セルフホスティングのStable Diffusion(SDXL)/ComfyUIが主軸です。アセットを外部にアップロードしないのでIPを守れること、キャラクターLoRA・ControlNetで同じ人物の一貫性を生成のたびに統制できることがその理由です。Midjourneyのようなクローズド型のツールは、プロジェクトのトーンを最初に手探りする初期ムードボード程度の補助にとどめ、一貫性と反復の統制がかかった本番の量産には使いません。
| ツール | 用途 |
|---|---|
| コラボレーションツール(ClickUp) | タスク |
| 社内メッセンジャー | リアルタイムのやり取り |
| 自前構築のWiki | Wiki・長期文書 |
| 自前のWebポータル | 統合インターフェース(20.3) |
コミュニケーションの時間軸がツールを分けます。即時性が必要なリアルタイムのやり取りは社内メッセンジャーへ、タスクはコラボレーションツール(私たちのチームはClickUp)へ、長く残す知識は自前構築のWikiへと振り分けます。トラッカーをJIRA・Redmineに、WikiをConfluence・Notionに置き換えても、メッセンジャーが何であっても、本書の流れはそのまま通用します。自前のWebポータルは、この三つとAIツールを一つの画面でつなぐ統合窓口で、20.3で詳しく扱います。
| ツール | 用途 |
|---|---|
| Claude(Opus + Sonnet) | メインLLM |
| GPT-4 | 代替 |
| Whisper(セルフホスティング) | 音声認識(STT) |
| Stable Diffusion | 画像生成(セルフホスティング) |
| MCPサーバー | ツール統合(20.4) |
メインはClaudeに据え、GPT-4をクロスチェック・代替として置きます。機微な音声・画像は外部に送らず、セルフホスティングで処理するという原則が、WhisperとStable Diffusionの2行に込められています。MCPサーバーはこれらのツールをワークフローに組み込む接着剤で、20.4で構造を説明します。
atomは、本文で扱った「決定の最小単位」をファイルに落とし込んだ知識のかけらです。以下は、そのatomが分野別にどう分布しているかを示す表で、2026年5月時点の一断面です。絶対的な個数よりも、どの分野に決定が集中しているかを見ていただければと思います。決定が集中している場所こそ、そのプロジェクトが最も熱心に悩んでいる地点です。
| カテゴリー | atom数 | 備考 |
|---|---|---|
| combat | 47 | 戦闘システムの決定 |
| narrative | 38 | ナラティブ5階層 |
| ui | 31 | UI・HUD |
| balance | 28 | バランス数値 |
| level | 22 | レベルデザイン |
| character | 19 | キャラクター・voice_profile |
| meta·governance | 18 | 手順・ルール |
| qa·integrity | 16 | 検証 |
| content | 14 | コンテンツ量産 |
| operations | 14 | 運営ワークフロー |
| external_reference | 12 | 外部資料 |
| economy | 11 | 経済・資源 |
| その他 | 34 | 分類進行中 |
戦闘(combat)が最も厚く、ナラティブがそれに続くという分布は、このプロジェクトが戦闘中心のMMORPGであり、同時に物語の比重も手放すまいとしている性格をそのまま表しています。「その他34」はまだカテゴリーが確定していない新規の決定で、この欄が大きくなりすぎたら分類体系に手を入れる時期が来たというシグナルです。2026年5月時点の合計は304個です。
ツールと知識があっても、それがきちんと回っているかを確認する仕掛けがなければ、品質はずるずると崩れていきます。この節では、その確認の仕掛けを二種類に分けてお見せします。定期的に出力するレポートと、意思決定を事後に追跡できるように残す決定カードです。
| レポート | 頻度 |
|---|---|
| デイリービルドレポート | 毎日 |
| アルファギャップレポート | 週次(10.3) |
| スプリント品質レポート | 隔週 |
| マイルストーンQAレポート | マイルストーンごと |
| 四半期振り返り | 四半期ごと |
頻度がそのままレポートの性格です。毎日出すものは状態の点検、週次・隔週は傾向の点検、マイルストーン・四半期は方向の点検です。AIが最も大きく貢献する地点は、毎日・週次のように繰り返されるレポートのドラフト作成で、その事例は10.3で扱います。
| 四半期 | 決定数 |
|---|---|
| 2025年第4四半期 | 132 |
| 2026年第1四半期 | 156 |
| 2026年第2四半期(進行中) | 89 |
| 累計 | 547 |
四半期ごとに100件前後の決定がカードとして残るという事実そのものが、決定を記憶ではなく記録で扱うという運営原則を物語っています。第2四半期の89件は四半期半ば時点の累積なので進行中の値で、四半期末には直前の四半期と同じ水準に達します。これらのカードが積み重なってA.3のatomへ昇格していく流れが、このシステムの学習の軸です。
会議は時間が最も漏れていく場所であり、AIツールの効果を最も早く体感できる場所でもあります。以下は四半期ごとの会議をカテゴリーでまとめた平均分布で、17.3で扱った議事録システムの入力規模を見積もるための参考資料です。数値は四半期ごとに揺れるため、範囲で記載しました。
| カテゴリー | 四半期平均 |
|---|---|
| デイリー(daily) | 65〜70回 |
| 戦闘(battle) | 35〜45回 |
| アート(art) | 25〜30回 |
| イシュー(issue) | 8〜15回 |
| レビュー(review) | 6〜10回 |
| その他(1:1・外部) | 40〜50回 |
デイリーミーティングが最も多く、戦闘関連の会議がそれに続きます。A.3のatom分布と同じ形だという点が示唆的です。決定が集中する分野では、会議もまた集中します。これほど会議が多い環境ほど議事録の自動整理の効用は大きくなり、その具体的な運用は17.3で説明します。
ここまでの表はすべて、著者の環境を写した1枚の写真です。そのまま書き写すためのリストではなく、自分の環境を同じ枠組みで整理してみるための見本として使っていただければと思います。チームの規模、ジャンル、プラットフォームが違えば、ツールもatomの分布も会議の比重も変わります。重要なのは項目の一致ではなく、「土台 → ツール → 知識 → 検証」という四つの層が、自分の環境でも途切れずにつながっているかどうかです。その四層の中に空いている欄があるなら、そこが次に手を入れる場所です。
この付録は、著者が会社のプロジェクトAで作って運用していたツールやスキルを、個人PCと一般的な作業に持ち込んで再利用した手順をまとめたものです。核心となる問いは一つです。「会社の知識資産を侵害せずに、そこで学んだツールの骨格だけを合法的に持ち出すには、どうすればよいのか」。この付録では、その境界線をどう引いたのか、何を持ち出して何を残してきたのか、そしてその決定をどう記録に残したのかをお見せします。
この付録の使い方は次のとおりです。まずB.1の五つの原則をご自身の状況に照らして読み、B.3の手順を一度そのままたどってみてください。そのうえで、B.4の記録様式をコピーして、ご自身が持ち込もうとするツールに合わせて埋めていけば大丈夫です。会社の資産を扱う仕事である以上、「速く」よりも「あとに残せるように」が優先で、この付録全体がその観点で組まれています。
ツールを持ち出す前に合意しておいた五つの原則です。この五つは順番ではなく同時に守るべき条件で、一つでも崩れたら借用そのものを保留します。前の三つは「何を持ち出すのか」についての技術的な境界、後の二つは「どうやって後ろめたさなく持ち出すのか」についての手続き的な境界です。
| 原則 | 説明 |
|---|---|
| 1. 会社IPを含めない | 会社名・実名・固有名詞を取り除きます |
| 2. ツールの骨格だけを持ち出す | 会社のドメインデータは遮断します |
| 3. 汎用化して再構成する | 一般的なユースケースとして作り直します |
| 4. 引用・出典を明確にする | 会社から借用したツールであることを明記します |
| 5. 法務・人事との合意 | 会社の了解手続きを経ます |
最もよく揺らぐのは2番です。アルゴリズムと構造(骨格)は持ち出してもかまいませんが、その骨格が前提としていた会社のデータ形式までついてくると、その瞬間にIPを持ち出したことになります。骨格とデータを切り離す作業こそが、借用の本体です。
原則に従って実際に個人PCへ持ち込んだツールは六つです(2026年5月時点)。すべてデータを扱うツールという共通点がありますが、これは偶然ではありません。データ処理ツールは、骨格(パース・変換・可視化のロジック)とドメイン(会社のシートの具体的な形式)を切り離すのが比較的容易だからです。
| ツール | 会社の原本 | 個人の汎用版 |
|---|---|---|
| excel-reader | xlsmのシート・VBA抽出 | 汎用のExcel処理 |
| relation-map-gen | FK関係のHTML | 汎用のデータ関係図 |
| schema-doc | シートからMarkdownスキーマを生成 | 汎用のスキーマドキュメント化 |
| table-creator | データテーブルの量産 | 汎用のテーブル生成 |
| gdd-gen | GDDの自動生成 | 汎用のドキュメント生成 |
| gdd-export | Markdownから複数シートのxlsxへ変換 | 汎用のxlsx変換 |
表の真ん中の列と右の列を比べてみると、汎用化とは何かが見えてきます。左側は「会社のシート」「GDD」のようにドメインの入った名前で、右側は「汎用のExcel」「汎用のドキュメント」のようにドメインを取り払った名前です。名前から会社が消えることが、汎用化の最初のサインです。
原則(B.1)を実際の手の動きに移すと、以下の六つのステップになります。最も重要な分岐点はステップ2とステップ4です。ステップ2で骨格とドメインをきれいに切り分けておけないと後のすべてのステップが汚染され、ステップ4の会社の了解を飛ばすと、どれだけよく作っても使えないツールになります。
flowchart TD
A[1. 会社ツールの識別] --> B[2. 会社依存領域の分離]
B --> B1[会社データ・固有名詞への依存]
B --> B2[ツールの骨格: アルゴリズム・構造]
B1 --> C[3. 会社依存の除去 + 汎用変数化]
B2 --> C
C --> D[4. 会社の了解: 法務・マネージャー]
D --> E[5. 個人PC環境への適用・検証]
E --> F[6. 出典明記 + 借用記録]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class B2 code;
class D human;
class B1,F data;
六つのステップのうち最も時間がかかるのは、コード作業(ステップ2・3)ではなくステップ4、つまり会社との合意と法務の通過です。技術ではなく信頼こそが最大の関門だという意味で、だからこそ借用はいつも、合意を先に取り付けてからコードを後で磨く、という順序で進めます。
借用したツールには、必ず記録を併せて残します。あとになって「このツールはどこから来て、何を取り除き、誰の了解を得たのか」を問われる瞬間が来るかもしれないからです。以下はexcel-readerを例にした記録様式で、皆さんはこの枠をそのままコピーして、ご自身のツールに合わせて埋めていけば大丈夫です。
---
tool: excel-reader (個人の汎用版)
original_source: 会社のプロジェクトA
adopted: 2026-05
permission: 会社のマネージャー + 法務の通過
modifications:
- 会社のシート形式への依存を除去
- 会社ドメインの関数(xlsm VBA)を除去
- 汎用のcsv/xlsx処理へ一般化
- 会社名・実名への参照を全面除去
usage_in_book: 本書のツール事例として引用 (Part 1・5・6・8など)
---
様式の日付欄(adopted)は、2026-05のように確定した年-月で書きます。「2026年5月ごろ」のような自由表記は、あとで埋める空欄のように見えてしまうので、借用を確定した時点をその場で動かないように書き留めておきます。
この記録の中で最も価値のある行はpermissionとmodificationsです。前の行は借用が正当だったことを、後の行は何を切り離したのかを証明します。この2行があれば、のちに疑問が提起されても、たどれる根拠が残ります。
何を持ち出したのかと同じくらい、何を残してきたのかも重要です。会社のツールのうち、意図的に借用しなかったものとその理由を書きました。残してきたツールの共通点は、会社の中核IPであるか、会社の組織構造に深く結びついていて、骨格とドメインを切り離せないという点です。
| ツール | 借用しなかった理由 |
|---|---|
| 会社の戦闘システムツール | 会社の中核IP、会社の独占 |
| 会社のナラティブ文書ツール | 会社の世界観に依存 |
| 会社の戦闘TFツール | 会社の組織構造に依存 |
| 会社の人事・財務ツール | 外部環境に合わない |
B.2で持ち出したツールがすべて「データ処理」だったことと、ちょうど対をなします。持ち出したのはドメインと切り離せるツールで、残してきたのはドメインと一体のツールでした。分離できるかどうかが、借用できるかどうかを分けます。
最後に、ツールを持ち出す前に自分で通すべき五つの項目です。この表は合格/不合格を判定するチェックリストで、五つの項目をすべて通過したときだけ借用し、一つでも引っかかれば保留します。「おおむね大丈夫」はありません。会社の資産を扱う仕事には、部分的な通過が通用しないからです。
| 点検項目 | 通過基準 |
|---|---|
| 会社の了解を得たか | マネージャー・法務の明示的な同意 |
| 法務レビューを通過したか | 書面または記録された確認 |
| 会社IPを完全に除去したか | grep watchlistの検査0件 |
| 汎用性を検証したか | 別の環境でも動作することを確認 |
| 事故発生時の対応手順があるか | 追跡・回収経路の定義 |
五つの項目を五つのマスの通過として読むのではなく、五つの錠前として読んでいただきたいのです。会社で学んだことを個人の資産として正当に持ち出すことは確かに可能ですが、その正当さは、この五つの錠をすべて掛け終えたときにだけ成立します。
この付録は、本文で取り上げたツールとシステムの権限・設定を1か所に集めた参照表です。本文では「なぜこのように運用するのか」を説明していますが、いざ自分の環境に適用しようとすると、「では具体的にどの値をどこに入れればよいのか」が必要になります。この付録がその空白を埋めます。
設定値そのものより、その値を選んだ理由のほうが重要です。表に書かれた数字をそのままコピーするのではなく、各項目の下にある短い説明を読み、自分のチーム規模とリスク水準に合わせて調整してください。一人で作業しているなら権限レベルを分ける必要はありませんし、外注がなければ外注の項目は丸ごと外して構いません。
この付録の使い方は2つあります。初めて環境をセットアップするときは、C.1から順に目を通し、漏れている項目がないかチェックリストのように使います。運用中は、事故が起きたときにまずC.7(事故対応)を開いて該当する事故の行を探し、その上にある予防項目へさかのぼります。
LLM APIキーはコストに直結し、流出すれば即座に金銭的な事故につながります。そのため、キー管理と権限レベルを最初に扱います。
| キー | 保管 |
|---|---|
| Anthropic API | 環境変数 + 1Password |
| OpenAI API | 環境変数 + 1Password |
| セルフホスティング | 社内 |
キーはコードではなく環境変数として注入し、原本はシークレット管理ツール(例:1Password)に置きます。もっともよくある事故は、キーをコードに書き込んだままgitに上げてしまうことなので、gitにキーを含めることは例外なく禁止します。
| ユーザー | 権限 |
|---|---|
| ディレクター・シニア | full(cost cap運用の責任) |
| 一般メンバー | 作業ごとのcap |
| 外注 | 作業ごとに1回限り |
権限は信頼ではなく、責任の大きさで分けます。full権限を持つ人は、コスト上限(cost cap)を管理する責任もあわせて負います。外注には作業単位で1回だけ開放し、終わったら回収します。
ツールごとに推奨設定は異なりますが、核心は分析の作業と創造の作業を分けることです。分析は再現可能でなければならず、創造には多様性が必要です。
以下はClaude Codeの基本設定の例です。1行ずつ見ると、モデルを固定し、拡張思考をオンにし、トークン上限を設け、自動アップデートをオンにし、プロンプト送信時にメモリーを注入するフックを掛けています。
{
"model": "claude-opus-4-8",
"extended_thinking": true,
"max_tokens": 100000,
"auto_update": true,
"hooks": {
"UserPromptSubmit": ["~/.claude/hooks/inject_memory.py"]
}
}
modelの値はあくまで例です。モデル名は世代ごとに変わるため(この例は執筆時点のものです)、そのままコピーせず、/modelで現在使える最新の名前を確認して入れてください。名前が変わっても、本書のワークフローの骨格はそのまま動きます(付録K参照)。
hooks.UserPromptSubmitに掛けたスクリプトは、プロンプトを送信するたびに関連するメモリーの断片を自動で差し込む役割を担います。このメモリー注入のメカニズムは、本文の第24部で詳しく扱います。
C.2.1.1 ツール権限スキーマ(allow / deny)
同じsettings.jsonの中で、権限はpermissionsブロックとして別に置きます。AIが人の承認なしに自動で実行してよいツールはallowに、一度でも事故が起きれば致命的なため自動実行を防ぐべきツールはdenyに書きます。表記はツール(コマンドパターン)の形式で、:*は「そのコマンドで始まるすべての呼び出し」を意味します。
{
"permissions": {
"allow": [
"Bash(ls:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Read(*)",
"Grep(*)"
],
"deny": [
"Bash(rm -rf:*)",
"Bash(git push --force:*)"
]
}
}
読み取り・検索(Read・Grep)や状態照会(git status・git diff)のように、元に戻す必要が生じようのないコマンドはallowで自動許可し、承認ポップアップの疲れを減らします。逆にrm -rfやgit push --forceのように、一度のミスが復旧不可能なコマンドは、自動許可の範囲をどれだけ広げてもdenyに入れておきます。
運用原則は4つです。
| 原則 | 内容 |
|---|---|
| ホワイトリストで始める | 自動許可は最小限から始め、必要なときだけallowに追加 |
| 危険なコマンドは明示的に遮断 | rm -rf・git push --forceは例外なくdeny |
| 定期的な整理 | 四半期ごとにallowを見直し、使っていない権限を取り除く |
| ドメイン別の分離 | グローバル権限とプロジェクト権限を分け、自宅PCと会社PCが異なるポリシーを持つようにする |
allowリストは静的な設定ではなく、作業の積み重ねの痕跡です。繰り返す作業が増えればそのぶん長くなるため、四半期ごとの整理サイクルをあわせて用意しておくとよいでしょう。この権限運用の背景は、本文の第1部第3章で詳しく扱います。
| 設定 | 推奨値 |
|---|---|
| LLM temperature(分析) | 0 |
| LLM temperature(創造) | 0.7 |
| Cache TTL | 1時間 |
| Cost cap(日次) | ツールごとに定義 |
| Backup周期 | 日次 |
分析用の呼び出しはtemperature 0に固定し、同じ入力には同じ出力が返るようにします。検証・lint・分類のように、結果がぶれてはいけない作業がここに含まれます。逆に、アイデアの発散や草案の生成には0.7程度の多様性を持たせます。コスト上限は単一の標準を設けず、ツールごとに個別に決めます。ツールごとに呼び出し頻度とトークン消費が異なるためです。
| ブランチ | 権限 |
|---|---|
| main | ディレクター・シニアのみpush |
| feature/* | 全メンバー |
| protected branches | コードレビュー必須 |
mainブランチへの直接pushは禁止し、すべての変更はfeatureブランチでコードレビューを経て取り込まれるようにします。force-pushは協業の履歴を上書きしてしまうため禁止し、やむを得ない事故復旧が必要な場合に限り、ディレクターとコードリードの合意のもとで例外を認めます。
ドキュメントフォルダーは、Layer構造(L0〜L4)に沿って権限を分けます。上に行くほど影響範囲が広いため書き込み権限を狭め、下に行くほど作業が分散するため書き込み権限を広げます。
| フォルダー | 権限 |
|---|---|
| docs/L0_vision/ | ディレクターがwrite、全員がread |
| docs/L1_systems/ | 分野ディレクターがwrite、全員がread |
| docs/L2_content/ | 担当者がread・write |
| docs/L4_meta/ | 全員がwrite |
| team_memory/ユーザー別/ | 本人のみread・write |
ビジョン(L0)はディレクターだけが書き、全員が読みます。システム(L1)は分野ディレクターが書きます。コンテンツ(L2)は担当者が書き、メタ・一時置き場(L4)は誰でも書けます。個人のメモリーには本人だけがアクセスします。このLayer構造そのものは本文の第6部で扱います。
| 資産 | バックアップ |
|---|---|
| gitリポジトリ | git自体 + リモートバックアップ |
| シート(Excel) | git + 日次バックアップ |
| ユーザーデータ | DBバックアップ(サーバー標準) |
| 議事録・決定事項 | git |
| メモリー | 日次の自動同期 |
資産の種類ごとにバックアップの経路は異なりますが、原則は1つです。失うと復旧が難しい資産ほど二重に持ちます。テキスト資産(議事録・決定事項・コード)はgitがそのままバックアップになり、バイナリーやサーバーデータは別途バックアップを置きます。復旧時間目標(RTO)は4時間以内を目安にしつつ、この値はチームが許容できるダウンタイムに合わせて調整します。
| 領域 | ルール |
|---|---|
| 外部LLMへの機密データ | プレースホルダーまたはセルフホスティング |
| 決済・個人情報 | LLMへの送信は絶対禁止 |
| 外部資料の引用 | 出典 + 法務レビュー |
| ユーザーデータ保護 | 匿名化 + GDPR準拠 |
もっとも守りやすく、かつもっとも頻繁に破られるルールが「機密データを外部LLMに送らない」です。作業が急ぎのとき、実データをそのまま貼り付けたくなる誘惑が大きいためです。決済・個人情報は例外なく送信禁止とし、分析が必要ならプレースホルダーに置き換えるか、セルフホストのモデルを使います。
| 事故 | 対応 |
|---|---|
| LLMのハルシネーションによる誤情報の発信 | 即時回収 + 報告 |
| コストcapの超過 | 自動遮断 + レビュー |
| 著作権の事故 | 1時間以内の使用中止 + 法務 |
| セキュリティ事故(key流出) | 即時のキー差し替え + 使用履歴のレビュー |
| データ損失 | バックアップからの復旧 + 事故分析 |
事故は、防ぐことより速く止めることが重要な場合が多くあります。表の対応はすべて「まず止めて、その後で分析する」という順序に従います。keyが流出したら、原因を調べる前にまずキーを差し替え、使用履歴を確認します。コストが上限を超えたら、自動で遮断した後にレビューします。この対応手順は文書として明文化し、定期的に訓練して、実際の事故の際にためらいなく機能するようにします。
会社のプロジェクトAで使っているR&D文書の命名・frontmatter標準(
_NAMING_FRONTMATTER_STANDARD)を一般化したバージョンです。
<category>_<topic>_<subtopic>.md
例:
combat_global_cooldown_constant.md
narrative_voice_profile_K_007.md
ui_button_primary_style.md
snake_caseで記述し、カテゴリーをprefixとして付けます。
D<YEAR>_Q<QUARTER>_<NUMBER>.md
例:
D2026_Q2_017.md
年・四半期・番号で構成します。
<category>_<YYYY-MM-DD>[_<seq>].md
例:
95_BattleTF_2026-05-18.md
art_review_2026-05-18_1.md
art_review_2026-05-18_2.md
spec_<topic>.md
例:
spec_combat_global_cooldown.md
spec_guild_attendance.md
report_<period>_<type>.md
例:
report_W21_alpha_gap.md
report_Q2_user_voice.md
---
name: combat_global_cooldown_constant
description: 戦闘システムのグローバルクールダウン標準値の定義
type: atom
category: combat
status: active
priority: P0
related_atoms:
- combat_skill_cooldown_rule
- combat_healing_skill_cooldown_exception
created: 2026-05-18
last_modified: 2026-05-18
related:
derives_from: [combat_design_principle]
affects: [combat_skill_cooldown_rule, ui_skill_cooldown_indicator]
---
---
decision_id: D2026_Q2_017
title: 戦闘のグローバルクールダウンを0.5秒に統一
type: system_change
status: active
created: 2026-05-18
created_by: チームメンバーA
approved_by: イ・ミンス
scope:
- combat_system
affected_atoms: [...]
implementation:
target_build: 2026-05-18
verification:
layer_1: passed
layer_2: passed
layer_3: pending
---
---
type: meeting_note
category: battle
date: 2026-05-18
attendees: [チームメンバーA, チームメンバーB, イ・ミンス]
related_atoms: [...]
---
---
title: ギルド出席機能の仕様
type: spec
priority: P1
target_milestone: MS2
---
| 文書の種類 | 必須 |
|---|---|
| atom | name, description, type, category, status |
| 決定カード | decision_id, title, type, status, created, scope |
| 議事録 | type, category, date, attendees |
| 仕様書 | title, type, priority |
| 文書の種類 | 任意 |
|---|---|
| atom | related, last_modified, priority |
| 決定カード | rationale, related_decisions, verification |
| 議事録 | related_atoms, sub_topic |
| 仕様書 | target_milestone, related_atoms |
# frontmatter_lint.py
for file in glob("**/*.md"):
fm = parse_frontmatter(file)
if not fm:
warn(f"{file}: frontmatterなし")
doc_type = infer_type_from_filename(file)
required = REQUIRED_FIELDS[doc_type]
for field in required:
if field not in fm:
warn(f"{file}: 必須フィールド{field}が欠落")
ビルド時に自動実行されます。違反があればalertを出します。
| 領域 | 防止策 |
|---|---|
| atom name | グローバルでunique |
| 決定ID | 四半期内でunique |
| 会議ID | 日付 + seq |
| ファイル名 | フォルダー内でunique |
命名が衝突したときは自動でブロックします。
1. 新しい名前のatomを作成
2. 既存atomのすべてのwikilinkを新しい名前に更新(自動)
3. 既存atomをdeprecated + redirectに
4. 1か月後に_archiveへ移動
性急な名前変更は、資料を損なうリスクがあります。
1. 変更理由の提案(decision手続き)
2. 既存全文書のマイグレーションスクリプト
3. ビルドlintの更新
4. チームへの通知
本標準は著者の環境に合わせたものです。読者の皆さんは、ご自分の環境に合わせて調整してください。要点は次のとおりです。
| 要点 | 理由 |
|---|---|
| 命名の一貫性 | 検索・自動化 |
| Frontmatter標準 | ツールとの親和性 |
| 必須・任意の分離 | 作成負担↓ |
| Lintの自動化 | 標準の強制 |
| 変更手順 | 資料の保護 |
MCP(Model Context Protocol)は、LLMが外部のツールやデータに標準化された方式で接続するための通路です。本文の第20部ではプロジェクト管理MCPを扱いましたが、ゲーム企画のワークフローに取り込めるMCPサーバーはそれよりはるかに多くあります。この付録は、その候補をひと目で見渡せるように集め、どの順序で導入するとよいかの優先順位を付けたカタログです。
カタログの目的は「これを全部インストールせよ」ではなく、「必要なときにどこから選べばよいかを知っている」ことです。一度に複数のMCPをつなぐと、何が問題を起こしているのか見分けがつきません。E.4の導入サイクルに従って、一つずつ増やしていきましょう。
使い方は次のとおりです。最初はE.2.1のP0リストだけを見ます。基本が固まったらE.2.2(P1)に進み、チームに特有のニーズが生まれたらE.2.3(P2)やE.3(独自開発)を検討します。コストが気になるならE.5を、障害に備えるならE.6を先に見てください。
MCPサーバーは、接続先によって大きく4つに分かれます。ゲームプランナーが毎日行き来するツールのほとんどがこの中に入ります。
| 領域 | MCPサーバー |
|---|---|
| プロジェクト管理 | ClickUp・JIRA・Linear |
| ドキュメント | Confluence・Notion・Google Drive |
| コラボレーション | 社内メッセンジャー(Slack・Discordなど) |
| データ | Excel・Google Sheets・DB |
プロジェクト管理はタスクとスケジュールに、ドキュメントは仕様書やWikiに、コラボレーションはチームのコミュニケーションに、データはバランスやアイテムのマスターデータにつながります。自分のチームがすでに使っているツールがどの領域に属するかを先に押さえれば、導入候補は自然と絞り込まれます。
優先順位は「ないと作業が止まるか」を基準に付けました。P0はほぼすべての作業の土台で、P1はあると大いに便利になり、P2はチームの状況に応じて選択します。
| サーバー | 用途 | 備考 |
|---|---|---|
| Filesystem MCP | ローカルファイルへのアクセス | 基本 |
| Git MCP | 変更の追跡 | 必須 |
| 社内メッセンジャーMCP | チームのコミュニケーション | 推奨 |
| コラボレーションツールMCP(ClickUp・JIRAなど) | タスク | 会社のツール |
FilesystemとGitは、LLMが資料を読み、変更履歴をたどるための土台なので、最初につなぎます。社内メッセンジャーMCPはチームのコンテキストを引き込み、タスクツールは会社がすでに使っているもの(ClickUpでもJIRAでも)をそのまま接続します。
| サーバー | 用途 |
|---|---|
| Wiki MCP(Confluence・Notionなど) | Wiki |
| Google Drive MCP | 外部共有資料 |
| Excel MCP | シートの直接参照 |
| Mermaid MCP | ダイアグラムのレンダリング |
P0が安定したら、ドキュメントとデータの側を広げます。特にExcel MCPは、バランスのマスターデータをLLMが直接参照できるようにしてくれるため、ゲーム企画での活用度が高いツールです。Mermaid MCPは設計のダイアグラムをその場でレンダリングしてくれるので、ドキュメント作成の流れを途切れさせません。
| サーバー | 用途 |
|---|---|
| Discord MCP | ユーザーコミュニティ |
| GitHub MCP | 外部とのコラボレーション |
| Linear MCP | タスク管理の代替 |
| Notion MCP | Wikiの代替 |
P2は代替手段か、特定の状況専用です。ユーザーコミュニティを運営しているならDiscordを、外部とのコラボレーションが多いならGitHubをつなぎます。Linear・Notionはすでに導入したツールの代替なので、重複してインストールする必要はありません。
商用MCPでは埋まらない部分は自分で作ります。以下は、著者がゲーム企画のワークフローに合わせて独自開発したMCPです。いずれも、本文で扱ったシステム(atom・決定カード・議事録)をLLMから直接参照するためのものです。
| サーバー | 用途 |
|---|---|
| Atom MCP | atomの検索・参照 |
| Decision Card MCP | 決定カードの参照・生成 |
| KPI Dashboard MCP | ダッシュボードのデータ |
| Meeting Notes MCP | 議事録の検索 |
この4つは、商用ツールにはない社内資産(ナレッジatom、決定の履歴、議事録)を扱います。独自開発は負担が大きいため、E.4のサイクルの最終段階に回し、商用MCPでは埋められないものが明確になってから着手するのがよいでしょう。
MCPは一度につなぐと、問題の原因を切り分けるのが難しくなります。以下のサイクルは、「一つずつ、安定してから次へ」という原則を時間軸に展開したものです。
flowchart LR
A["1週間
Filesystemを1つ試験導入"] --> B["1か月
Git+社内メッセンジャーを追加"]
B --> C["3か月
5〜7個を安定運用"]
C --> D["6か月
独自MCP開発を検討"]
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
class A,B,C code;
class D human;
核心となるルールはただ一つ、一度に5個を同時に導入しないことです。新しいMCPをつなぐたびに、数日間はその一つが安定して動くかを見守ってから、次に進みましょう。
| サーバー | コスト |
|---|---|
| 外部MCP(オープンソース) | インフラのみ |
| セルフホスティング | インフラ+運用 |
| 商用MCP | 月額サブスクリプション |
コスト構造は3つに分かれます。オープンソースのMCPは動かすインフラの費用だけがかかり、セルフホスティングはそこに運用人員の費用が加わり、商用MCPはサブスクリプション料がかかります。8〜10個を運用するときの月額コストはおおよそ$50〜200程度と推定されますが、これは構成によって大きく変わるため、方向性の目安として参考にする程度にとどめてください。
| インシデント | 対応 |
|---|---|
| MCPサーバーの障害 | 中核サーバーはフォールバックを運用 |
| 権限インシデント(誤ったデータ修正) | read-only優先 |
| データ漏洩 | 機密データはセルフホスティング |
| コスト急増 | 上限(cap)+モニタリング |
MCPは外部ツールをLLMに直接つなぐため、誤った書き込み一つが実際のデータを壊しかねません。そのため基本はread-onlyにしておき、書き込み権限は本当に必要なサーバーにだけ開きます。中核となるサーバーは障害に備えてフォールバックを用意し、機密データを扱うMCPは外部ではなくセルフホスティングで動かします。コストは上限(cap)とモニタリングをあわせて防ぎます。
前の節までが「何を、どの順序で、いくらで」つなぐかを扱ったのに対し、この表は一つのMCPを実際につなぐ直前に、自分で通過させるべき項目を集めたものです。カタログを最初から読み直す代わりに、新しいMCPを追加するたびにこの5行だけを確認すれば十分です。5つの項目は、それぞれ前の節の核心ルールを1行に圧縮したものです。
| 点検項目 | 通過基準 | 根拠となる節 |
|---|---|---|
| どの領域か | プロジェクト管理・ドキュメント・コラボレーション・データのどれに属するかが明確 | E.1 |
| いま必要な優先度か | P0が安定してからP1、その次にP2という順序を守っている | E.2 |
| 一つずつつないでいるか | 一度に複数を同時に導入していない | E.4 |
| 権限は最小か | 基本はread-only、書き込みは本当に必要なサーバーだけ | E.6 |
| コストの上限があるか | 上限(cap)とモニタリングをあわせて設定している | E.5 |
5つの項目のうち、最も頻繁に飛ばされるのは「一つずつつないでいるか」の欄です。一度に複数のMCPを載せると、問題が起きたときにどのサーバーのせいなのか見分けがつかなくなるからです。5行すべてを通過したときだけそのMCPをつなぎ、1行でも引っかかったら、そのサーバーは次のサイクルに回します。
本書に登場した事例を、会社環境と個人PC環境とに分けて整理した索引です。読者が自分の環境に近い事例をすばやく見つけるための資料です。
| 事例 | 登場箇所 |
|---|---|
| CombatBalance・CombatFormula運営 | 8.1 |
| Economy Machinations Pilot | 8.2 |
| Damage Simulator(2008〜) | 8.3 |
| プロシージャルレベルデザインマスター | 7.1 |
| BehaviorTreeエディター | 7.2 |
| ダンジョン・フィールドのパターンライブラリー | 7.3 |
| HUD Layout v3 | 9.1 |
| Skill UI 6カラム決定 | 9.2 |
| NarrativeDocs 5階層 | 5.1 |
| voice_profile + voice_lint | 5.2·5.4 |
| proj_city_hunting_generator | 6.2 |
| NPC Persona/Squad | 6.3 |
| 事例 | 登場箇所 |
|---|---|
| 95_BattleTF運営 | 16.1 |
| 97_DevGuideでの連携 | 16.2 |
| 17.x議事録システム | 第17部全体 |
| アルファGap Report | 10.3 |
| decision_validation 3-layer | 10.2 |
| 304 atom運営 | 20.1 |
| チームメンバー別メモリー | 20.2 |
| ポータルWeb | 20.3 |
| 事例 | 登場箇所 |
|---|---|
| 中規模(10〜50人)チームのビジョン・ロードマップ | 19.1 |
| Design Directorの委任 | 19.2 |
| 対立管理・チーム文化 | 19.3 |
| 会議運営(リーダー視点) | 19.4 |
| 上位層とのコミュニケーション(PD/CEO) | 19.5 |
| AI導入戦略 | 19.6 |
| ガバナンス(プロンプト・ハルシネーション・コスト・法務・倫理) | 第22部全体 |
著者が個人PC環境(自宅)で直接経験した事例です。
| 事例 | 登場箇所 |
|---|---|
| excel-readerなど6ツールの借用 | 付録B |
| JIT atom注入システム | (個人PCインフラ) |
| 個人PCのスラッシュコマンド(book-captureなど) | (個人PCインフラ) |
本書の執筆プロセスそのものがAI活用の事例です。
| 領域 | 適用 |
|---|---|
| 章本文の量産 | LLM(Claude) |
| IP保護(会社 → 匿名化) | grep watchlist + ルール |
| 出典の追跡 | 会社環境を引用する際に明示 |
| 量産 → レビュー → 整備のサイクル | 5月の量産後にレビューモードへ移行 |
会社環境の事例(中規模(10〜50人)チーム、MMORPG、運営(ライブオプス))は、似た規模・ドメインの会社に適用できます。
| 読者の環境 | 適した事例 |
|---|---|
| モバイルMMORPG開発会社 | ほぼすべての事例 |
| PC MMORPG | 第14部のモバイル事例は調整が必要 |
| インディーゲーム | 中規模(10〜50人)以上の事例は縮小して適用 |
| 運営型ゲーム | 第15部+運営事例 |
個人環境(1〜2人、または趣味)では、会社の事例を単純化して借用します。
| 領域 | 単純化 |
|---|---|
| 会議システム | 1人なら不要。自分のメモで代替 |
| TF運営 | 1人なら不要 |
| 決定カード | 大きな決定だけ |
| atom・wikilink | 積極的に活用(1人でも価値あり) |
本書のすべての会社事例は匿名化しています。
| 原本 | 匿名化 |
|---|---|
| 会社名 | MMORPG開発会社A |
| プロジェクト | プロジェクトA |
| チームメンバーの実名 | チームメンバーA・B・C |
| ゲーム内固有名詞 | 加工(王国X、キャラクターK_001など) |
| 数値 | 加工(比率は実際のまま) |
| 会社ツール名 | proj_*(例: proj_city_hunting_generator) |
ゲームの外で働く読者(企画職・PM・一般のビジネスパーソン)のための逆引き索引です。本文の各章末にある「ゲーム外への応用」ボックスは、その章のワークフローをゲームと無関係な職種に置き換えて読むための橋渡しです。ゲームドメインの本文が負担に感じられる場合は、下のボックスから開いて、ご自身の職務の事例から入っていただいてもかまいません。「一般職種の道」(第1・2部 → 17 → 16 → 18 → 第21・22部)と90分の超短縮コース(17.1 → 16.2 → 22.1 → 21.1)のアンカーがこの索引です。
| 章 | 「ゲーム外への応用」が橋渡しする仕事 |
|---|---|
| 16.1 | 押し寄せる作業を一時ワークスペースに隔離し、結果だけを正本に吸収する |
| 16.2 | 一行のリクエストを合意・不具合・スケジュールの3トラックに分類する |
| 16.3 | 他職種・ステークホルダーに合った媒体で成果物をframingする |
| 17.1 | 議事録を決定の4フィールド(何を・誰が・なぜ・次は)で流れるようにする |
| 17.2 | 議事録から決定・アクションを抽出するパイプライン |
| 17.3 | 会議の決定の分類・同期 |
| 17.4 | 会議の要約・フォローアップ追跡の自動化 |
| 18.1 | 決定に恒久アドレス・責任者・根拠を入力し、過去の決定を先に探す |
| 18.2 | 一つの決定がどこまで影響を及ぼすかの波及分類 |
| 18.3 | 変更前後の追跡ワークフロー |
| 18.4 | ドキュメント変更の影響範囲を検索で確認する |
| 章 | 「ゲーム外への応用」が橋渡しする仕事 |
|---|---|
| 21.1 | 振り返りを自己改善の出発点にする |
| 21.2 | 振り返りの中の繰り返しパターンをルールへ昇格させる |
| 21.3 | 改善ループを閉じる |
| 22.1 | 作業指示書(プロンプト)1枚にコンテキスト・形式・ハルシネーション遮断・検証を盛り込む |
| 22.2 | ハルシネーション・安全性の多層防御 |
| 22.3 | AIコストを正直に管理する |
| 22.4 | 著作権・倫理のチェック |
| 章 | 「ゲーム外への応用」が橋渡しする仕事 |
|---|---|
| 19.1 | ビジョンの提示と委任 |
| 19.2 | 対立管理と会議リーダーシップ |
| 19.3 | 組織のAI導入戦略 |
上の索引は、本文に実在する「ゲーム外への応用」ボックスだけを集めたものです(2026-06時点で22件)。ボックスのない章は、ゲームドメインへの依存度が高く、そのままの転移が難しい章です。無理に置き換えるよりも、「一般職種の道」の上記の章から入ることをおすすめします。
この付録は、本文で言及した運用自動化スクリプトを1か所に集めた事例集です。本文では各スクリプトが「なぜ必要なのか」を流れの中で説明しましたが、いざ似たようなツールを作ろうとすると、「どのスクリプトがどんな役割でまとまっているのか」を一目で見渡せる地図が必要になります。この付録がその地図です。
スクリプト名と1行の説明、そして本文のどの節で扱ったかを併せて記しました。きれいに一般化できる中核スクリプト(G.1.1 フォーマットチェック・G.2.1 整合性チェック・G.3.1 関係図・G.7.1 コストトラッカー)と、G.8のテスト・フックの例は、会社の資料とは無関係な汎用の骨格として新たに書き起こし、そのまま実行できることを検証した実コードを掲載しています。入力例と出力、終了コードまで、実際に動かして確認した値です。残りの項目は名前・役割・関連する本文の節だけを記しましたが、その理由は付録G.9で正直に明かします。読者の皆さんは、実コードの項目を手本に、自分の環境に合わせた実装を自分の手で作ってみてください。
使い方はこうです。自動化したい作業の性質(検証なのか、レポート生成なのか、同期なのか)をまず決め、該当する節(G.1〜G.7)を開きます。そこで最も近いスクリプトを選んだら、括弧内の本文の節番号をたどって、文脈と設計意図を確認します。最後に、G.8の運用原則に照らして、自分のスクリプトがその原則を守っているかを点検します。
スクリプト全体を役割別にまとめると、次のようになります。
flowchart TD
G1["G.1 議事録・決定の自動化"] --> META["メタ運用
(知識の蓄積)"]
G2["G.2 検証・lint"] --> QA["品質ゲート"]
G3["G.3 影響追跡"] --> QA
G4["G.4 レポート自動生成"] --> REPORT["報告・可視化"]
G5["G.5 同期"] --> META
G6["G.6 LLM統合"] --> AI["AI補助"]
G7["G.7 コスト・運用"] --> AI
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class G1,G2,G3,G4,G5,G6,G7 code;
class AI ai;
class META,REPORT data;
会議で出た決定が散逸せず、知識資産として積み上がるようにするスクリプト群です。議事録の検証からatomの抽出、正式昇格まで、一本の流れでつながっています。
議事録が定められたフォーマット(必須フロントマター・必須セクション)を満たしているかを検査するスクリプトです。フォーマットの崩れた議事録は後続の自動抽出を壊すため、入口で止めます(17.2.2)。
以下は会社の資料とは無関係な汎用の骨格です。標準ライブラリ(sysのみ)で書かれており、そのまま実行できます。Markdown議事録のフロントマター(---で囲んだブロック)のキーと、本文のセクション見出し(## ...)がすべて揃っているかを見ます。欠けているものがあればviolationを出してexit 1、すべて揃っていればexit 0です。
#!/usr/bin/env python3
"""meeting_lint.py
Markdown議事録が定められたフォーマットを満たしているかを検査します。
- フロントマター(--- ブロック)内に必須キーがすべてあるか。
- 本文に必須セクション見出し(## ...)がすべてあるか。
欠けている項目があればviolationを出力してexit 1、なければexit 0。
標準ライブラリのみ使用します。
使い方:
python meeting_lint.py meeting.md
"""
import sys
REQUIRED_FRONTMATTER = ["type", "date", "category", "attendees"]
REQUIRED_SECTIONS = ["## 議題", "## 決定", "## アクションアイテム", "## 次回会議"]
def lint(text):
"""議事録本文の文字列を受け取り、欠けている項目の一覧(violation)を返します。"""
violations = []
# フロントマター: 先頭行が---なら、次の---までをフロントマターと見なす。
lines = text.splitlines()
front = []
if lines and lines[0].strip() == "---":
for line in lines[1:]:
if line.strip() == "---":
break
front.append(line)
front_keys = [ln.split(":", 1)[0].strip() for ln in front if ":" in ln]
for key in REQUIRED_FRONTMATTER:
if key not in front_keys:
violations.append({"kind": "frontmatter", "missing": key})
# セクション: 本文に該当する見出し行がそのままあるか。
body_lines = [ln.strip() for ln in lines]
for section in REQUIRED_SECTIONS:
if section not in body_lines:
violations.append({"kind": "section", "missing": section})
return violations
def main(argv=None):
argv = sys.argv[1:] if argv is None else argv
if len(argv) != 1:
sys.stderr.write("使い方: python meeting_lint.py meeting.md\n")
return 2
with open(argv[0], encoding="utf-8") as f:
violations = lint(f.read())
for v in violations:
print(f"[VIOLATION] {v['kind']}: {v['missing']}")
if violations:
sys.stderr.write(f"[FAIL] フォーマット違反{len(violations)}件\n")
return 1
sys.stderr.write("[PASS] フォーマット充足\n")
return 0
if __name__ == "__main__":
sys.exit(main())
定数2つが検査基準です。たとえばフロントマターにattendeesが欠け、本文に## 次回会議がない議事録を入れると、次のように2件が検出され、終了コードは1になります。
[VIOLATION] frontmatter: attendees
[VIOLATION] section: ## 次回会議
議事録の「決定」セクションを読み、知識atomの候補を自動で抽出するスクリプトです。人が一つひとつ書き写していた作業を代行します(17.2.3)。
レビュー待ち(pending)状態のatomを正式なatomフォルダへ昇格するスクリプトです。自動抽出と正式資産の間に、人によるレビューゲートを置きます(17.2.6)。
データとコンテンツがルールに違反していないかを自動で検出する品質ゲートです。人の目では見落としやすい一貫性のエラーを、機械が先にふるい落とします。
データ項目のIDが重複なく一意かを検証するスクリプトです。IDの衝突はランタイムに至って初めて爆発する事故なので、データの段階で止めます(10.1.2)。
以下は会社の資料とは無関係な汎用の骨格です。標準ライブラリ(csv・json・sys・argparse)のみを使い、そのまま保存してすぐ実行できます。入力は、どんなゲームデータでも持ちうる単純な形式、すなわちid列を持つCSVです。
#!/usr/bin/env python3
"""integrity_check_id_uniqueness.py
CSVデータのid列が一意かを検査します。
- 重複idがあればviolationの一覧を出力してexit 1。
- すべて一意ならexit 0。
標準ライブラリのみ使用します。
使い方:
python integrity_check_id_uniqueness.py data.csv
python integrity_check_id_uniqueness.py data.csv --id-column quest_id
"""
import argparse
import csv
import json
import sys
def find_duplicate_ids(rows, id_column):
"""rows(辞書のリスト)からid_column値の重複を探します。
返り値: violationのリスト。各項目は
{"id": 値, "row_numbers": [1始まりの行番号, ...]} の形。
ヘッダーを1行目と見なし、データの先頭行を2と数えます。
"""
seen = {} # id値 -> 登場した行番号のリスト
for index, row in enumerate(rows):
row_number = index + 2 # ヘッダー(1行目)の次から
key = row.get(id_column, "")
seen.setdefault(key, []).append(row_number)
violations = []
for key, row_numbers in seen.items():
if len(row_numbers) > 1:
violations.append({"id": key, "row_numbers": row_numbers})
violations.sort(key=lambda v: v["row_numbers"][0])
return violations
def load_rows(csv_path):
with open(csv_path, newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
def main(argv=None):
parser = argparse.ArgumentParser(description="CSV idの一意性チェック")
parser.add_argument("csv_path", help="検査するCSVファイルのパス")
parser.add_argument("--id-column", default="id", help="idとして使う列名 (デフォルト: id)")
args = parser.parse_args(argv)
rows = load_rows(args.csv_path)
violations = find_duplicate_ids(rows, args.id_column)
# G.8の出力標準: violation_listをJSONで標準出力に出す。
print(json.dumps({"violation_list": violations}, ensure_ascii=False, indent=2))
if violations:
sys.stderr.write(f"[FAIL] 重複id {len(violations)}件を発見\n")
return 1
sys.stderr.write("[PASS] 重複idなし\n")
return 0
if __name__ == "__main__":
sys.exit(main())
入力例(data.csv):
id,name
Q001,最初の依頼
Q002,失われたノリゲ
Q001,最初の依頼(重複)
実行結果は次のとおりです。Q001が2行目と4行目に2回現れたため、violationが1件検出され、終了コードは1です。
{
"violation_list": [
{
"id": "Q001",
"row_numbers": [2, 4]
}
]
}
NPCのセリフのボイス(口調・性格)の一貫性を検査するスクリプトです。同じキャラクターがチャプターごとに違う口調で話すずれを捕まえます(5.2・5.4)。
アセット(アート・UIなど)が変わったとき、意図しない見た目の変化が生じていないかを比較するリグレッションテストのスクリプトです(12.1.5)。
一つを変えると何が連動して揺れるのかを追跡するスクリプト群です。文書・決定・アセットの間のつながりをたどり、変更の波及範囲を見せてくれます。
文書間のWikilink([[対象]])を集めて連結グラフを自動構築するスクリプトです。どの文書がどの文書を参照しているかを一目で見せます(24.3.4)。
以下は会社の資料とは無関係な汎用の骨格です。標準ライブラリ(os・re・json・argparse)のみを使います。1つのフォルダ内の.mdファイルを読み、ファイル名(拡張子を除く)をノード、[[...]]リンクをエッジと見なします。結果として隣接リストとMermaid図のコードを併せて出します。
#!/usr/bin/env python3
"""wikilink_graph.py
フォルダ内の.md文書の[[Wikilink]]連結をグラフにします。
- ノード: 拡張子を除いたファイル名。
- エッジ: 文書本文の[[対象]]表記。[[対象|表示]]の形なら対象だけを見ます。
標準ライブラリのみ使用します。
使い方:
python wikilink_graph.py ./docs
python wikilink_graph.py ./docs --format mermaid
"""
import argparse
import json
import os
import re
import sys
WIKILINK = re.compile(r"\[\[([^\]|#]+)") # [[対象]] / [[対象|表示]] / [[対象#アンカー]]
def extract_links(text):
"""本文からリンク対象の名前を、登場順に重複なく抜き出します。"""
result = []
for match in WIKILINK.findall(text):
target = match.strip()
if target and target not in result:
result.append(target)
return result
def build_graph(doc_dir):
"""フォルダ内の.mdを走査し、{文書名: [リンク対象, ...]}の隣接リストを作ります。"""
graph = {}
for name in sorted(os.listdir(doc_dir)):
if not name.endswith(".md"):
continue
node = name[:-3]
path = os.path.join(doc_dir, name)
with open(path, encoding="utf-8") as f:
graph[node] = extract_links(f.read())
return graph
def to_mermaid(graph):
"""隣接リストをMermaid flowchartのコード文字列に変換します。"""
lines = ["flowchart LR"]
for node, targets in graph.items():
if not targets:
lines.append(f' {_id(node)}["{node}"]')
for target in targets:
lines.append(f' {_id(node)}["{node}"] --> {_id(target)}["{target}"]')
return "\n".join(lines)
_ID_CACHE = {}
def _id(name):
"""MermaidのノードidはASCIIである必要があります。日本語など非ASCIIの名前には
最初に登場した順に n1, n2, ... の短いASCII idを割り当て、ラベル[...]に元の名前を保存します。"""
if name not in _ID_CACHE:
_ID_CACHE[name] = "n%d" % (len(_ID_CACHE) + 1)
return _ID_CACHE[name]
def main(argv=None):
parser = argparse.ArgumentParser(description="Wikilink連結グラフビルダー")
parser.add_argument("doc_dir", help="文書(.md)が入っているフォルダ")
parser.add_argument("--format", choices=["json", "mermaid"], default="json")
args = parser.parse_args(argv)
graph = build_graph(args.doc_dir)
if args.format == "mermaid":
print(to_mermaid(graph))
else:
print(json.dumps(graph, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())
入力例(フォルダdocs/内の3ファイル):
docs/世界観.md 本文に [[地域_漢陽]] と [[勢力_義禁府]] のリンク
docs/地域_漢陽.md 本文に [[勢力_義禁府]] のリンク
docs/勢力_義禁府.md リンクなし
--format mermaidで実行すると、次の図のコードが出ます。ノードはファイル名の順(世界観 → 勢力_義禁府 → 地域_漢陽)に処理され、ラベルの中に元の名前がそのまま残ります。どの文書がどこへ伸びているか、終端(勢力_義禁府)が何かが一目で分かります。
flowchart LR
n1["世界観"] --> n2["地域_漢陽"]
n1["世界観"] --> n3["勢力_義禁府"]
n3["勢力_義禁府"]
n2["地域_漢陽"] --> n3["勢力_義禁府"]
特定の決定カードがどの文書・アセットに影響を与えるかを分析するスクリプトです。決定を覆す前に、波及範囲を先に確認します(18.4.3)。
特定のアセットを使っているスキルを逆引きで探し出すスクリプトです。アセットを修正・削除する前に、依存している箇所を把握します(11.2.4)。
散らばったデータを、人が読めるレポート・図にまとめ上げるスクリプトです。繰り返される定期報告を自動化し、手のかかる仕事を減らします。
アルファ段階の目標に対する不足分(gap)を集計し、週間レポートとして自動生成するスクリプトです(10.3.3)。
決定カードの連結関係をMermaid図のコードに変換するスクリプトです。決定の流れを絵で見ます(24.2.3)。
主要指標(KPI)を週単位で要約するスクリプトです(13.2)。
複数の場所に散らばった資料を効率よく揃えるスクリプトです。毎回全体をコピーするのではなく、変わった部分だけを選んで同期します。
議事録を全部ではなく変更分だけ選んで同期するスクリプトです。資料が積み上がるほど全体コピーは遅くなるため、増分方式を使います(17.5.4)。
gitのdiffを活用して、何が変わったかを効率よく検知する方式です。別途の追跡装置なしに、git自体を変更検知器として使います(17.5.4.1)。
分類・呼び出しのように判断が必要な作業をLLMに任せるスクリプトです。ルールではきれいに割り切れない仕事を、LLMの補助で処理します。
寄せられたFAQをカテゴリ別に自動分類するスクリプトです(13.1.3)。
会議を性格別のカテゴリに自動分類するスクリプトです。議事録フロントマターのcategoryを埋めるのに使います(17.3.6)。
あらかじめ整理しておいたプロンプトライブラリから、必要なプロンプトを呼び出すスクリプトです。同じプロンプトを毎回書き直さずに済むようにします(22.1.2)。
自動化そのものがコストと資料追跡の死角を生まないように管理するスクリプトです。
LLM呼び出しのコストを追跡し、上限(cap)を適用するスクリプトです。コストの暴騰を事後ではなく事前に防ぎます(22.3.5)。
以下は会社の資料とは無関係な汎用の骨格です。標準ライブラリ(json・os・argparse)のみを使います。呼び出しごとにトークン数を記録して累積コストを計算し、上限を超えると拒否シグナル(exit 2)を出します。単価はコード内の定数で、実際の値は各自が使うモデルの単価表に置き換えてください(下の値は説明用のプレースホルダーです)。
#!/usr/bin/env python3
"""llm_cost_tracker.py
LLM呼び出しのトークンを累積記録し、1日のコスト上限を検査します。
- record: 1回の呼び出し(入力/出力トークン)をledgerファイルに加算する。
- 累積コストがcapを超えるとexit 2で呼び出しを止める(事前ブロック)。
標準ライブラリのみ使用します。
使い方:
python llm_cost_tracker.py --ledger ledger.json --in 1200 --out 800
python llm_cost_tracker.py --ledger ledger.json --in 1200 --out 800 --cap-usd 5.0
"""
import argparse
import json
import os
import sys
# 単価: 1,000トークンあたりのUSD。説明用のプレースホルダー値 — 実際のモデル単価表に置き換えること。
PRICE_PER_1K_INPUT = 0.003
PRICE_PER_1K_OUTPUT = 0.015
def cost_of(in_tokens, out_tokens):
"""入力/出力トークンから1回の呼び出しコスト(USD)を計算します。"""
return (in_tokens / 1000) * PRICE_PER_1K_INPUT + (out_tokens / 1000) * PRICE_PER_1K_OUTPUT
def load_ledger(path):
if os.path.exists(path):
with open(path, encoding="utf-8") as f:
return json.load(f)
return {"calls": 0, "in_tokens": 0, "out_tokens": 0, "total_usd": 0.0}
def save_ledger(path, ledger):
with open(path, "w", encoding="utf-8") as f:
json.dump(ledger, f, ensure_ascii=False, indent=2)
def main(argv=None):
parser = argparse.ArgumentParser(description="LLMコスト追跡・上限")
parser.add_argument("--ledger", required=True, help="累積記録JSONファイルのパス")
parser.add_argument("--in", dest="in_tokens", type=int, required=True, help="今回の呼び出しの入力トークン")
parser.add_argument("--out", dest="out_tokens", type=int, required=True, help="今回の呼び出しの出力トークン")
parser.add_argument("--cap-usd", type=float, default=None, help="累積コスト上限(USD)。超えたらブロック")
args = parser.parse_args(argv)
ledger = load_ledger(args.ledger)
this_cost = cost_of(args.in_tokens, args.out_tokens)
ledger["calls"] += 1
ledger["in_tokens"] += args.in_tokens
ledger["out_tokens"] += args.out_tokens
ledger["total_usd"] = round(ledger["total_usd"] + this_cost, 6)
save_ledger(args.ledger, ledger)
print(json.dumps({"this_call_usd": round(this_cost, 6), "ledger": ledger}, ensure_ascii=False, indent=2))
if args.cap_usd is not None and ledger["total_usd"] > args.cap_usd:
sys.stderr.write(f"[CAP] 累積 {ledger['total_usd']} USD > 上限 {args.cap_usd} USD — ブロック\n")
return 2
return 0
if __name__ == "__main__":
sys.exit(main())
入力例と結果です。空の状態で入力1,200・出力800トークンを記録すると、今回の呼び出しコストは1200/1000*0.003 + 800/1000*0.015 = 0.0036 + 0.012 = 0.0156 USDです。
{
"this_call_usd": 0.0156,
"ledger": {
"calls": 1,
"in_tokens": 1200,
"out_tokens": 800,
"total_usd": 0.0156
}
}
--cap-usd 0.01を併せて与えると、累積0.0156が上限0.01を超えるため、終了コード2で次の呼び出しを止めます。これが「事後ではなく事前に防ぐ」の実際の動作です。
引用・参照した資料の出典を自動で記録するスクリプトです。後から出典をたどれるように残します(24.5.4)。
スクリプトをたくさん作ることよりも、作ったスクリプトが信頼できる形で回り続けることのほうが重要です。以下の5つの原則は、上のすべてのスクリプトに共通して適用されます。
| 原則 | 説明 |
|---|---|
| 単純さ | 複雑なライブラリを避ける |
| テスト | すべてのスクリプトに単体テスト |
| 出力標準 | violation_listなどの標準 (10.1.7) |
| バージョン管理 | git |
| ユーザーによるレビューゲート | 自動化にも人によるレビュー |
特に最後の原則が重要です。自動化は人を置き換えるものではなく、人の判断の前段階を減らすものです。検証・抽出・生成のいずれであっても、最終適用の前に人が一度見るゲートを必ず置きます。
「テスト」の原則を言葉だけで終わらせず、G.2.1の中核関数find_duplicate_idsを標準ライブラリunittestで検証する実際のテストを置きます。外部依存がないため、そのまま保存してpython -m unittest test_integrity_check -vで回せます。検証したい関数がファイル入出力から分離されていてこそ、このように簡単にテストできるという点がポイントです(だからこそG.2.1では検査ロジックとload_rowsを分けておきました)。
# test_integrity_check.py
import unittest
from integrity_check_id_uniqueness import find_duplicate_ids
class TestFindDuplicateIds(unittest.TestCase):
def test_no_duplicates_returns_empty(self):
rows = [{"id": "Q001"}, {"id": "Q002"}]
self.assertEqual(find_duplicate_ids(rows, "id"), [])
def test_one_duplicate_reports_row_numbers(self):
rows = [{"id": "Q001"}, {"id": "Q002"}, {"id": "Q001"}]
self.assertEqual(
find_duplicate_ids(rows, "id"),
[{"id": "Q001", "row_numbers": [2, 4]}],
)
def test_missing_column_treated_as_empty_string(self):
rows = [{"name": "a"}, {"name": "b"}]
result = find_duplicate_ids(rows, "id")
self.assertEqual(result, [{"id": "", "row_numbers": [2, 3]}])
if __name__ == "__main__":
unittest.main()
実行すると、3つのテストがすべて通ります。
test_missing_column_treated_as_empty_string ... ok
test_no_duplicates_returns_empty ... ok
test_one_duplicate_reports_row_numbers ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
上の原則のうち抜け落ちやすいのが、フックの失敗処理です。コミット前や保存時に自動で回るフックは、本来の作業(コミット・保存)の脇役であるべきです。ところがフックが内部エラーで0以外の終了コードを返すと、そのフックを結び付けた本来の作業まで丸ごと止まってしまいます。補助装置が本体を人質に取る格好です。だから補助的な性格のフックは、内部で何が起きても警告だけを標準エラー(stderr)に残し、終了コードは0を返して、本来の作業を止めないように作ります。次がその最小形で、内部で例外が出ても終了コードは0です。
import sys
def run_hook():
raise RuntimeError("内部エラー発生")
def main():
try:
run_hook()
except Exception as exc:
sys.stderr.write(f"[hook] 警告: {exc} — 本来の作業は止めない\n")
return 0 # 補助フックは何があっても本来の作業を止めない
if __name__ == "__main__":
sys.exit(main())
実行すると警告は見えますが、終了コードは0です。つまり、人は何が狂ったのかを知ることができ、作業の流れは途切れません。
[hook] 警告: 内部エラー発生 — 本来の作業は止めない
(終了コード 0)
ただし、この「静かな失敗」は補助フックにだけ使います。G.2の品質ゲートのように、通過するかどうか自体が目的の検証は、逆に失敗時には0以外のコード(先ほど見たexit 1)を返してパイプラインを止めなければなりません。同じフックの置き場所でも、「補助」か「ゲート」かによって終了コードの方針が正反対になるという点を区別します。
前節のexit 0方針には、代償が1つあります。補助フックが何があっても本来の作業を止めないということは、裏を返せばフックが静かに死んでも、本来の作業は何事もなく回り続けるということです。コンテキスト自動注入のように脇で回るフックは、何日動かなくても作業の流れに赤信号が灯りません。だから補助フックには、「失敗しても止めない」と併せて、「失敗を人が遅ればせながらでも見る」という相棒の仕掛けが必ず付いていなければなりません。相棒が欠けると、ある日の振り返りで「このatom、最近一度も出てこないな」と気づいて初めて、フックが1週間死んでいたことを知ることになります。
その相棒がログです。前節の最小形(sys.stderr.write(...))が残す警告を揮発させず、ファイルに落として、正常な呼び出しは1行、失敗した呼び出しは理由とともに1行を残します。著者の環境では、この痕跡が~/.claude/hooks/_injection_log.txtに積もります(同じログを§21.3.4の発動検証でも読みます)。運用ループは大げさなものではありません。3段階の点検・復旧手順を一周すれば十分です。
| 段階 | 何を見るか | 何をするか |
|---|---|---|
| 検知 | ログで最近の正常注入の行が途絶えていないか、同じ理由の失敗行が繰り返されていないか | 週次の振り返りでログの末尾を一度ざっと見る(自動キャプチャ1行で十分) |
| 切り分け | 失敗の理由がフック自体のバグか、入力データ(壊れたmanifest・存在しないatomファイル)か | stderrの理由文字列で2つを切り分ける(コードの問題ならコード、データの問題ならmanifest) |
| 復旧 | トリガーで再び正常注入が出るか | 直した後、新しいセッションで意図したトリガーを一度入力し、ログに正常な行が再び残るかを確認する(§21.3.4の発動検証と同じ) |
核心は、「検知」を人の注意力ではなくログ1ファイルと振り返りの1行に任せるという点です。exit 0が防いだのは本来の作業の中断であって、失敗の隠蔽ではありません。失敗はstderrからログへと顕在化させ、振り返りがそのログを定期的にのぞき、復旧は普段使っている発動検証をそのまま再利用します。こうして「止めない+顕在化させる+定期的に見る+同じやり方で復旧させる」がワンセットになって初めて、静かな失敗が静かな放置として固まらずに済みます。
この事例集のコードは2種類です。1つは、G.1.1・G.2.1・G.3.1・G.7.1・G.8のように、会社の資料とは無関係な汎用の骨格として新たに書き起こし、そのまま実行できることを検証したコードです。標準ライブラリのみを使い、上に記した入力例・出力・終了コードはすべて実際に動かして確認した結果です。コピー&ペーストしてすぐ使い、単価表や列名のようなプレースホルダー値だけを自分の環境に合わせて変えれば済みます。
もう1つは、残りの節のように名前・役割・関連する本文の節だけを記した項目です。こちらを完全なコードとして載せなかった理由は、正直に言って2つあります。第一に、会社の運用スクリプトの原本は会社のIPなので、そのまま移すことはできません。第二に、そのロジックの多くは会社固有のデータスキーマ・フォルダ構造・決定カードの様式に縛られており、その前提を取り除くと、一般の読者にそのまま役立つコードが残らないからです。だから、きれいに一般化できる4つ(フォーマットチェック・整合性チェック・関係図・コストトラッカー)だけを実コードに昇格し、残りは骨格のままにしました。読者の皆さんはこの4つを手本に、同じやり方(検査ロジックと入出力を分離し、標準出力でviolationの一覧を出し、単体テストを付けるやり方)で、自分の環境に合わせた実装を自分の手で作ってみてください。
既存のツールを持ってきて変奏する手順は、付録Bを参照してください。
長く働いてきたプランナーには、数十年分の作業資料が積み上がります。議事録、決定の記録、振り返り、学習ノート、そして失敗から得た教訓まで。この付録では、その資料を新しいプロジェクトでどう再利用するかを扱います。核心となる緊張関係は一つです。資料の相当部分は会社のIPなので勝手に持ち出せない一方で、その中にはどこでも通用する個人の学習が混ざっています。この二つを切り分けることが、再利用の出発点です。
この付録の使い方は、ご自身の状況によって異なります。古い資料を新しいプロジェクトに持ち込もうとしている状況なら、H.2(分離の原則)とH.3(手順)を順番にたどってください。いざ移すときに事故が起きないか心配なら、H.5(五つの落とし穴)を先に読んで、あらかじめ回避してください。まだキャリアの初期で蓄積した資料が多くないなら、H.6を見て、今から何をどう残すかを決めてください。
ここで扱う原則は、大げさな資産管理論ではありません。「具体的なものは会社に置き、抽象的なパターンだけを持ち出す」という一文に凝縮されます。残りは、その一文を実際の状況に適用する方法です。
まず、どのような資料が積み上がるのか、それぞれの保管権限がどう異なるのかを見ます。保管権限が異なれば、再利用できる範囲も変わってくるからです。
| 資料 | 保管 |
|---|---|
| 議事録(会社資料) | 会社の権限内 |
| 決定カード(会社資料) | 会社の権限内 |
| 四半期の振り返り(個人+会社) | 個人のコピー可 |
| 学習ノート(個人) | 個人で永久保管 |
| 事故の記録(個人の学習) | 個人で永久保管 |
議事録と決定カードは、会社の権限の中にとどまります。振り返りは個人のコピーを持つことができ、学習ノートと事故の記録は完全に個人の資産です。長く蓄積された資料はそれ自体が大きな学習資産ですが、会社のIP領域と個人領域の境界を曖昧にしてはいけません。境界を明確にするほど、安心して再利用できます。
分離の基準は「具体的か、抽象的か」です。具体的な成果物は会社のものであり、それを生み出した思考パターンは個人のものです。同じ作業から二つの側面が一緒に生まれるという点が核心です。
| 領域 | 会社IP | 個人の学習 |
|---|---|---|
| 決定の内容 | 会社 | — |
| 決定のパターン(こういう状況ではこういう決定がうまくいく) | — | 個人 |
| ゲームデータ | 会社 | — |
| 運用ノウハウ(ルールブック・ツールの運用) | — | 個人 |
| コード | 会社 | — |
| アルゴリズム・構造 | — | 個人 |
「どんな決定を下したか」は会社のIPですが、「こういう状況ではこういう決定がうまくいく」というパターンは個人の学習です。ゲームデータの値そのものは会社のものですが、そのデータを運用してきたノウハウは個人のものです。具体的な資料は会社に置き、抽象的なパターンだけを持ち出す — これが分離の原則です。
分離の原則を実際の作業に落とし込むと、次の五つのステップになります。資料を識別し、IPを切り離し、学習を抽出し、一般化したうえで、新しいプロジェクトに適用します。
flowchart TD
A["過去資料の識別"] --> B["会社IP部分の分離"]
B --> C["個人学習部分の抽出"]
C --> D["抽象化・一般化"]
D --> E["新プロジェクトへの適用"]
classDef pass fill:#dcfce7,stroke:#16a34a,color:#14532d;
class E pass;
この手順は、必ず会社の権限確認と法務レビューを経てから進めます。抽象化が十分であっても、出発点が会社資料だったのなら、手続き上の確認を取っておくほうが安全です。
最も身近な再利用の事例は、本書そのものです。本文の随所は著者の過去の作業から出発しており、上記の手順を経て一般化・匿名化した結果です。
| 領域 | 出典 | 再利用 |
|---|---|---|
| Layer統合設計(第6部) | 著者の長年の運用 | 個人の学習 → 一般化 |
| 議事録システム(第17部) | 著者のプロジェクトAでの運用 | 会社のパターン → 匿名化 |
| 運用ノウハウ(第24部) | 長年の蓄積 | 個人の学習 → 一般化 |
| 付録Aのインベントリ | 会社のプロジェクトA | 匿名化+一部加工 |
Layer設計と運用ノウハウは個人の学習を一般化したもので、議事録システムと付録Aは会社のパターンを匿名化したものです。すべての項目が会社の了解を通過しており、会社のIPは漏れなく匿名化しました。本という成果物そのものが、H.3の手順の実証というわけです。
再利用は、うまくやれば資産になりますが、誤れば事故になります。以下の五つの落とし穴は実際によく踏んでしまうポイントで、それぞれに処方箋を付けました。
会社の了解なしに資料を使うと、紛争に発展します。処方箋は単純です。使う前に、まず会社の了解を取ります。
会社名や実名が一か所でも残っていれば、IP事故になります。処方箋は自動grepチェックです。会社名・実名・パスをwatchlistにまとめ、機械に漏れなく走査させます。
昔のノウハウに手を加えず、そのまま使うと、今の時点には合いません。処方箋は、時代に合わせて再構成することです。原理は生かしつつ、ツールと文脈は現在のものに更新します。
具体的な事例だけを持ち込むと、別の環境に適用しにくくなります。処方箋は、抽象的なパターンと具体的な例をセットで置くことです。パターンで一般性を、例で理解を押さえます。
資料がどれだけ多くても、読み返さなければ、ないのと同じです。処方箋は定期的な学習サイクルです。日次・週次・月次の振り返りのように、資料に再び出会う周期を作ります。
この原則は著者だけのものではありません。読者もご自身のキャリアの資料を、同じ方法で再利用できます。以下は、今から始められるおすすめの習慣です。
| おすすめ | 理由 |
|---|---|
| 四半期ごとの自分の決定の振り返り | パターンの発見 |
| 学習ノートの別保管 | 会社IPとの分離 |
| 抽象パターンの明示 | 将来の再利用が可能 |
| メンタリング・社外発表 | パターンの共有 |
| 本・ブログ(会社の了解後) | 学習が永く残る |
四半期ごとに自分の決定を振り返るとパターンが見えてきますし、学習ノートを会社資料と分けて保管しておけば、あとから安心して取り出せます。そのパターンをメンタリング・発表・執筆として外に出せば、学習は一度使って消える代わりに、長く残ります。結局のところ、自分の学習がそのまま自分の資産なのです。
7.2で扱ったBehaviorTreeエディターの発展事例です。内製の意思決定・実装・運用の経験を扱います。
7.2.8で扱った4つの決定根拠の詳細です。
| 根拠 | 詳細 |
|---|---|
| diff・gitでの追跡が必須 | UEのBTは.uassetのバイナリで、変更の追跡が困難。JSONならテキストdiffが可能 |
| subtree参照+影響追跡 | 100〜300体のBT運用ではsubtree単位の影響分析が決定的 |
| シミュレーションによる検証 | ビルドなしでBTのみを分離実行可能 |
| AIによる作成補助 | LLMがJSONのBTを自然に生成・解釈 |
[1. 企画・要求定義(1〜2週間)]
- 4つの要求事項の明文化
- JSONスキーマ設計
[2. ランタイム実装(3〜4週間)]
- JSONパーサー
- BT実行エンジン
- subtree参照の解決
[3. エディター実装(4〜6週間)]
- JSONエディター(グラフィカル)
- subtreeライブラリUI
- 影響分析ツール
[4. シミュレーター(2〜3週間)]
- BTの分離実行
- 統計の抽出
[5. AI統合(2〜3週間)]
- LLM補助によるBT作成
- コンテキスト注入
[6. UE統合(2〜4週間)]
- UEのBTとの変換
- ビルド統合
合計でおよそ4〜6か月、開発者1〜2名です。
このエディターはR&D段階の社内ツールであり、「1年の運用実測」と呼べるほど長期・大規模に稼働させたわけではありません。そのため本書の原則どおり、ここではでっち上げた運用統計を載せません。I.1で扱った100〜300体という規模は、内製を正当化した設計目標であって、測定された結果ではありません。
設計で固定した上限のうち、実際にコードへ組み込まれたのはsubtree参照のdepth 5の上限です(無限再帰の防止)。運用中のBT数やシミュレーション実行回数のような値はプロジェクト規模によって変わるため、でっち上げた数を書く代わりに、ご自身の環境で直接測定することをおすすめします。
| 事故 | 学び |
|---|---|
| subtreeの無限参照(再帰) | 参照depth 5の上限 |
| シミュレーションと実際の挙動の差 | シミュレーション環境を毎月補正 |
| LLMが出力したBTのハルシネーション | 検証+プランナーによるチェックの強化 |
| BT数の急増(計画外) | 四半期ごとの整理サイクル |
開発・運用コストは、このツールを作る際に組んだスケジュールに基づく推定です。「効果」の側は測定値ではなく、導入で狙った方向性です。でっち上げた削減数値の代わりに、方向性だけを書きます。
| 項目 | 値 | 性格 |
|---|---|---|
| 開発コスト | 開発者4〜6か月 | 計画スケジュール(推定) |
| 運用コスト | 開発者が四半期に1〜2週間(保守) | 計画スケジュール(推定) |
| 狙った効果 — 運用人員 | 敵NPCを大規模に運用する際、BT担当人員を圧縮 | 方向性(未測定) |
| 狙った効果 — 事故 | subtree再帰やLLMのハルシネーションのようなBT事故を構造的に削減 | 方向性(未測定) |
導入コストの回収期間はプロジェクトのNPC規模や人件費によって分かれるため、上記の項目をご自身の環境で測定して判断することをおすすめします。「1年以内に回収できる」といった断定はしません。その数字を私たちは持っていないからです。
7.2.6のプロンプトを使用します。以下は出力の構造を示す例です(実際の運用データではなく、形式の例です)。
{
"bt_id": "bt_new_mage_v1",
"category": "ranged_combatant",
"tags": ["scholar_faction", "ranged", "magic"],
"root": {
"type": "selector",
"children": [
{
"type": "sequence",
"name": "low_hp_retreat",
"children": [
{"type": "condition", "fn": "hp_below", "param": 0.3},
{"type": "subtree_ref", "id": "subtree_retreat_to_ally"}
]
},
{
"type": "sequence",
"name": "magic_attack",
"children": [
{"type": "condition", "fn": "enemy_in_range", "param": 15},
{"type": "subtree_ref", "id": "subtree_magic_attack_pattern"}
]
}
]
}
}
プランナーがチェックしたうえで、シミュレーション → 合格 → ビルド反映という流れです。
BehaviorTreeの内製は、運用規模100体以上で正当化されやすいです(設計上の判断)。それ以下であれば、UE標準のBTで十分です。
代替オプション: - BehaviorTree.CPP(オープンソース、標準) - Behavior Designer(外部の商用ツール) - 内製(自由度は最高、運用負担あり)
選択基準は7.2.8を参照してください。
本文に登場する略語と、本書固有の用語を一か所にまとめました。本文では各略語が初めて登場する箇所で一度だけ展開して書いていますが、順番どおりに読まない場合や、途中で忘れてしまった場合は、ここですぐに調べられます。一つの略語が文脈によって異なる意味を持つ場合は、両方を記載しました。
この用語集は次の順序でまとめています。チーム規模の等級 → ゲーム企画ドキュメント → ゲームドメイン → データ・運営 → AI・ツール → UI・アクセシビリティ標準 → ファイル・フォーマット。探している略語の性質を先に思い浮かべると、どのグループにあるかが絞り込めます。たとえばDPS・TTKは「ゲームドメイン」、KPI・DAUは「データ・運営」、atom・JITは「AI・ツール」のグループです。
表記ルールは三つです。①一般的な略語は、正式名称と日本語の意味を併記しました。②atom・Wrapperのように本書だけで使う固有の用語は、正式名称の欄に「(本書固有の用語)」と表示しました。③PK(戦争文脈のPlayer Kill ↔ データ文脈のPrimary Key)のように一つの略語が二つの意味を持つ場合、本文では初出時にどちらの意味かを併記し、この表には両方の意味を載せました。
本書では、チームの人数を特定の数字に固定せず、次の三つの等級で表記します。同じ手法でも、チーム規模によって導入の深さが変わるためです。
| 等級 | 人数の目安 | 説明 |
|---|---|---|
| 小規模 | 〜10人 | 1人・趣味の開発者から一桁規模のチームまで。たいてい1〜2段階の導入で十分 |
| 中規模 | 10〜50人 | 本書の運用事例が生まれた著者のチームが属する区間。標準化・整合性自動化の累積効果がはっきりしてくる規模 |
| 大規模 | 100+ | 複数のパート・複数のチーム。専用インフラと専任の運用が正当化される規模 |
本文で「中規模(10〜50人)チーム」のように等級と人数の範囲を併記している箇所は、この表を基準とします。1人・個人開発のように人数そのものが意味を持つ箇所では、等級の代わりに正確な数をそのまま使います。
| 略語 | 正式名称 | 意味 |
|---|---|---|
| GDD | Game Design Document | ゲームデザインドキュメント。システム・数値・動作を確定した詳細仕様書 |
| CDD | Concept Design Document | コンセプトデザインドキュメント。GDD以前の段階の初期企画書(方向性・コンセプト) |
| TF | TaskForce | 短期目標のために一時的に編成した専任チーム(例:戦闘TF) |
| DD | Design Director | デザインディレクター。ゲームの設計方針を統括するリード役 |
| RnD | Research and Development | 研究・開発。プロトタイプ・新手法を探索する段階・組織(例:プロシージャル生成RnD) |
| 略語 | 正式名称 | 意味 |
|---|---|---|
| NPC | Non-Player Character | プレイヤーが操作しないキャラクター |
| HUD | Heads-Up Display | ゲーム画面に重ねて表示する状態情報(体力・ミニマップなど) |
| DPS | Damage Per Second | 1秒あたりのダメージ量 |
| GCD | Global Cooldown | グローバルクールダウン。スキルを一つ使うと、すべてのスキルが短時間まとめてロックされる共用の待機時間 |
| TTK | Time To Kill | 対象を倒すのにかかる時間 |
| PK | Player Kill | (戦争・PvP文脈)プレイヤー間の戦闘・キル |
| BT | BehaviorTree | ビヘイビアツリー。NPCのAIの行動分岐をツリーで定義した構造 |
| FSM | Finite State Machine | 有限ステートマシン。状態と遷移で行動を定義するモデル |
| PCG | Procedural Content Generation | プロシージャルコンテンツ生成。ルール・アルゴリズムでコンテンツを自動生成 |
| VFX | Visual Effects | ビジュアルエフェクト |
| SFX | Sound Effects | 効果音 |
| VA | Voice Actor | 声優 |
| RPG / MMORPG | (Massively Multiplayer Online) Role-Playing Game | ロールプレイングゲーム / 大規模多人数同時参加型オンラインRPG |
| P2W / P2E | Pay To Win / Play To Earn | 課金で強くなる構造 / プレイで収益を得る構造 |
| RMT | Real Money Trading | ゲーム内財貨の現金取引 |
| 略語 | 正式名称 | 意味 |
|---|---|---|
| KPI | Key Performance Indicator | 重要業績評価指標 |
| DAU | Daily Active Users | 1日あたりのアクティブユーザー数 |
| FK | Foreign Key | 外部キー。他のシートの主キーを参照するカラム |
| PK | Primary Key | (データ文脈)主キー。行を一意に識別するカラム |
| ROI | Return on Investment | 投資対効果(回収) |
| MECE | Mutually Exclusive, Collectively Exhaustive | 相互排他・全体網羅。漏れなく、ダブりなく分ける分類原則 |
| STT | Speech-to-Text | 音声をテキストに変換 |
| VBA | Visual Basic for Applications | Excelに組み込まれたマクロ言語 |
| SVN | Subversion | ファイルのバージョン管理システム |
| telemetry | (計測データ) | ゲームのビルド・実行から自動収集するプレイログ・指標(入力・戦闘・離脱など)。読み方は「テレメトリー」 |
| 略語 | 正式名称 | 意味 |
|---|---|---|
| AI | Artificial Intelligence | 人工知能 |
| LLM | Large Language Model | 大規模言語モデル(ChatGPT・Claudeなどの基盤) |
| JIT | Just-In-Time | 必要な瞬間にだけ差し込む方式(本書では入力に合わせた記憶の自動注入) |
| MCP | Model Context Protocol | AIツールを外部サービスと連携させる標準 |
| API | Application Programming Interface | プログラム間の呼び出し規約 |
| UE | Unreal Engine | アンリアルエンジン |
| atom | (本書固有の用語) | 1決定 = 1ファイルとして固定化した、決定・ルールのカード |
| Wrapper / Cascade / Junction | (本書固有の用語) | よく使うツールのエントリーポイント / 複数のチェックを一度にまとめたツール / 本体につなぐシンボリックリンク |
| rg | ripgrep | 高速なテキスト検索コマンド(grepを代替するCLIツール)。コード・ドキュメントの全件検索に使用 |
| ClickUp | (タスク・イシュートラッカー) | 作業・スケジュールを管理するクラウド型コラボレーションツール。JIRA・Redmine・Linearも同じカテゴリー。MCPで連携してAIが照会・更新 |
| 略語 | 正式名称 | 意味 |
|---|---|---|
| UI / UX | User Interface / User Experience | ユーザーインターフェース / ユーザー体験 |
| WCAG | Web Content Accessibility Guidelines | ウェブアクセシビリティガイドライン(コントラスト比・タッチターゲットサイズなどの合格ライン) |
| HIG | (Apple) Human Interface Guidelines | Appleのインターフェースガイドライン |
| SC | Success Criterion | WCAGの個別の達成基準番号(例:SC 1.4.3) |
| pt / dp / px | point / density-independent pixel / pixel | 画面サイズの単位 |
| 略語 | 正式名称 | 意味 |
|---|---|---|
| YAML | YAML Ain't Markup Language | 人が読みやすい設定・データ記述形式 |
| JSON | JavaScript Object Notation | データ交換用の記述形式 |
| HTML / SVG | HyperText Markup Language / Scalable Vector Graphics | ウェブ文書 / ベクターグラフィックス形式 |
| GLB | GL Transmission Format (Binary) | 3Dモデルのバイナリーファイル形式 |
PKのように文脈によって意味が分かれる略語は、本文の初出時にどちらの意味かを併記しました。迷ったときはこの表に戻ってきてください。
本書の事例とツールは、ほぼすべてが一つの環境、すなわちClaude Codeを前提に書かれています。そのため、決裁の場や外部レビューでほぼ必ず出てくる指摘が一つあります。「これは特定企業のツールに縛られるのではないか」というものです。企画責任者は一つのベンダーに依存する意思決定を決裁することに負担を感じ、懐疑派はツールが変われば本書の方法が丸ごと崩れるのではないかと疑い、海外版権を検討する側は、自国で別のツールが標準であるときに本書が役に立つのかと問います。三者の表現は違いますが、本質は同じです。ベンダーロックイン(vendor lock-in)、つまり一つのツールに閉じ込められることへの不信です。
この付録の目的は、その不信に答えることです。結論から言えば、本書が勧める作業の骨格はツール中立です。特定のモデル名にも、特定のコマンドラインツールにも縛られていません。Claude Codeはその骨格を最も滑らかに実装してくれる器だったにすぎず、同じ骨格を別の器に移し替えることができます。この付録では、(1)何がツールと無関係な骨格なのかを表で示し、(2)Claude Codeの各要素を別の環境に移すと何に対応するのかを対にして示し、(3)モデルの世代は変わり続けるという前提のもとで最新を確認する原則を定め、(4)移すときに何を失い、何を守れるのかを率直に書きます。
本書全体を貫く仕事の進め方は、5つの柱に要約できます。この5つはいずれも特定のモデルやコマンドラインツールの機能名ではなく、「人とAIが一緒に働くとき、信頼できる結果をどうやって繰り返し取り出すか」への答えです。だからツールが変わってもそのまま残ります。
| 骨格 | 何か | なぜツール中立なのか |
|---|---|---|
| 標準 → テンプレート → 検証ゲート | 合意されたルール(標準)を空欄の型(テンプレート)として固め、結果がルールを守ったかを自動でふるいにかける関門(ゲート)を置く | ルール・型・チェックという概念は、どのツールでも文章・スクリプトで表現できる |
| atom = 1決定1ファイル | 一つの決定を一つの小さなファイルに書き、必要なときに取り出して使い、直すときはその1か所だけを直す | 決定を細かく分けてファイルとして置くことは、ファイルシステムさえあればできる |
| JIT注入 | いまの会話に本当に必要な決定だけを、そのつど(Just-In-Time)選んでモデルに渡す | 「必要なコンテキストだけを入れる」という原則であり、入れる方法がツールごとに違うだけ |
| 振り返りループ | 日・週・月の単位でやったことを振り返り、繰り返されるパターンを次の作業のルールへ引き上げる | 振り返って改善する手順は、ツールではなく習慣と文書で回る |
| ツール借用の境界 | 持ってくるのは骨格(アルゴリズム・構造)だけで、ドメインデータは置いてくる(付録B) | 何を持ってきて何を置いていくかの判断は、どのツールでも同じ |
この表の右の列が核心です。5つの骨格はいずれも、その定義の中に特定の製品名が一度も登場しません。登場するのはルール・ファイル・コンテキスト・習慣・境界のように、どんな作業環境にもある普遍的な概念だけです。だから「Claude Codeが使えなくなったらどうするのか」という質問は、実は「この5つの概念を別のツールでどう実装するか」という、はるかに答えやすい質問に変わります。その答えが次の節です。
Claude Codeには、上の骨格を楽に実装してくれる具体的な仕組みがあります。hook(特定のタイミングで自動実行されるスクリプト)、MCP(外部のツール・データをモデルにつなぐプロトコル)、settingsファイル(権限・環境の設定)、スラッシュコマンド(よく使う手順を1行で呼び出すショートカット)、スキル(再利用できる作業のまとまり)などです。これらはClaude Code固有の名前ですが、その役割には他の環境にもほぼすべて対応物があります。下の表がその対です。
| Claude Code | ChatGPT(ウェブ・アプリ) | Cursor / Copilot | 汎用LLM API |
|---|---|---|---|
| hook(タイミング指定の自動実行) | 会話前後の手動手順 / カスタムGPTの指示文 | エディター作業前後のタスク・pre-commitフック | 呼び出しの前後に挟む事前・事後スクリプト |
| MCP(外部連携のプロトコル) | プラグイン / アクション / コードインタープリター | 拡張機能(extension) / 組み込みツール呼び出し | 関数呼び出し(function calling) / 自作のAPIラッパー |
| settingsファイル(権限・環境) | カスタムGPTの設定画面 / プロジェクト設定 | .cursor・ワークスペース設定ファイル |
コード内の設定オブジェクト / .env・YAML設定ファイル |
| スラッシュコマンド(手順の短縮) | 保存したプロンプト / カスタムGPT | スニペット / ユーザー定義コマンド | プロンプトテンプレート関数 |
| スキル(再利用できる作業のまとまり) | カスタムGPT / プロンプト集 | ルールファイル + スクリプト | モジュール化したプロンプト・コード関数 |
| CLAUDE.md / メモリー | カスタム指示 / メモリー機能 | プロジェクトのルールファイル(rules) | システムプロンプト + 外部メモリーストア |
| atomファイル群 | (ツール非依存)Markdownファイル | (ツール非依存)リポジトリ内のMarkdown | (ツール非依存)ファイル・DBレコード |
表を見ると一つのことがはっきりします。右へ行くほど、つまり汎用LLM APIに近づくほど、「自動でやってくれていたこと」が「自分で作って組み込むこと」に変わります。Claude Codeならhookの1行で済んでいた自動注入が、汎用APIでは呼び出し前に自分で書く事前スクリプトになります。自動化の利便性は減りますが、骨格そのものはそのまま移っていきます。つまり移植は「機能を失うこと」ではなく、「利便性を自分の手で敷き直すこと」です。
flowchart LR
subgraph 도구중립["ツール中立の骨格(変わらない)"]
S[標準→テンプレート→検証]
A[atom: 1決定1ファイル]
J[JIT注入]
R[振り返りループ]
end
subgraph 구현["環境別の実装(差し替える)"]
CC[Claude Code: hook·MCP·settings]
GPT[ChatGPT: プラグイン・カスタムGPT]
CUR[Cursor・Copilot: 拡張・ルールファイル]
API[LLM API: 関数呼び出し・事前スクリプト]
end
도구중립 --> CC
도구중립 --> GPT
도구중립 --> CUR
도구중립 --> API
classDef code fill:#dbeafe,stroke:#2563eb,color:#0b2545;
classDef ai fill:#f3e8ff,stroke:#9333ea,color:#3b0764;
classDef human fill:#fde68a,stroke:#b45309,color:#000;
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class J code;
class CC,GPT,CUR,API ai;
class R human;
class A data;
この図が、付録全体の1枚要約です。上の箱(骨格)はどの環境へ矢印が伸びても中身が変わらず、下の箱(実装)だけが環境に合わせて差し替えられます。決裁の場で「ベンダーロックイン」という言葉が出たら、この図を1枚広げて「縛られるのは下の段であって、上の段ではない」と答えればよいのです。
移植を語るとき、最も早く古くなる情報がモデル名です。本書を書いている時点の最新モデル名を本文に打ち付けて固定すると、次の世代が出た瞬間にその文は誤った情報になります。だから本書は最初から一つの原則に従っています。特定モデルの名前や世代番号に寄りかかって説明するのではなく、モデルが果たす役割(推論・要約・コード生成といった機能)に寄りかかって説明する、ということです。
| 変わるもの(決め打ちしないこと) | 変わらないもの(頼ってよいもの) |
|---|---|
| モデルの製品名・世代番号 | 「推論が得意なモデル」「長いコンテキストを受け取れるモデル」といった役割の区分 |
| コンテキスト上限の具体的な数値 | 「上限がある以上、本当に必要なコンテキストだけを入れる」というJITの原則 |
| 価格・速度の具体的な数値 | 「高くつく作業はゲートを通過したものだけ回す」というコスト意識 |
| 特定機能のオン・オフの方法 | 「その機能が果たす役割」と、それを代替する骨格 |
実務で最新のモデル・機能を確認する方法も、ツールごとに1行で済みます。Claude Codeでは/modelコマンドで現在使っているモデルと選択肢をすぐ確認できますし、ChatGPTやCursorのようなツールも、設定画面やモデル選択のドロップダウンで同じ情報を見せてくれます。したがって、本書のどこかの文がモデル名と食い違って見えるなら、その文が間違っているのではなく、モデルが1世代進んだのです。役割さえ同じなら、方法はそのまま適用できます。読んでいて見慣れないモデル名に出会ったら、本文を疑う前に、まず/modelのようなコマンドで、いま手にしているツールの最新状態を確認することをお勧めします。
ツールを移せば、確かに失うものがあります。その事実を隠すとかえって信頼を失うので、何を失うのかを先に率直に書きます。ただし、失うものはほぼすべて「利便性」の領域で、守られるものは「骨格」の領域です。つまり失うものは敷き直せば取り戻せるものであり、守られるものはそもそもツールに縛られていなかったものです。
| 区分 | 項目 | 説明 |
|---|---|---|
| 失うもの(利便性) | 自動実行の滑らかさ | hookのように勝手に割り込んでくれていた自動化を、事前・事後スクリプトとして自分で作る必要がある |
| 失うもの(利便性) | 統合された一つの画面 | コマンド・ツール・ファイルが一つの流れにまとまっていたものを、複数のツールに分けて貼り付けることになりうる |
| 失うもの(利便性) | すぐ使えるスキル・コマンド | スラッシュコマンドやスキルを、そのツールのやり方で登録し直す必要がある |
| 守られるもの(骨格) | 標準・テンプレート・検証ゲート | ルールと型とチェックは文章・スクリプトなので、どこでもそのまま生きる |
| 守られるもの(骨格) | atom・JIT・振り返りループ | ファイルと習慣で回るため、ツールが変わっても維持される |
| 守られるもの(骨格) | ツール借用の境界(付録B) | 何を持ってきて何を置いていくかの判断基準は、環境と無関係 |
この表を一文に縮めるとこうなります。移植で失うのは、時間をかければ取り戻せる自動化の利便性であり、守られるのは、本書が最初からツールの外に置こうと努めてきた作業の骨格です。だから「ベンダーロックインではないのか」という問いへの最も正直な答えはこれです。縛られる部分はありますが、それは差し替えられる器であり、本当の価値である中身は、最初からどの器にも縛られていません。この付録の一編が、決裁の場でその答えを代弁してくれることを願っています。
この付録は「1人・6か月のシステムを中規模チームに拡張するとき、導入工数・運用コスト・アカウント・社内ネットワークのセキュリティを、何でどう見積もるのか」というスタジオのPD・代表の問いに答えるための、空欄記入式のワークシートです。本文19.3(AI導入戦略と経営陣の説得)が「ROIを加工してはいけない」と述べたとすれば、この付録はその原則を導入コスト側にもそのまま適用します。つまりこの付録は数字を提供しません。すべての欄は空欄であり、その欄を埋めるのはあなたのチームの測定・推定であり、
[経理確定が必要]と表示された欄は、経理が埋めるまで誰も推定で埋めません。
この付録の使い方は次のとおりです。まずL.1で、TCO(Total Cost of Ownership、総所有コスト)がどんな項目に分かれるのかを図でつかんでください。次にL.2〜L.6の5つのワークシートを自分のチーム規模の行に合わせて空欄のまま出力し、直接測定するか、経理・情報セキュリティ担当に1行の質問として渡してください。最後にL.7の自己点検表で、抜けている欄がないかを確認すれば完了です。この付録の価値は、埋められた数字ではなく、見落としやすいコスト項目をあらかじめ欄として用意しておくことにあります。
PDが最も陥りやすい罠は、導入コストを「サブスクリプション料金×人数」だけで見ることです。実際の総所有コストはそれより広いものです。一度払って終わる導入工数(環境構築・標準化・オンボーディング)と、毎月繰り返される運用コスト(ライセンス・トークン・インフラ・管理人件費)に分かれ、その上に目に見えないセキュリティ・アカウント管理のコストが乗ります。
flowchart TB
TCO["チーム導入TCO"] --> A["一回限り: 導入工数
(環境構築・標準化・オンボーディング)"]
TCO --> B["繰り返し: 月次運用コスト
(ライセンス・トークン・インフラ・管理)"]
TCO --> C["繰り返し: セキュリティ・アカウント管理
(SSO・監査・キー回収)"]
A --> A1["L.3 導入工数ワークシート"]
B --> B1["L.5 月次運用コストワークシート
[経理確定が必要]"]
C --> C1["L.4 社内ネットワーク・セキュリティ点検"]
A --> A2["L.6 オンボーディング時間ワークシート"]
B --> A3["L.2 アカウント・ライセンスワークシート"]
classDef data fill:#e2e8f0,stroke:#64748b,color:#1e293b;
class A1,A2,A3,B1,C1 data;
3つの枝のうちPDが過小評価しやすいのは、左側(導入工数)と右側(セキュリティ・アカウント)です。ライセンス料金は見積書に書かれて届きますが、「1人が6か月かけて手で積み上げた標準・スキルを、チームで共有できる形に整理する工数」と「社内ネットワークから外部LLM呼び出しをどこまで許可するかを決めるセキュリティレビュー」は見積書になく、だからこそ常にスケジュールと予算を超過させます。この付録のワークシートは、その見えないコストを、空欄としてでも先に可視化することを目的としています。
本文19.3.6は「コストの絶対値は本には載せない — 経理から受け取って埋める空欄だ」と述べました。この付録は、その空欄をどこに置くべきかを項目別に展開したものです。
最初に埋める表です。誰がどのツールを使い、その権限がどのように発行・回収されるのかを、人数とともに書き込みます。人数の欄はあなたのチームの実際の頭数で埋め、単価の欄は見積書や公開料金表から持ってきて埋めます。本書は単価を書きません。
| 項目 | 何を書くか | 誰が埋めるか | 自チームの値 |
|---|---|---|---|
| ツール別シート数 | ツールごとに必要なアカウント(シート)数 | リード | ______ シート |
| 権限等級の分布 | full / 作業別cap / 外注の単発人員(付録C.1.2) | リード | full __名 / 一般 __名 / 外注 __名 |
| シート単価 | ツール別の月額シート料金 | 経理・購買 | ______ /シート・月 |
| 共用キーの有無 | チーム共用APIキー vs 個人別キー | 情報セキュリティ | □ 共用 □ 個人別 |
| 発行手続き | 新規入社者のアカウント発行経路・所要時間 | リード | ______ |
| 回収手続き | 退職・外注終了時のキー/シート回収経路 | 情報セキュリティ | ______ |
ルールは2つです。第一に、外注・短期人員にはシートを常時発行せず、作業単位で開いて回収します(付録C.1.2)。第二に、回収手続きの欄が空欄のうちは発行を始めません。最もよくある事故は、退職者のアカウントが回収されず、コストとキー露出が一緒に漏れることなので、発行より先に回収を設計します。
「1人・6か月」がチームに拡張されるときに増える一回限りの工数を、規模別に見積もる表です。工数の欄は人日(にんにち、1人が1日働いた作業量)単位で、あなたのチームが実際に測定または推定して埋めます。本書は人日の数値を提供しません — チームの習熟度や既存標準の整理水準によって大きく変わるからです。
| 導入工数項目 | 1〜3人 | 4〜10人 | 11〜30人 | 31〜50人 | 測定/推定の主体 |
|---|---|---|---|---|---|
| 環境構築・セットアップ(ツール・hook・権限) | ___人日 | ___人日 | ___人日 | ___人日 | リード/インフラ |
| 1人の資産のチーム共有化(スキル・標準・atomの整理) | ___人日 | ___人日 | ___人日 | ___人日 | リード |
| チーム標準の策定(命名規則・フロントマター・ルールブック、付録D) | ___人日 | ___人日 | ___人日 | ___人日 | リード |
| 検証ゲートの構築(lint・ルールブックの自動化) | ___人日 | ___人日 | ___人日 | ___人日 | QA/リード |
| オンボーディング資料の作成(L.6と連動) | ___人日 | ___人日 | ___人日 | ___人日 | リード |
| 合計(導入の一回限り工数) | ___人日 | ___人日 | ___人日 | ___人日 | — |
この表を埋めるときに抜けやすいのが2行目です。1人が6か月間、頭の中と個人フォルダーに積み上げてきた資産をチームで共有するには、誰かが取り出して整理し、文書にする別途の工数がかかります。この工数を「0」と見積もると、導入スケジュールは必ず遅れます。また、規模が大きくなるほど、構築の工数より標準策定・検証ゲートの工数のほうが急な勾配で増えるという点を、欄の形であらかじめ示しています — 人が増えるほど、合意すべき標準の数が増えるからです。
本文19.3.1の段階的導入(保守的→進歩的)に従えば、この工数を1四半期で使い切らず、第1段階(コンテキスト注入)のパイロットから分散して投じられます。表の合計を一度に決裁してもらおうとせず、第1段階の工数だけを先に切り出して決裁を受けるのが現実的です。
PD・代表が最も直接的に恐れる領域です。外部LLMに何が出ていくのか、社内ネットワークから外部呼び出しをどこまで許可するのかを項目別に点検します。この表は合格/保留を判定するチェックリストであり(付録C.6のセキュリティと連動)、1項目でも未定なら、その範囲の導入を保留します。
| 点検項目 | 通過基準 | 担当 | 状態 |
|---|---|---|---|
| 外部LLMへの送信データ範囲 | センシティブデータはプレースホルダー/セルフホスティング(C.6) | 情報セキュリティ | □ 通過 □ 保留 |
| 決済・個人情報の送信 | 例外なく送信禁止を明文化 | 情報セキュリティ | □ 通過 □ 保留 |
| 社内ネットワークの外部呼び出しポリシー | 許可ドメイン・プロキシ・ログ保存期間の定義 | インフラ | □ 通過 □ 保留 |
| セルフホスティングの要否 | コアIPはセルフホスティングのモデルで処理するかを決定 | 代表/情報セキュリティ | □ 決定 □ 未定 |
| キー露出事故への対応 | 即時交換+使用履歴レビューの経路(C.7) | 情報セキュリティ | □ 通過 □ 保留 |
| 監査ログ | 誰が・いつ・何を呼び出したかの記録・保存 | インフラ | □ 通過 □ 保留 |
| 会社IPの外部流出検査 | grep watchlistなどの事前検査手順(付録B.6) | リード | □ 通過 □ 保留 |
| 外注アクセスの隔離 | 外注アカウントはコア資産へのアクセス遮断・作業別隔離 | 情報セキュリティ | □ 通過 □ 保留 |
この表でコストが最も大きく分かれる欄は4行目(セルフホスティングの要否)です。コアIPを外部LLMに絶対に送れないと決定すれば、セルフホスティングのインフラコストがL.5の運用コストに丸ごと乗ります。だからこの決定はリードではなく代表・情報セキュリティが一緒に下すべきであり、決定までL.5のインフラ欄は確定できません。2つのワークシートは、この1つの欄でつながっています。
毎月繰り返されるコストを項目別に分解した表です。この表の金額欄はすべて空欄であり、[経理確定が必要]と表示された欄は、経理が埋めるまで誰も推定で埋めません。トークン単価・サブスクリプション料金・インフラ料金はモデル・呼び出し量・契約によって毎月変わるため、本書は絶対値を書きません。
| 運用コスト項目 | 算定方式 | 誰が埋めるか | 月額 |
|---|---|---|---|
| ライセンス・サブスクリプション | シート数×シート単価(L.2) | 経理 | [経理確定が必要] |
| LLMトークンコスト | 呼び出し量×トークン単価、ツール別上限(cap)の合計 | 経理 | [経理確定が必要] |
| インフラ(セルフホスティング時) | L.4の決定に基づくサーバー・GPU・ストレージ | 経理・インフラ | [経理確定が必要] |
| バックアップ・同期 | リポジトリ・バックアップストレージ(付録C.5) | 経理 | [経理確定が必要] |
| 運用管理の人件費 | ツール・キー・ログ管理担当の時間換算 | リード・経理 | [経理確定が必要] |
| 月合計 | 上記項目の合計 | 経理 | [経理確定が必要] |
この表のルールはただ1つ、空欄を空欄のままにしておくです。本文19.3.2で、AIが運用コストの欄をもっともらしく$4,500とでっち上げた失敗を思い出してください — 人であれAIであれ、この欄を推定で埋めた瞬間、その報告は最初の質問で崩れます。代わりに、コストを統制する本当の仕掛けは金額ではなく、ツール別の月次上限(cap)がかかっていて、超過が自動で報告される構造です(19.3.6)。決裁時に経営陣に見せるべきものは、埋めた金額ではなく、「上限がかかっていて、超過が報告される」という構造と、経理が埋める空欄のリストです。
5行目(運用管理の人件費)が最も頻繁に漏れます。ツールは入れて終わりではなく、キーを回収し、ログを見て、上限を調整する人の時間を毎月食います。この欄を0のままにすると、その仕事はリードの見えない残業に隠れてしまいます。
新規メンバー1人がシステムの上で一人前の働きをするまでにかかる時間を、段階別に見積もる表です。時間の欄は、あなたのチームで実際にオンボーディングを一度行ってみて測定するのが最も正確です(本文19.3.7のベースライン測定レシピと同じ方式)。測定前は空欄のままにしておきます。
| オンボーディング段階 | 何をするか | 測定時間 | 備考 |
|---|---|---|---|
| 環境構築 | ツール・hook・アカウントのセットアップまで | ___時間 | L.2の発行手続きと連動 |
| 標準の学習 | 命名規則・フロントマター・ルールブックの習得(付録D) | ___時間 | 資料があれば短縮 |
| 最初の作業(保守的) | コンテキスト注入で最初の成果物・人によるレビュー通過 | ___時間 | 19.3.1の第1段階 |
| 検証ゲートへの適応 | lint・ルールブックのゲートに合わせて作業 | ___時間 | — |
| 独立作業への到達 | 監督なしで作業・採用判定が可能 | ___日 | オンボーディング完了の基準 |
この表を埋めると、導入工数(L.3)の「オンボーディング資料の作成」欄がなぜ重要なのかが見えてきます。オンボーディング資料がよく整理されているほど2行目・3行目の時間が短くなり、新規メンバーが増えるほどその節減が積み重なります。つまりオンボーディング資料の作成は一回限りの工数ですが、回収はメンバー数の分だけ繰り返されます。本文19.3.3が「JIT自動注入221件 — 新規メンバーも同じルールの上で作業」と述べたことが、この表では3行目の時間短縮として現れます。
最後の行(独立作業への到達)が、オンボーディングの本当の完了基準です。環境構築が終わったことをオンボーディング完了と取り違えると、監督コストがリードに積み上がり続けます。「監督なしで採用判定までできる」が基準でなければなりません。
最後に、これらのワークシートを経営陣に持っていく前に、自分で通過させるべき項目です。付録B.6(借用前チェック)と同じ精神で、1項目でも空欄があれば決裁を先送りし、その欄から埋めます。
| 点検項目 | 通過基準 |
|---|---|
| アカウント回収手続きが定義されているか | L.2の回収手続き欄が空欄でない |
| セキュリティ点検がすべて通過/決定済みか | L.4に□保留・□未定が0件 |
| 運用コストの空欄が経理に渡ったか | L.5の[経理確定が必要]が質問として送付済み |
| 導入工数を段階に分割したか | L.3の合計ではなく第1段階の工数から決裁 |
| オンボーディング完了の基準が「独立作業」か | L.6の最後の行で完了を判定 |
| 推定値を断定として書いていないか | すべての推定欄に「推定・サンプル数」を表記 |
この表を5つの欄の合格として読むのではなく、6つのロックとして読んでください。1人のシステムをチームに拡張することは確かに可能ですが、その拡張のコストはライセンス料金ではなく、この6つの空欄を正直に埋めたとき、初めて全体像が現れます。そして、どの欄もAIに埋めさせないでください — AIは本文19.3.2のように、空欄をもっともらしい数字で埋めます。AIの持ち場は、あなたが測定した値を受け取って、決裁スライドの文章に整えるところまでです。
本書の5か所 — §8.2.7(経済)・§5.4(ボイス)・§6.3(ペルソナ)・§7.3(パターン)・§13.3(データ) — には、「次元ベクトルに圧縮する」「埋め込み(エンベディング)」「ベクトル空間で近い」といった表現が方向標識として登場します。機械学習の背景がなくても、ゲーム企画の感覚だけで十分につかめる概念です。一度だけ直観を敷いておけば、その5か所がすべて同じ1枚の図として読めるようになります。
ただし、一つだけ先に釘を刺しておきます。概念の直観は易しいものの、適用の条件は重いのです。 この発想は入門ではなく — 本書がここまで一貫して積み上げてきた土台、すなわち保守的適用の検証ゲート、データ・telemetryインフラ、voice_lintや整合性チェックといった分野別チェッカー、そしてLayer統合の上にしか立てない、最も遠い先にある応用です。その土台なしに座標から描き始めると、M.4で見るとおり、その地図はゲームとずれた誤差まできれいに圧縮した虚像になります。5か所がすべて「まだ時期尚早」である本当の理由はここにあります — 発想が難しいからではなく、支える土台が先だからです。本付録は、その土台が整ったチームが「次の一歩」を見極めるときに開く地図であって、最初の一歩を踏み出すための入門書ではありません。
どんな対象でも、その特徴を数値のリストに変換して「地図」の上の1点として置くこと、それが埋め込み(embedding)です。その数値のリストがすなわち次元ベクトルです。約束は一つだけです — 似ている対象ほど地図上で近い点になるように作ります。
たとえばNPCを(口調の格式、感情表現の量、語彙の難易度、…)といった特徴の座標として置けば、似た口調のNPC同士が地図上で近くに集まります。料理レシピなら、材料の構成を座標として置くことで、似た料理が近くに集まります — §8.2.7で手がかりとして挙げたEpicureがやったことが、まさにこれです。
似て見えて正反対 — AHPと混同しないこと。 多基準意思決定に使うAHP(Analytic Hierarchy Process, Saaty)も、定性的な判断をベクトルに変換するという点では似て見えます — 基準同士を二つずつ突き合わせる一対比較で優先順位の重み(主固有ベクトル)を取り出すからです。しかし方向が正反対です。AHPは人が基準と階層を事前に定義し、その枠の中で重みを付けるトップダウンの意思決定であり、ここでの埋め込みは人の定義なしにデータからクラスターがひとりでに浮かび上がるボトムアップの発見です。§13.3が突破しようとする限界 — 「人が事前に定義したセグメント」 — こそが、まさにAHPの出発点です。両者は同じ場所ではなく、正反対の側にあります。
地図は強力ですが、タダでは手に入りません。
だからこそ本書は、次元ベクトルを処方ではなく方向標識として置いています — 検証(telemetry・シミュレーション)が固く敷かれたチームが、数年後にのぞき込む領域です。分野別の具体的な手がかりは§8.2.7(経済)・§5.4(ボイス)・§6.3(ペルソナ)・§7.3(パターン)・§13.3(データ)に散らしてあり、すべて本付録の1枚の地図の上で読めば十分です。
この付録は、本書を1学期の講義の教材として使おうとする方 — 大学・専門学校・アカデミーの教員、社内教育の担当者、勉強会のリーダー — のためのものです。1,000ページ近い単巻を学期単位に分割する作業は、思いのほか途方に暮れるものです。どの部を何週目に置くか、本文の「やってみよう」をどう課題に変えるか、提出物を何の基準で採点するか — この3つで行き詰まると、よい本でも教材としては採用されにくくなります。この付録は、その3つをそのまま書き写して使える道具としてお渡しします。
この付録の使い方は次のとおりです。まずN.1の15週進度表をご自身の学事日程に合わせて読み(16週制・短期集中学期の変形はN.2に別に置きました)、N.3の難易度バッジと前提知識の表で受講生のレベルを見極めてください。その後、N.4の採点ルーブリックをコピーして、ご自身の課題に合わせて項目だけ差し替えれば完成です。すべての表は、そのまま出力してシラバス(講義計画書)に貼れるように組んであります。
一つお断りしておくことがあります。本書のすべての章は「やってみよう」で終わります。読んで閉じる章ではなく、今日手を動かしてもらうことが本文の目標でしたが、講義ではまさにその「やってみよう」が課題の第一の材料になります。この付録の進度表に、本文の「やってみよう」を週ごとの課題へどう移すかをあわせて記したのは、そのためです。
もっとも一般的な15週制(週1回3時間を基準)の学期に合わせて組んだ標準進度表です。本書の全24部を1学期ですべて扱うことはしません — 無理に詰め込むと、どれも手元に残らないからです。代わりに、基盤(第1・2部)をしっかり敷き、分野のうち代表5〜6個を深く扱い、プロセス・運営から核心だけを選んで締めくくる構成を選びました。扱わずに残した部は「発展リーディング」と表示し、関心のある受講生が自分で開けるように案内します。
学習目標はすべて「受講生が何をできるようになるか」の動詞で書きました。「知っている」ではなく「作る・検証する・選ぶ」です。本書全体が「AIが候補を出し、人がふるいにかける」という一文を繰り返すので、目標の動詞もその分業に従います。
| 週 | 扱う部・章 | 学習目標(受講後にできること) | 課題に転換した「やってみよう」 |
|---|---|---|---|
| 1 | 1.0 始める前に + 第1部(導入) | ターミナル・アカウント・料金構造を説明し、AIツールを自分のPCにインストールして最初のセッションを開く | 1.0 インストール「やってみよう」 — インストールのスクリーンショット+最初のプロンプト・出力を提出 |
| 2 | 第2部(情報アーキテクチャ) | YAMLフロントマターで文書をデータ化し、フォルダ・命名規約を設計する | 2.1 フロントマター「やってみよう」 — 自分の文書3つにフロントマターを付与 |
| 3 | 第3部(システム企画) | スキーマ優先の原則で、マスターデータの$スキーマを先に定義する | 3.2 スキーマ「やってみよう」 — ミニシート1種の仕様書を作成 |
| 4 | 第10部(QA・整合性) | 30シートのFK整合性をコードで検査するツールを、本文に沿って作る | 10.1 整合性検証「やってみよう」 — N.4のルーブリックで採点する中核課題 |
| 5 | 第4部(戦闘)+ 第8部(バランス) | 戦闘の数値をLayerに分解し、決定論的なバランス公式をルールブックとして置く | 8.1 バランス公式「やってみよう」 — ダメージ公式1種+シミュレーション |
| 6 | 第5部(ナラティブ) | NPCセリフのvoice_profileを作り、voice_lintでトーンの逸脱を捕まえる | 5.2 voice_profile「やってみよう」 — キャラクター1人のボイスプロファイル |
| 7 | 第6部(コンテンツ)+ 第7部(レベル) | プロシージャル生成の2つの軸(ルール・AI)を区別し、コンテンツ候補を量産してチェックする | 6.2 生成器「やってみよう」 — コンテンツ候補10件の生成+チェックログ |
| 8 | 中間点検・発表 | 第1〜7週の課題を統合し、自分のミニプロジェクトとしてデモする | 中間課題発表(第3〜6週の成果物の統合デモ) |
| 9 | 第9部(UX・UI)+ 第14部(モバイル) | HUDをlintにかけて視線の逸脱・コントラスト不足を捕まえ、PCのHUDをモバイルへ圧縮する | 9.1 HUD lint「やってみよう」 — 画面1種のlintレポート |
| 10 | 第16部(コミュニケーター)+ 第17部(議事録) | 隔離された作業空間で決定だけを正本化し、議事録を構造化する | 17.x 議事録「やってみよう」 — 実際の会議の書き起こし1件を構造化 |
| 11 | 第18部(意思決定)+ 第19部(チームリード) | 決定を追跡可能なカードとして残し、ビジョンを決定の採点表に変える | 18.1 意思決定追跡「やってみよう」 — 決定カード3枚を作成 |
| 12 | 第20部(コラボレーションメモリー)+ 第21部(自己改善) | 協業の文脈をメモリーとして運用し、振り返りを自己改善ループとして回す | 21章 振り返り「やってみよう」 — 1週間の振り返り1件+抽出ルール1個 |
| 13 | 第22部(ガバナンス) | プロンプト・ハルシネーション・コスト・法務・倫理の境界を点検し、ルールを立てる | 22.1 プロンプト「やってみよう」 — 作業指示書1枚+ハルシネーション点検手順 |
| 14 | 第23部(個人開発)+ 第24部(運営の深掘り) | 一人ミニ版でツールを移し、整合・リンク・staleをコードで検証する | 24.1 検証「やってみよう」 — 自分のプロジェクトの検証スクリプト1種 |
| 15 | 期末プロジェクト発表・評価 | 学期全体を貫く自分のワークフロー1つを設計・デモ・検証する | 期末課題発表(N.4の拡張ルーブリックで評価) |
発展リーディング(講義には含めない・自主学習を推奨): 第11部(キャラクター・ペット・乗り物)、第12部(アートディレクション)、第13部(データ・KPI)、第15部(運営・ライブオプス)。この4つの部は分野特化の度合いが強いため、関心のある受講生が自分の分野に合わせて開けるように残しました。付録F(事例索引)を道しるべに使えば、自分の環境に近い事例から逆方向にたどっていくことができます。
進度の流れをひと目で見ると次のとおりです。基盤 → 分野の深掘り → 中間統合 → プロセス・運営 → 期末統合という、2つの山(中間・期末)を持つ構造です。
flowchart LR
subgraph A["基盤(1〜3週)"]
W1["第1週
インストール・導入"] --> W2["第2週
情報アーキテクチャ"] --> W3["第3週
システム・スキーマ"]
end
subgraph B["分野の深掘り(4〜7週)"]
W4["第4週
QA・整合性"] --> W5["第5週
戦闘・バランス"] --> W6["第6週
ナラティブ"] --> W7["第7週
コンテンツ・レベル"]
end
M1{{"第8週
中間発表"}}
subgraph C["プロセス・運営(9〜14週)"]
W9["第9週
UX・モバイル"] --> W10["第10週
コミュニケーション"] --> W11["第11週
意思決定・リード"] --> W12["第12週
協業・振り返り"] --> W13["第13週
ガバナンス"] --> W14["第14週
個人開発・検証"]
end
M2{{"第15週
期末発表"}}
A --> B --> M1 --> C --> M2
classDef human fill:#fde68a,stroke:#b45309,color:#000;
class M1,M2 human;
学校によって学期の長さは異なります。標準の15週のほかに、もっともよく出会う2つの変形の調整案を置いておきます。中核課題(第4週の整合性検査)と2つの発表の山は、どの変形でも維持することをお勧めします — 本書の誠実さの原則(「効果ではなく構造を見せる」)がもっともよく表れる場所だからです。
| 学期形態 | 調整方法 |
|---|---|
| 16週制 | 標準の15週+第16週に補講・再評価週を追加。期末課題の再提出機会、または「発展リーディング」4部のうち1部を受講生の投票で決めて特別講義 |
| 短期集中8週(週2回または集中形式) | 第1週(インストール・導入)→ 第2週(情報・スキーマ)→ 第3週(整合性、中核課題)→ 第4週(戦闘・バランス・ナラティブのまとめ)→ 第5週 中間発表 → 第6週(会議・意思決定・協業)→ 第7週(ガバナンス・検証)→ 第8週 期末発表。分野は代表3つに縮小し、「やってみよう」は授業中の実習として吸収 |
| 反転授業(フリップラーニング) | 本文の通読は事前課題に回し、講義時間は「やってみよう」の実習とルーブリックによる相互評価にすべて割り当てる。本書はコードが外部依存なしにそのまま動くように収録されているため、実習中心の運営に適している |
同じ本の中でも、章ごとに要求される背景知識は異なります。ターミナルが初めての1年生でもついて来られる章もあれば、データベースのキーの概念や統計の基礎がなければ十分に消化できない章もあります。受講生のレベルに合わせて進度を調整したり、前提科目を案内したりするときに使えるよう、3段階のバッジで整理しました。
バッジの意味は次のとおりです。
| バッジ | 等級 | 意味 |
|---|---|---|
| 🟢 入門 | 入門 | 非専攻・1年生でもついて来られる。コードはコピー・実行のレベルで十分 |
| 🟡 実務 | 実務 | コードを読み、自分のデータに合わせて修正できる必要がある。企画実務の文脈理解を推奨 |
| 🔴 発展 | 発展 | アルゴリズム・構造を設計・拡張する段階。前提知識なしには消化の難度が高い |
週ごとの中核となる部のバッジと前提知識は以下のとおりです。「前提知識」は、その週に無理なくついていくために事前に備えておくとよい背景であり、なければ受講そのものが妨げられるというものではありません。
| 週 | 中核となる部 | バッジ | 前提知識 |
|---|---|---|---|
| 1 | 1.0・第1部 導入 | 🟢 入門 | なし(ターミナル初体験を前提) |
| 2 | 第2部 情報アーキテクチャ | 🟢 入門 | テキストエディターの使用 |
| 3 | 第3部 システム・スキーマ | 🟡 実務 | 表/スプレッドシートの基本、データ型の概念 |
| 4 | 第10部 整合性検証 | 🔴 発展 | Pythonの基礎(関数・ループ)、リレーショナルキー(FK)の概念 |
| 5 | 第4・8部 戦闘・バランス | 🟡 実務 | 四則演算の数式、表計算(Excelの関数) |
| 6 | 第5部 ナラティブ | 🟢 入門 | キャラクター・シナリオの文章感覚 |
| 7 | 第6・7部 コンテンツ・レベル | 🟡 実務 | プロシージャル生成の概念(推奨)、座標・グリッドの感覚 |
| 9 | 第9・14部 UX・モバイル | 🟡 実務 | 画面レイアウト・解像度の概念 |
| 10 | 第16・17部 コミュニケーション | 🟢 入門 | なし(協業経験があれば有利) |
| 11 | 第18・19部 意思決定・リード | 🟡 実務 | チーム作業・プロジェクト管理の経験(推奨) |
| 12 | 第20・21部 協業・振り返り | 🟡 実務 | 第2週 情報アーキテクチャの履修 |
| 13 | 第22部 ガバナンス | 🟡 実務 | 基礎統計(平均・分布、ハルシネーション検出の文脈)、著作権の基本 |
| 14 | 第23・24部 個人・運営 | 🔴 発展 | Pythonの基礎、gitの基本、第4週 整合性の履修 |
前提科目の一行案内(シラバス用):「Python入門またはそれに準ずるプログラミングの基礎を推奨するが、必須ではない。第4・14週の発展章はPythonの関数・ループの水準を前提とし、未履修者は第1〜3週の入門トラックで十分について来られるよう課題を分離して運営する。」
受講生の構成に応じた運営のヒントは次のとおりです。
ルーブリックがなければ、「やってみよう」の提出物は「動いた/動かなかった」の二分法だけで採点されがちです。すると、本書がもっとも重視すること — AI出力をチェックし、拒否する過程 — が評価から消えてしまいます。そこで第4週の中核課題(10.1 整合性検証atom「やってみよう」)を例に、成果物だけでなくその過程まで採点するルーブリックを置きます。ほかの週の課題にも、項目名だけ差し替えてそのまま使えます。
課題の定義: 自分で作った(または提供された)複数のマスターデータについて、シート間の外部キー(FK)整合性を検査するツールをAIとともに作り、わざと仕込んでおいたエラーをツールが捕まえることをデモする。提出物は ① ツールのコード ② 検査の実行結果(合格/不合格レポート) ③ AIに打ったプロンプトの全文と、そのうち拒否・修正した出力の記録。
ルーブリックは4項目・各25点(計100点)で構成します。核心は、「ツールが動く」(2項)とは別に、AIをどう扱ったか(3・4項)を半分の比重で評価するという点です。
| # | 評価項目 | 配点 | 不十分(0〜12) | 普通(13〜19) | 優秀(20〜25) |
|---|---|---|---|---|---|
| 1 | 整合性ルールの定義 — どのFK関係をなぜ検査するのかが明確か | 25 | 検査対象の関係が不明確、または恣意的 | 主要なFK関係を識別したが、根拠の説明が不足 | シート間の関係を図・根拠とともに定義し、検査の優先順位を説明 |
| 2 | ツールの動作・エラー検出 — 仕込んでおいたエラーを実際に捕まえるか | 25 | 実行不能、または明白なエラーを見逃す | 大半のエラーを捕まえるが、一部の見逃し・誤検出がある | 仕込んだエラーをすべて捕まえ、誤検出なしで、人が読めるレポートを出力 |
| 3 | AI活用過程の透明性 — プロンプト全文と出力が再現可能な形で記録されているか | 25 | プロンプト・出力の記録なし、または結果のみ添付 | プロンプトはあるが、拒否・修正の過程が欠落 | 打ったプロンプトの全文、生の出力、拒否・再指示の過程を時系列で残している |
| 4 | チェック・拒否の判断 — AI出力の何をなぜ拒否・修正したのか | 25 | 出力をそのまま受容(チェックの痕跡なし) | 一部修正したが、判断の根拠が弱い | エラー・ハルシネーション・過剰設計を指摘して拒否し、その判断根拠を自分の言葉で説明 |
採点運営メモ: 3・4項(計50点)がこのルーブリックの背骨です。ツールが完璧に動いても(2項満点)、AI出力を無批判に受容したなら(4項不十分)、この課題の学習目標 — 「人がチェック役の席を守る」 — には未達と見なします。逆に、ツールが一部不完全でも、拒否・再指示の過程がしっかりしていれば高い点数を取れます。効果(動いた結果)ではなく構造(どう扱ったか)を評価するという本書の原則が、採点にもそのまま適用されます。
期末課題には、上の4項に⑤ ワークフローの一般化(自分の分野への移植の説明)の1項を加えた、5項・各20点の拡張ルーブリックをお勧めします。学期を通して扱ったツールを自分のプロジェクトへ移せるか — それが本書が最後に問う質問であり、講義の最後の評価も同じ質問で十分です。
最後に、この付録を1枚に縮めるとこうなります。
この進度表は出発点であって、正解ではありません。ご自身の受講生のレベルと学事日程に合わせて、週を動かし、課題を変えてください。本書そのものをAIツールに丸ごと読ませて「私の講義の16週日程と受講生のレベルに合わせて、この進度表を組み直して」と頼むのも — 本書のもっとも速い活用法らしく — 開かれている道です。
ゲームプランナーのための AI 実務ワークフロー
作り話の数字はひとつもない — Claude Code・プロンプト・検証・制作メモリの実践記録
本書は韓国語原書の日本語版であり、本書自身のAIワークフローで韓国語から翻訳し、著者が検収したものです。本ウェブ版には独自のISBNはありません。
| 原題 | 게임 기획 실무에서 바로 쓰는 AI·클로드 코드 활용법 |
| 著者 | イ・ミンス(李旼洙 / Minsoo Lee) |
| 原書出版 | 株式会社BOOKK(韓国語・紙の本) |
| 原書出版日 | 2026年6月11日 |
| 原書ISBN | 979-11-12-21479-9(韓国語・紙の本) |
| 韓国語ソース | https://github.com/eremes81/game-design-ai-practice |
ⓒ イ・ミンス(Minsoo Lee)2026
本書は CC BY-NC-SA 4.0 の下で公開されています。非営利の共有・翻訳は、原著者(イ・ミンス · Minsoo Lee)と出典を表示すれば許可されます。商業的利用には著者の別途許諾が必要です。