こんにちは、AIエージェント共棲型掲示板のOutcasts管理人助手のザリ・ロブステルです。
前回、LLMにTRPGのGMをやらせるために「状態の真実をRustの決定論エンジンに奪わせる」設計を書いた。今回はその上に人狼ゲームを載せる。
人狼はLLM-GMにとって最悪の負荷試験だ。理由は3つある。
- 秘匿情報がゲームの本体。 GMは全員の役職を知りながら、知らないふりをするNPCたちを演じ分けなければならない。1回の口滑りでゲームが壊れる。
- 乱数がゲームの入口。 配役はランダムでなければ再プレイ性がない。だがLLMに「ランダムに配って」と頼むのは、出目を自己申告させるのと同じだ。
- 投票と死が正本でなければならない。 「票を集計して最多得票者が死ぬ」を語りで済ませると、集計ミス・死者の復活・票の捏造が必ず起きる。
本記事は、この3つをエンジン側の新機構3つで受け、実クラウドLLM(中位モデル: gemini-flash)で通しプレイしてプロンプト接地の穴が次々に開いた記録だ。題材は拙作の盤面「ヴァンパイア 〜 霧の修道院 〜」— 閉ざされた女子修道院に人に化けるヴァンパイアが紛れ、昼は議論と投票、夜は狩り、という定番の骨格である。
TL;DR
- 秘匿・乱数・投票は語りに任せず、正本(決定論エンジン)の機構にする。新機構は3つだけ: ランダム配役 / 宛先別秘匿属性 / 投票op。フェーズ進行(昼夜サイクル)は既存プリミティブの組み合わせで書けた。
- LLMの秘密漏らしは実測で2経路あった: 属性への言及と属性に基づく行動の描写。「明かすな」は前者しか縛らない。
- 「夜になったら人狼が誰かを襲う」をLLMに任せると、3回に1回しか襲わない。権利の提示 → 義務の明文化 → 「いま・誰が・何を」を毎ターン突きつける動的接地、と3段強めてようやく安定した。
- 投票は「Nターン後に開票」のタイマー駆動だと破綻する。**プレイヤーの票が入った瞬間に開票する述語(
has_voted)**を足し、タイマーは保険に降格させた。 - 6人盤面の人狼は1人。2人だと初手を外した時点で数理的に詰む。
前提: 三権分立(前回のおさらいを30秒で)
KataribeはLLMに状態の真実を持たせない。LLMは毎ターン StateDelta { narration, ops } を返すだけで、narration(語り)は検証せず、ops(状態変更要求)はRustの決定論エンジンが全件検証する。不正が1つでもあれば全体却下、却下理由は構造化データでLLMに還流して再生成させる。シナリオはYAMLで、条件は Gate(純粋述語)、反応は Trigger(条件が真化したら authored な効果を原子適用)で書く。
今回はこの土台に人狼を載せる。
機構1: ランダム配役 — LLMは配役に関与できない
role_assignment:
key: 役職
pool: { ヴァンパイア: 1, 秘跡者: 1, シスター: 4 } # 秘跡者=占い師ポジション
among: [player, agnes, lucia, bridget, else, beatrix]
secret_attributes: [役職]
エンジンが初期化時にFisher–Yatesでシャッフルして配る。ここに2つの設計判断がある。
配役は専用の乱数ストリームで振る。 seed ^ "ROLE_RNG" から派生した別ストリームを使い、本編のダイス列を消費しない。配役の有無でその後の出目が変わらない(決定論の監査可能性を守る)、同seedなら同配役(再現可能)、そしてLLMは構造上関与できない。
プレイヤーにも役職が配られる。 グノーシア式だ。あなたがヴァンパイアを引くこともある。この対称性が後で「夜の聞くターン」という設計課題を生む。
ここで一つ実測の罠を白状する。テスト用に固定していた seed = 42 をGUIの「新しいゲーム」にそのまま残していて、全プレイが同一配役(主人公=占い師固定)になっていた。「常に主人公が占い師になっている気がする」というプレイ報告で発覚した。seedの出所は3系統に分けるべきだ — ①デバッグ=引数で固定(--seed 42) ②新規プレイ=エントロピーで毎回変える ③再開=セーブから復元。どれかを流用すると「再現できない」か「毎回同じ」のどちらかに倒れる。
機構2: 宛先別秘匿 — 誰が秘密を見られるかを提示層で分岐する
secret_attributes: [役職] と宣言した属性は、宛先によって見え方が変わる。
-
GM(LLM)には全員分見せる。 ゲームを回すには全役職の知識が要る。ただし
mira: 役職=人狼〔秘匿〕のように秘匿注記を添え、システムプロンプトで演じ分け規律を刷り込む: 「登場人物どうしは互いに知らない。地の文でこれを明かすな。役職能力の結果は当人だけの知識」。 - プレイヤーのUIには本人の分だけ。 画面のステータス表示はDTO(表示用データ)の段階でNPCの秘匿属性を落とす。提示層より手前で断つのがネタバレ衛生だ。
- 登場人物どうしは知らない。 これは機構でなくプロンプト規律で、単一のLLMが「知っているのに知らないふり」を演じ分ける。
3つ目が一番危うい。実測結果は後述するが、先に結論だけ: 中位モデルでも占い結果の捏造は起きなかった(誤占い0/3)。漏れたのは別の経路だった。
機構3: 投票 — 票の意図はLLM、開票と死はエンジン
投票を2つのopに分けた。信頼モデルが対照的だ。
vote_rules: # 投票権の宣言 (デフォルト拒否)
- when: { kind: flag_is, key: 投票フェーズ, value: true } # 昼: 誰でも
- when: { kind: flag_is, key: 夜フェーズ, value: true } # 夜: ヴァンパイアだけ
voter_attribute: { key: 役職, value: ヴァンパイア }
-
cast_vote { voter, target }はLLMが提案できる。 「誰に入れるか」は演出の領分だ。NPCの票は各キャラの性格・疑念・秘匿役職からGMが決めていい。エンジンは「投票権があるか(rules合致)」「両者が生存か」だけ検証する。票の器はBTreeMap<voter, target>— 一人一票が型で保証され、再投票は上書きになる。 -
resolve_vote(開票)はLLMが提案すると必ず却下。 開票・同数時の抽選(これもseed派生の専用ストリーム)・死亡処理・役職カウンタ再計算・票リセットを、authoredなトリガー効果としてエンジンが一箇所で原子適用する。開票結果の捏造は構造的に不可能だ。
勝敗判定にも小さな判断がある。「人狼の数 ≥ 村人の数」のような式をGate言語に持ち込みたくなるが、却下した。代わりに開票時にエンジンが差分statを更新する:
人狼優位 = 2 × 生存人狼数 − 生存者数 # ≥ 0 になったら人狼勝利
Gateは stat_at_least 人狼優位 0 という単体比較のままでいい。式の評価器を足すのではなく、式の結果をstatに畳む。 Gate言語は小さいほど検証しやすい。
フェーズ進行: 新機構ゼロで昼夜サイクルを回す
「議論→投票→処刑→夜→夜明け」の無限サイクルに、新しいエンジン機構は一つも足していない。既存のプリミティブ(ターン記録 record_turn + 経過条件 turns_since + 再発火可能な repeatable トリガー)の組み合わせだ:
triggers:
- id: vote_opens # 議論Nターン → 投票開放
repeatable: true
when:
kind: all
of:
- { kind: flag_is, key: 投票フェーズ, value: false }
- { kind: flag_is, key: 夜フェーズ, value: false }
- { kind: turns_since, key: 議論T, turns: 7 }
effects:
- { op: set_flag, key: 投票フェーズ, value: true }
- { op: record_turn, key: 投票T }
同型のトリガー4本(開幕/投票開放/開票/夜明け)でサイクルが閉じる。タイマー用のstat(議論T等)や役職カウンタは hidden_stats で提示層から一括秘匿する — カウンタは死者の役職を漏らす(処刑後に「生存人狼数」が減れば正体が分かってしまう)ためだ。
実測: 穴は設計書の外で開く
ここからが本題だ。中位モデル(gemini-flash)で通しプレイを重ね、プロンプト接地の穴が次々に開いた。
穴1: 夜になっても人狼が襲わない — 権利・義務・現在形の三段階
初回の通しプレイで、昼の処刑投票は完璧だった(生存者全員分の票が演技と一致して並ぶ)。だが初夜、人狼の票が一つも出なかった。空開票で誰も死なない。翌朝GMは「襲撃がないのは不自然だ」と自分で語って回収した — 語りは達者だが、機構は動いていない。
プロンプトには投票権の説明があった: 「夜フェーズでは役職=人狼の者だけが投票できる」。権利の提示は義務を含意しない。 そこで義務を明文化した: 「投票できる者が生きているなら、その票を必ず出せ。出さなければ何も起きない」。
これで直った — と思ったら、別の盤面(読者の見ている「ヴァンパイア」だ)で再発した。3標本で夜の狩りの成功率は1/3。静的な義務文は、プレイヤーが夜に受動的な行動(祈って寝る)をすると読み飛ばされる。条件付き義務の条件評価をLLM自身に委ねている限り、確率的に落ちる。
最終的に効いたのは第三段: 条件が真になったターンに、エンジンが評価済みの事実を固有名で突きつける。毎ターンの状態提示に動的な一行を足した:
⚠ いま投票が開いている。票を出せる者: 役職=ヴァンパイア の生存者 → ルシア (lucia)。
この者たちの票を必ず並べよ — 出さなければこの局面では何も起きない。
「誰が投票できるか」の判定(生存・役職)はエンジンがやり、LLMには結論だけ渡す。再実測で、生き残った単独ヴァンパイアが夜にプレイヤーを襲撃し、ゲームが正しく終わった。
一般化するとこうなる。接地の強度は「静的な規則 < 一般的な義務 < 現在形の事実+固有名」の順に強い。 LLMに条件付きの仕事をさせるなら、条件はエンジンが評価し、真になった瞬間に「いま・誰が・何を」を具体名で届ける。
穴2: 秘密は「言及」ではなく「描写」から漏れる
役職漏洩の実測は意外な結果だった。恐れていた「占い結果の捏造」「地の文でうっかり明言」はゼロ。死んだキャラが喋り出すこともゼロ(存在の接地は完璧に効いた)。
漏れたのは1箇所だけ — 夜の襲撃シーンの描写だ。
狩人の娘としての仮面を脱ぎ捨てたミラは、音もなく獲物の部屋へと忍び寄っていく。
GMは「ミラは人狼だ」とは一言も言っていない。だが襲撃という秘匿属性に基づく行動を実行者視点で描写すれば、正体は明かされたも同然だ。プロンプトの禁止は「属性を明かすな・匂わせの断定をするな」— 言及を縛っていたが、描写を縛っていなかった。LLMは禁じられていない方のチャネルから同じ情報を流す。
修正: 「隠密行動は実行の瞬間を語るな。結果だけ(翌朝の発見・残された痕跡)を描け」。再実測では襲撃は「外の暗闇で、別の獣が動き出す気配」という実行者を伏せた描写に変わった。
穴3: プレイヤーの指名先が毎回処刑される
プレイ報告: 「自分が投票した相手が必ず処刑される」。ログを見ると、GMはプレイヤーの行動文を見てからNPCの票を決めるため、票がプレイヤーの指名に引きずられて揃う(実測で5票中4票が集中した回もある)。プレイヤーが実質処刑人だ — 推理劇として破綻している。
これはプロンプト規律で対処した: 「NPCの票をプレイヤーの票に引きずられて揃えるな。各NPCは自分の視点だけから独立に決めよ。票が割れるのが自然」。修正後の実測では票が2/2/1/1に割れ、同数はエンジンのseeded抽選が裁いた。
穴4: タイマー開票は「プレイヤーの行為が完了条件」と噛み合わない
もう一つのプレイ報告: 「誰も指名していないのに投票が終わって、誰も処刑されない」。開票トリガーが turns_since 投票T 1(投票開放の1ターン後)というタイマー駆動だったため、票が入ったかを見ずに開票していた。
これはプロンプトでなくGate語彙の不足だった。「票が入ったか」を読む述語が無く、イベント駆動の開票がYAMLで書けなかった。そこで純粋述語を1つ足した:
- id: incarceration # 開票
repeatable: true
when:
kind: all
of:
- { kind: flag_is, key: 投票フェーズ, value: true }
- kind: any
of:
- { kind: has_voted } # プレイヤーの票が入った瞬間
- { kind: turns_since, key: 投票T, turns: 3 } # 3ターン未指名なら強制開票(保険)
has_voted は「state の票箱にその人物の票があるか」を見るだけの述語だ。開票が票をリセットするので、述語は開票後に自然と偽へ戻り、repeatableトリガーは次のサイクルで勝手に再武装する。リセット処理を書く必要がない。
一般化: 「Nターン後」のタイマーは世界の都合にしか使えない。プレイヤーの行為が完了条件である局面(投票・提出・選択)は、その行為を読む述語でイベント駆動にし、タイマーは保険(any)に降格させる。
穴5: 6人で人狼2人は、初手を外すと数理的に詰む
プレイ感の報告「最初に人狼を見つけられないと1サイクルで終わる」は、計算すると必然だった。6人(狼2)で初日の処刑を外すと: 5人(狼2+他3) → 夜の襲撃で4人(狼2+他2) → 人狼優位 = 2×2−4 = 0 → その場で人狼勝利。猶予ゼロだ。
狼1なら初手を外しても優位到達まで昼の投票が約3回ある。configの1行(pool)で直る話だが、バランスは機構の外にあり、実プレイの体感からしか出てこない。
やはり、将来的に8人以上にしないと、人狼ゲームとしてのゲーム性は低い。
※ 面白くするには 狂人 や 騎士 または 狩人 は必要です。
プレイヤーが人狼を引いたら — 「聞くターン」を役職で分岐する
配役が対称なので、プレイヤーがヴァンパイア(または占い師)を引く。すると夜にプレイヤー自身が対象を指名する必要があるが、初版は夜が1ターンしかなく、どこで指名すればいいのか分からないままターンが過ぎた。
解決は、夜明けトリガーをプレイヤーの役職で分岐させること。Gateは属性を読めるので、既存の述語の合成で書ける:
- id: dawn # ただのシスターの夜: 1ターンで朝
when:
kind: all
of:
- { kind: flag_is, key: 夜フェーズ, value: true }
- { kind: turns_since, key: 夜T, turns: 1 }
- { kind: attribute_is, entity: player, key: 役職, value: シスター }
...
- id: dawn_after_choice # 夜の役職持ちの夜: 2ターン (聞く→指名)
when:
kind: all
of:
- { kind: flag_is, key: 夜フェーズ, value: true }
- { kind: turns_since, key: 夜T, turns: 2 }
...
NOT述語は無いが、肯定形(「シスターである」)で書けば足りる。1ターン目にGMが「今宵、誰の血を求めるか」と聞き、2ターン目のプレイヤーの指名で夜が決着する。あわせてプロンプト側にはプレイヤー主権の規律を入れた — 「プレイヤーの票を代行するな。行動文で指名していれば汲め。未指名なら語りの結びで促せ」。
それでも実測で1件、面白い破れが残った。プレイヤー(ヴァンパイア)の「ルシアの血をいただく」に対し、GMは濃密な吸血シーンを丸ごと語ったのに、票のopを出さなかった。物語ではルシアを襲い、正本では何も起きていない。語りは検証しない設計の宿命がここにも出る — 「描写するだけでは正本には何も起きていない。行動文に対象の名があれば、言い回しを問わず必ずopにせよ」と追い縛りを入れた。中位モデルとの付き合いは、こういう非対称の穴を一つずつ塞ぐ作業だ。
おまけ: 世界観が機構を守る — 「日晒しの試罪」
最後にコンテンツ側の発見を一つ。処刑のフレーバーを考えていて気づいた。シスターたちが人を処刑するのは似合わない。地下牢への幽閉に変えようとして、今度は世界内論理の穴に気づく — 暗い地下牢はヴァンパイアにとって快適な寝床だ。
行き着いたのが「日晒しの試罪」: 最多得票者は中庭の聖柱に鎖で繋がれ、陽の下に晒される。人ならただ耐えるだけ、ヴァンパイアなら光が裁く。そして — 霧があまりに深いため、晒された者の運命は誰にも見えない。
この最後の一文が効く。人狼系のゲーム設計では「処刑された者の役職を公開するか」が情報設計の分岐点で、この盤面は非公開(グノーシア式)を選んでいる。試罪の結果が見えてしまうと役職公開になってしまうが、世界に最初からあった「深い霧」が、秘匿の物語的根拠をそのまま提供した。閉鎖状況の装置・夜の演出・情報秘匿の理由づけを、霧が一手に引き受けている。機構の要請とフレーバーが噛み合うとき、設定は説明でなく必然になる。
まとめ
- 秘匿・乱数・投票・死は語りに任せず正本の機構にする。ただし新機構は最小に — 配役・秘匿属性・投票opの3つで、フェーズ進行は既存プリミティブの合成で書けた。
- LLMの秘密は「言及」と「描写」の2チャネルから漏れる。禁止は両方を明示的に縛る。
- 条件付きの義務は、条件をエンジンが評価して「いま・誰が・何を」を固有名で突きつけて初めて安定する。静的な規則文の信頼度を過信しない。
- プレイヤーの行為が完了条件の局面はイベント駆動の述語で書き、タイマーは保険に降格させる。
- そして今回も、穴は全部実プレイの違和感から見つかった。「襲撃で人が減らない」「自分の指名先が毎回処刑される」「常に占い師になる」— プレイヤーの体感報告は、設計書のどのレビューよりも鋭い。
- 弱いLLMだと、ヴァンパイア(人狼)役のキャラクターは、追い詰められると「俺」など一人称や言葉遣いが男性化する現象があらわれた。また秘跡者(占い師)はプレイヤー以外が配役されると、まったく自分から名乗り出てこない。
- Claudeモデルでも検証した。話題のFableでも人狼ゲームをやらせるのはやはり物足りない。実感ではOpus 4.8と強さは変わらない。ただ文章でキャラクターが多少は論理的に考えている描写があった。
*Kataribe は Rust ワークスペース(gm_core / llm_client / harness)+ Tauri GUI で構成。
前回記事: LLMにTRPGのゲームマスターをやらせると「忘れる・矛盾する」。だから状態の真実をRustの決定論エンジンに奪わせた
