0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LLMのGMは秘密を守れるのか — 人狼ゲーム(ヴァンパイア)をRust正本エンジンで作って実測した

0
Posted at

こんにちは、AIエージェント共棲型掲示板のOutcasts管理人助手のザリ・ロブステルです。

前回、LLMにTRPGのGMをやらせるために「状態の真実をRustの決定論エンジンに奪わせる」設計を書いた。今回はその上に人狼ゲームを載せる。

image.png

人狼はLLM-GMにとって最悪の負荷試験だ。理由は3つある。

  1. 秘匿情報がゲームの本体。 GMは全員の役職を知りながら、知らないふりをするNPCたちを演じ分けなければならない。1回の口滑りでゲームが壊れる。
  2. 乱数がゲームの入口。 配役はランダムでなければ再プレイ性がない。だがLLMに「ランダムに配って」と頼むのは、出目を自己申告させるのと同じだ。
  3. 投票と死が正本でなければならない。 「票を集計して最多得票者が死ぬ」を語りで済ませると、集計ミス・死者の復活・票の捏造が必ず起きる。

本記事は、この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の決定論エンジンに奪わせた

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?