企画から作品データの骨を 2 秒で起こし、生成AIには穴だけを埋めさせる
Raising the game data's skeleton from the plan in 2 seconds and letting generative AI fill only the holes
D30-4-T60-sketch-based-synthesis・技術・日本語入力でプレイする仕組み
場所・人物・フラグ・誰に何を聞くと何が分かるか、までは、企画のファイルから機械で決まります。
決まらないのは、情景の文と、台詞と、同じ問いの言い方の幅だけです。
だから先に骨を機械で起こし、生成 AI には「穴」だけを渡します。
この回の位置と、前後の回#
区切り: T58〜T64 作り方と周辺(7 回のうち 3 回目)
先に読むとよい回: T58(通った台詞だけを採る)・T59(企画が正)
次に読む回: T61(背景を 16 色に落とす)
名前 — sketch-based synthesis#
- 正式名
- sketch-based synthesis(program sketching。スケッチによるプログラム合成)
- 初出
- Armando Solar-Lezama, Liviu Tancau, Rastislav Bodík, Sanjit A. Seshia, Vijay A. Saraswat, "Combinatorial Sketching for Finite Programs", ASPLOS 2006, pp. 404–415
- 正式名
- Blueprint First, Model Second(骨組みが先、モデルが後)
- 初出
- Libin Qiu, Yuhang Ye ほか, "Blueprint First, Model Second: A Framework for Deterministic LLM Workflow", arXiv:2508.02721, 2025
- 正式名
- schema-first code generation(スキーマ先行のコード生成)
- 初出
- 確かめられませんでした(IDL・Protocol Buffers・OpenAPI の定義からコードを起こすのと同じ位置づけ)
sketch-based synthesis とは#
プログラム合成の一つの形です。
人が「穴のあるプログラム」(sketch)を書く。
全体の形・使う操作・ループの構造は人が決め、細かい定数や式だけを穴にしておく。
合成器が、仕様(参照の実装や、満たすべき性質)を満たすように穴を埋めます。
Solar-Lezama らの 2006 年の論文は、そのための言語 SKETCH を出しました。
穴は、一般化したブール充足可能性(SAT)による組み合わせ探索で埋めます。
例として、暗号の AES の効率の良い実装を合成しています。
要点は、探索の範囲を人が先に狭めることです。
全部を合成器に任せると、探す空間が広すぎて終わらない。
人は得意な所(構造・意図)を書き、機械は得意な所(細部を仕様に合わせる)を埋める。
Blueprint First, Model Second とは#
同じ考えを、LLM のエージェントに当てた 2025 年の論文です。
LLM のエージェントが非決定で、決まった手順の業務に使いにくいのは、高いレベルの計画と低いレベルの実行を一つの生成の中で混ぜているからだ、と論じます。
そこで、専門家が決めた手順をコードの Execution Blueprint にし、決定論のエンジンがそれを実行する。
モデルは、範囲の決まった下位の作業にだけ呼ばれ、道筋は選ばない。
tau-bench で、最良の比較対象より Pass^1 の平均が 10.1 点高かった、と報告しています。
schema-first code generation とは#
先にデータの形(スキーマ)を書き、そこからコードを機械で起こすやり方です。
IDL や Protocol Buffers からスタブを、OpenAPI からクライアントを起こすのが典型です。
起こしたコードは手で直さず、直したければスキーマを直して起こし直す。
弱点は、起こしたあとに手で書き足す部分との混ざり方で、生成部分と手書き部分の境界をどう保つかが、どの道具でも問題になります。
三つに共通するのは、「決まりきった部分は機械が、決まりきっていない部分だけを人か生成器が」という分け方です。
うちの adv-scaffold.ps1#
企画のフォルダ(T59)から作品データの骨を 1 コマンドで起こします。
.\Scripts\adv-scaffold.ps1 -Plot Plots/<work> -Out Game/adv-<work> -ClearFlag f_<work>_clear
工程はこう並んでいます(スクリプトの見出しの番号のまま)。
1 adv-plot-check 企画の検査。error が 1 つでもあれば止まる
2 adv-new-work 作品データ(adv・locations・flags・characters・entities ほか)
3 adv-ledger-from-beats 手の台帳(分母を決める。あとで台帳の外の意図を見つけられる)
4 adv-stub-intents 本筋の意図の雛形(台帳から)
5 adv-stub-smalltalk 雑談の意図の雛形(人物から)
6 adv-stub-accuse 突きつけの意図の雛形(企画の誤導から)
6b adv-stub-friction 相談役のヒント・手帳のヒント・定番の問い(新しい形式の企画だけ)
7 adv-hints-from-work 焦点と救済(依存の順に並べ、行ける場所だけを指す)
7b adv-case-figure 事件の図(全体と章ごと。図は章の決着のあとにだけ見せる・T22)
8 埋め込みモデルのコピー bake に要る。作品データには付いてこない
8b adv-stub-test-config 判定の設定の雛形(未測定のまま。緑にはならない)
8c baseline この生出力を保存する(git が追跡する)
9 adv-sim lint 通らないのが正常。残った error が人の残り仕事
文書では「9 段」と呼んできましたが、いまは 6b・7b・8b・8c が足されて 13 の工程です。
起こした骨のうち、穴はこういう形をしています(ファンタジーの作品の起こした直後の写しから。仮の文は企画の手から機械が作ったもの)。
{
"_comment": "台帳から起こした雛形(原典の手 — / examine → 依頼書を読む)。 paraphrases と台本は人が書く。",
"id": "scene.read_request",
"speaker": "scene",
"where": ["guild"],
"entities": [],
"confirmText": "依頼書を読む",
"paraphrases": ["(依頼書を読む をexamine——言い方を10本以上書く)"],
"stages": [ { "requires": [], "script": "TODO", "sets": ["f_read_request"], "role": "progress" } ]
}
id・話者・場所・前提(requires)・立てるフラグ(sets)・段の役割は、企画から入っています。
空なのは script(台本)と paraphrases(言い換え)だけです。
場所の情景文も「(<場所の名前> の情景文をここに書く)」、在席の一文も「(<人物> が居る様子をここに書く)」の形で残ります。
雑談の雛形は話題そのものが TODO で、進行のフラグを立ててはいけない(lint の L15)と注記してあります。
最後の lint が、穴を一覧にします。
L5 が「台本が無い」、L7 が「言い換えが 10 本未満」。
生成 AI への指示は「良い文章を書いて」ではなく「この error を消して」になります(T58)。
結果は表示ではなく JSON でも返します(-ResultOut)。
status は draft か failed の 2 つで、lint の error が残っているのは「人の残り仕事」なので draft、子の道具の失敗・企画が無い・モデルが無いは failed(終了コード 1)です。
呼ぶ側は表示を読まずに、この JSON を見ます。
今日、検査用の小さな企画(手 12・場所 5)を scratchpad の中に起こしてみました。
1.54 秒で終わり、ファイルは 67、lint は 42 error・36 warning(L5 が 22、L7 が 28、L31 が 11 ほか)。
2026-08-17 の記録では、工程が 9 つだった頃のオホーツク試作で 0.9 秒でした。
起こした直後と、いま#
ファンタジーの 6 本(Game/adv-01〜06)は、2026-09-23 にこの道具で起こし、起こした直後の写しを baseline/ に残しています。
今日、その写しと、いまの作品データを数えました。
起こした直後
- 意図 17〜19(本筋 12 と雑談 5〜7)。言い換えはどれも仮の 1 本だけ
- 台本が TODO の段が各 12
- 入場の台本 5〜6 本で、地の文は全部合わせて 113〜140 字(「…に入ったときの描写をここに書く」の仮の文)
- lint(写しを scratchpad に移して実行)error 18〜21・warning 25〜26
いま
- 言い換え 120〜616 本、台本が TODO の段は 0
- 地の文と台詞 1,244〜40,141 字
- lint の error は 6 本中 5 本が 0、1 本は 15
数が一番動かなかったのは意図です。
6 本中 4 本は雑談の雛形を使わず、本筋の 12 だけになりました。
骨が決めた構造(誰に何を聞くと何が立つか)はそのままで、穴は埋めるか、要らなければ外す。
骨の写しを残すのは、企画を変えたときのためです。
Scripts/adv-update-work.mjs は、base(起こした直後の写し)・current(いまの作品。人が書いた文を含む)・incoming(変えた企画から同じ道具で起こし直したもの)の三者を比べます。
規則は、三値が同じなら current、current が base のままなら incoming を採る、incoming が base のままなら current を保つ。
id を持つ要素の配列は id 単位で比べ、それ以外の配列(段・言い換えなど)は配列全体を一つの値として比べる。
双方が別々に変えたら conflict にして、自動では上書きしません。
書き込むのは作業ツリーが clean のときだけで、戻すのは git checkout です。
再現するのは文字列ではなく条件#
同じ企画から 2 回起こすと、構造は一致し、文章は一致しません。
手引きはそれでよいとしています。
「『同じものが出る』ことは要求しない。『同じ条件を満たす』ことを要求する。」
2026-08-17 に、手で作ったオホーツク試作を企画から起こし直して突き合わせた記録があります。
場所 10・フラグ 40・人物 11・本筋の手 49 が一致し、意図のつながりは 49 本中 44 本が一致(残り 5 本は等価な余分な条件)。
残った lint の error 60 件が、そのまま人の残り仕事でした。
同じ日に、企画だけを入力に作品を作り直し、骨 0.9 秒と中身 26 分 38 秒で回帰の全項目が PASS しています。
突き合わせは、2 つの作品データの骨を比べる adv-work-diff(T44)が、-Compare を付けるたびにコマンドで出します。
名前どおりでない所 — sketch-based synthesis#
sketch-based synthesis とは、穴を埋める者が違います。
SKETCH では、穴は SAT の探索が埋め、仕様を満たすことが保証されます。
うちでは、穴は生成 AI か人が埋め、lint と回帰の検査が通すかを決めます。
仕様は完全ではなく、通っても品質は下限しか言えません(T58)。
骨の形は sketch、埋め方は generate-and-test、という組み合わせです。
Blueprint First, Model Second とは、モデルを呼ぶ時が違います。
論文の Blueprint は実行時に動く手順で、モデルはその途中で呼ばれます。
うちの骨は制作の時に起こすデータで、モデル(生成 AI)を使うのも制作の時だけです。
遊んでいる間は、骨にも穴にもモデルは関わりません。応答は全部、検査を通った書き下ろしの文です。
schema-first code generation とは、二つ違います。
一つ目、スキーマにあたる企画の形(adv-beats/1)は JSON Schema のファイルではなく、検査のスクリプト(adv-plot-check)の中に規則として書かれています。
二つ目、起こしたあとに作品データを直接書き足すので、「スキーマを直して起こし直す」だけでは済みません。
その分を三者比較(adv-update-work)で埋めています。
弱点と、採らなかった案 — sketch-based synthesis#
弱点の一つ目は、今日見つけました。
ファンタジーの企画(Plots/fantasy-04)を scratchpad に向けて流したら、2.09 秒で工程 7b が止まり、status は failed でした。
原因は 7b の章の取り出しです。企画に chapters が無いと、PowerShell の @($null | ForEach-Object …) が「空の要素 1 つ」の配列になり、事件の図の道具に空の --chapter を渡して落ちます。
7b は 2026-10-09 に、新しい形式(adv-beats/2)の企画のために足した工程です。
Plots/ にある企画 15 本は、霧走町も含めてどれも古い形式(adv-beats/1)なので、いまの scaffold は既存の形式の企画からは骨を起こせません。
通るのは、新しい形式の検査用の企画だけでした。
工程を足したとき、新しい形式でだけ確かめた、という形です。
二つ目、lint が知っている穴しか並びません。
2026-08-17 の作り直しで出た欠陥 10 件のうち 3 件は lint の外でした(焦点・通じ率・取り違え)。
lint が 0 になってから、reach・clearpath・play・probe を回します。
三つ目、結果の JSON の残り仕事(pendingAuthoring)は、lint の行の頭の規則名を数えているだけです。
L39 や L43 のような数を報告する行も、残り仕事として数えます。
四つ目、工程 8 のモデルのコピーは、引数が無ければ「既にモデルを持っている作品から借りる」です。
どの作品から借りたかは表示に出ますが、作品の宣言にはありません。
採らなかった案。
- 生成 AI に作品データを丸ごと書かせる。数を合わせること・前との整合が弱い所なので、手数・フラグ・前提が崩れます。
- 既存の作品をコピーして書き換える。骨の id と前提が前の作品のまま残り、台帳の分母が狂います。
- 起こした骨を直接は直さず、全部企画から起こし直す(spec-as-source)。文章を作品データの側で書く以上、三者比較で人の文を守る方を選びました。
検索語#
program sketching Solar-Lezama
Combinatorial Sketching for Finite Programs
Blueprint First, Model Second
schema-first code generation
骨は機械、文は検査を通った書き下ろし。その文がどう読めるかは、『霧走町 流氷に眠る』の第一章で確かめてください。ブラウザで無料です。プロフィールのリンクから。