AIディレクティブとマクロ
AIはストーリーを書きます。そのストーリーには小さな指示 — [health: -15]、[gold: +50] — が埋め込まれており、プレイヤーが何かを見る前にエンジンがそれを取り除きます。プレイヤーが綺麗な物語を読んでいる間、舞台裏でゲーム状態が静かに変動しているのです。
これがディレクティブシステムです:ストーリーテリングとメカニクスの橋渡し。そしてマクロはその相棒 — {{char}} や {{user}} のようなプレースホルダで、エントリをコンテキストに自動的に適応させます。
まだ 変数 — AIが追跡するもの を読んでいない場合は、そこから始めてください。ディレクティブは何を変えているのかを理解して初めて意味を持ちます。
なぜディレクティブが存在するのか
テーブルトークRPGでは、ゲームマスターが「ゴブリンが腕を切りつけた — 15ダメージ」と言い、プレイヤーがキャラクターシートの数字を消します。Yuminaでは、AIがゲームマスターでエンジンがキャラクターシートです。
AIは単に「プレイヤーは今85のhealthを持っている」と出力することもできます — しかしそれだとエンジンが自然言語をパースし、どの変数が変わったかを把握し、新しい値を計算する必要があります。これは脆く信頼できません。
代わりに、AIは物語の末尾に構造化された括弧を書きます。
The goblin's claw rakes across your forearm, drawing a hot line of pain.
[health: -15]エンジンは [health: -15] を見て15を減算し、min/max境界にクランプし、テキストからディレクティブを除去します。プレイヤーはストーリーだけを見ます。ステータスパネルはこっそり更新されます。
この分離こそ、システムを信頼できるものにしています。AIはフィクションを扱う。エンジンは計算を扱う。
ディレクティブ構文
すべてのディレクティブは同じパターンに従います。
[variable-id: operation value]括弧の中の3つの部分:どの変数、何をするか、どの値で。
9操作すべて
| 操作 | 構文 | 例 | 何が起きるか |
|---|---|---|---|
| Set | [var: set value] または [var: value] | [location: set "forest"] | 変数の値を完全に置き換え |
| Add | [var: add N] または [var: +N] | [gold: +50] | number変数にNを加算 |
| Subtract | [var: subtract N] または [var: -N] | [health: -15] | number変数からNを減算 |
| Multiply | [var: multiply N] または [var: *N] | [damage: *2] | number変数をNで乗算 |
| Toggle | [var: toggle] | [has-key: toggle] | booleanを反転(trueはfalse、falseはtrue) |
| Append | [var: append "text"] | [log: append ", found key"] | string変数の末尾にテキストを追加 |
| Merge | [var: merge {...}] | [stats: merge {"level": 2}] | JSON変数にオブジェクトをshallow-merge |
| Push | [var: push {...}] | [inventory: push {"name": "Sword"}] | JSON配列に要素を追加 |
| Delete | [var: delete "key"] または [var: delete N] | [config: delete "old-field"]、[inventory: delete 0] | JSONオブジェクトからキーを削除(引用付き文字列)、またはJSON配列からインデックスで要素を削除(数値)。delete の後の値は必須 — 裸の [var: delete] は静かに暗黙のsetに退化する。 |
値の構文ルール
- 数値:裸の数字 —
10、3.5、-7 - 文字列:二重引用符 —
"forest"、"magic sword" - ブール:裸のキーワード —
true、false - JSON:インラインのオブジェクトまたは配列 —
{"level": 2}、[1, 2, 3]
ショートハンドの罠
新規クリエイターが最も混乱しやすい点です。
[health: -10] → 10を減算(subtractのショートハンド)
[health: +10] → 10を加算(addのショートハンド)
[health: *2] → 2倍(multiplyのショートハンド)
[health: 10] → healthを10にSET(暗黙のset、ADDではない)先頭の記号(-、+、*)が操作を決定します。記号なし=setを意味します。 つまり [health: 50] は50を加算するのではなく、現在の値を50に置き換えます。
明示的に負の値を設定するには(まれですが可能)、[health: set -10] と書きます。
命名:IDまたは表示名
ディレクティブは変数を ID(player-hp)または 表示名(Player HP)で参照できます。エンジンは name-to-ID マップを管理し両方を解決します。IDの方が信頼できます、特に表示名にスペースが含まれる場合は。
JSON変数のネストパス
ネストされた構造を持つJSON変数では、AIはドット記法で特定のフィールドを対象にできます。
[relationships.aria.trust: +10]
[game-state.factions.ember-court.affinity: set 75]
[inventory.0.durability: -1]エンジンは自動的にパスをナビゲートします。中間オブジェクトが存在しない場合は作成します。これによりAIは複雑なオブジェクト深部の1つのフィールドを、他に触れずに更新できます。
オーディオディレクティブ
オーディオは似たような括弧構文 — [audio: track-id action] — を play、stop、crossfade、volume、トラックチェーン等のアクションで使います。完全なオーディオディレクティブのリファレンスと例については オーディオ設計 を参照してください。
JSON Patch 形式
複雑な操作 — 特に配列からアイテムを削除する場合 — エンジンはXMLでラップされたJSON Patch形式をサポートします。これはSillyTavernから移行したワールドでも使われる形式です。
<UpdateVariable target="inventory">
<JSONPatch>
[
{"op": "remove", "path": "/0"},
{"op": "replace", "path": "/1/durability", "value": 5}
]
</JSONPatch>
</UpdateVariable>4つのパッチ操作がサポートされています。
| 操作 | 何をするか | 例 |
|---|---|---|
replace | パスに値をセット | {"op": "replace", "path": "/health", "value": 80} |
delta | 加算または減算(正で加算、負で減算) | {"op": "delta", "path": "/gold", "value": -50} |
insert | 新しいキーと値のペアを追加 | {"op": "insert", "path": "/inventory/torch", "value": 1} |
remove | キーまたは配列要素を削除 | {"op": "remove", "path": "/0"} |
パスはスラッシュ(/health、/inventory/0)を使い、エンジンが内部的にドットパスに変換します。
JSON Patch vs 括弧ディレクティブ — いつ使い分けるか
ほとんどの場合、括弧ディレクティブの方がシンプルで、AIも信頼性高く生成します。JSON Patchが有用なのは2つの特定ケースです。
インデックスで配列要素を削除する —
[inventory: delete 0]は機能しますが、JSON Patchの{"op": "remove", "path": "/0"}は配列を扱うときに一部のAIモデルがより一貫して生成する形式です。1つの変数へのバッチ操作 — 1つのJSON変数の複数フィールドを一度に更新する必要がある場合、1つのJSON Patchブロックは5つの個別の括弧ディレクティブよりすっきりします。
AIにbehavior rulesを書くなら、どの形式を使うか指定してください。ほとんどのワールドは括弧ディレクティブだけを使います。配列削除が必要で括弧構文が信頼できない場合に限ってJSON Patchを導入してください。
エンジンがAI出力を処理する方法
AIが応答を返すと、エンジンはプレイヤーが何かを見る前にパースパイプラインを通します。このパイプラインを理解すると、ディレクティブが拾われない状況のデバッグに役立ちます。
ステップ1 — Thinkingタグの除去。 一部のモデル(Geminiなど)は内部推論を <thinking>...</thinking> タグで出力します。エンジンはこれを最初に除去します。
ステップ2 — JSON Patchブロックの抽出。 エンジンは <UpdateVariable> XMLブロックをスキャンし、内部のエフェクト操作に変換します。
ステップ3 — オーディオディレクティブの抽出。 [audio: ...] にマッチするものをすべて取り出してオーディオシステムにキューします。
ステップ4 — JSONディレクティブの抽出。 JSON値を含む括弧ディレクティブ(merge、push、オブジェクト/配列の set)を抽出します。これらが先に処理されるのは、JSONペイロード内の文字がシンプルな正規表現を混乱させかねないためです。
ステップ5 — 標準ディレクティブの抽出。 [var: op value] にマッチするすべて — add、subtract、set、toggleなどの単純な操作。
ステップ6 — テキストのクリーン。 抽出されたディレクティブはすべて除去されます。余分な空白行は圧縮されます。結果は綺麗な物語テキストとエフェクトのリストになります。
プレイヤーは綺麗なテキストを見ます。エンジンはエフェクトを適用してゲーム状態を更新します。その後ルールが評価され(条件が満たされたかチェック)、サイクルが完了します。
なぜパース順序が重要か
JSONディレクティブが標準ディレクティブより先に抽出されるのは、[stats: merge {"health": 50, "mana": 30}] のようなディレクティブにはコロンと数字が含まれており、標準正規表現が誤解する可能性があるためです。JSONパターンを先に処理することで、エンジンは偽マッチを避けます。
ディレクティブが認識されない場合、最も一般的な原因は不正なJSON — 閉じていない波括弧や引用の欠落です。エンジンはパース不能なディレクティブを静かにスキップし、クラッシュしません。
完全なリファレンス → World Spec:変数
マクロ — エントリ内の動的テキスト
マクロは、エントリに書くプレースホルダで、AIに送る前にエンジンが実際の値に置き換えます。{{char}}、{{user}}、{{turnCount}} のような形をしています。
中心となるアイデア:エントリを1回書けば、あらゆるコンテキストに適応する。キャラクター名を変えれば、すべての {{char}} が自動更新。プレイヤーがペルソナを選べば、{{user}} がそれに従います。
必須マクロ
| マクロ | 展開先 | 出力例 |
|---|---|---|
{{char}} | 現在のキャラクター名 | Luna |
{{user}} | プレイヤー名(またはアクティブペルソナ名) | Kai |
{{turnCount}} | 現在のターン番号 | 42 |
この3つでほとんどのユースケースをカバーします。{{char}} と {{user}} はほぼすべてのエントリで使うでしょう。
ランダム性マクロ
| マクロ | 動作 | 例 |
|---|---|---|
{{random::a::b::c}} | 毎回ランダムに1つ選ぶ | {{random::sunny::cloudy::rainy}} は「cloudy」を返すかも |
{{pick::a::b::c}} | 安定した選択 — 同一ターン内で同じ結果 | {{pick::red::blue::green}} は同じターン中はいつも同じ色 |
{{roll::NdS+M}} | ダイスロール:N個のS面ダイスに修正値Mを加算 | {{roll::2d6+3}} は11を返すかも |
random と pick の違い:random はマクロが展開されるたびに振り直すので、同じエントリを2回展開すると違う結果になるかもしれません。pick はマクロのエントリ内の位置と現在のターン数に基づく安定したハッシュを使います — 同一ターン内では毎回同じ結果ですが、次のターンでは変わることがあります。
random はバリエーション用に(天気、群衆描写など)。pick は同一ターン内の一貫性が重要なときに(キャラクターの服装は2度言及されても同じであるべき)。roll はAIが反応するダイスベースのメカニクスに使います。
時間と日付のマクロ
| マクロ | 展開先 | 出力例 |
|---|---|---|
{{time}} | 現在時刻(HH:MM) | 14:30 |
{{date}} | 現在日付(ローカル形式) | 2026/5/13 |
{{weekday}} | 曜日 | Tuesday |
{{isodate}} | ISO日付形式 | 2026-05-13 |
{{isotime}} | ISO時刻形式 | 14:30:00 |
{{idle}} | 最後のプレイヤーメッセージからの経過時間 | 5 minutes |
これらは現実世界の時間であり、ゲーム内の時間ではありません。現実とフィクションの時間を融合するワールドや、アイドル検出メカニクス(「プレイヤーが10分応答しなかったらキャラクターが心配したメッセージを送る」など)に有用です。
コンテキストマクロ
| マクロ | 展開先 |
|---|---|
{{lastMessage}} | 直近メッセージの全文 |
{{lastUserMessage}} | プレイヤーの直近メッセージ |
{{lastCharMessage}} | キャラクターの直近メッセージ |
{{model}} | 使用中のAIモデル名 |
プレイヤーペルソナマクロ
プレイヤーは プロフィールページ → ペルソナカルーセル からペルソナを作成します — 外見、性格、背景を持つ名前付きアイデンティティです。ペルソナがアクティブなとき、これらのマクロはそこから値を引きます。
| マクロ | 展開先 |
|---|---|
{{persona_name}} | アクティブペルソナの名前 |
{{persona_appearance}} | 身体的描写 |
{{persona_personality}} | キャラクターの特徴 |
{{persona_backstory}} | 経歴と起源 |
{{persona}} | 上記4フィールドを結合 |
ヒント:System Presetsの「About the Player」というエントリに {{persona}} を入れておけば、AIは常にプレイヤーが誰をロールプレイしているか分かります。ペルソナを切り替えるとAIが自動的に追従します。
{{user}} はまずアクティブペルソナを確認します — あればペルソナ名を使い、なければワールドの playerName フィールドに戻ります。{{user}} を使う古いエントリはペルソナシステムとシームレスに動作します。
ユーティリティマクロ
| マクロ | 何をするか |
|---|---|
{{// comment text}} | コメント。何にも展開されない — AIに何も送らずにエントリにメモを残せる |
{{trim}} | 周囲の空白を食べる。マクロが奇妙な隙間を残すときの精密なフォーマット制御に |
変数フォールバック
マクロ名が組み込みマクロに一致しない場合、エンジンは変数IDに一致するかチェックします。mood という変数の現在値が "suspicious" なら、{{mood}} は "suspicious" に展開されます。
組み込みマクロにも変数にも一致しない場合、エンジンは {{xxx}} をそのまま残します。エラーもクラッシュもありません。
機能するパターン
比例ダメージの戦闘
health(number、0-100)を定義し、武器タイプごとのダメージ範囲をbehavior rulesで指定します。AIは物語を書き、末尾にディレクティブを付けます。
The bandit's dagger catches you across the ribs — a shallow
cut, but it burns. You stumble back, keeping your guard up.
[health: -8]
You swing your greatsword in a wide arc. The bandit tries to
dodge but catches the blade across the shoulder. He screams.
[enemy-hp: -22]プレイヤーは戦闘シーンを見ます。数字が帳簿付けをします。
条件付きエントリによる関係進行
数値変数(aria-trust、0-100)としきい値でゲートされたエントリをペアにします。AIはディレクティブで信頼を更新し、エントリシステムが各ステージでキャラクターの振る舞いについてAIが知る内容を自動調整します。
- エントリ「Aria — Guarded」(条件:
aria-trust < 30):堅苦しい、距離を保つ - エントリ「Aria — Warming」(条件:
aria-trust >= 30 AND < 60):意見を共有、たまに微笑む - エントリ「Aria — Close」(条件:
aria-trust >= 60):弱さを見せる、守ろうとする、内輪のジョーク
AIはディレクティブで数字を動かします:親切なやり取りの後の [aria-trust: +5]。エントリシステムがAIが見るAriaのバージョンを決めます。どちらのシステムも他方を知らず、変数を介してコミュニケーションします。
マクロによる動的シーン描写
コンテキストに適応するエントリ:
{{char}} looks up as {{user}} enters the room. The current time
is {{time}}, and the weather outside is {{random::clear::overcast::drizzling}}.
{{char}}'s mood seems {{mood}} today.キャラクターがLuna、プレイヤーがKai、午後2時、mood 変数が "tense" の場合、AIには次のように届きます。
Luna looks up as Kai enters the room. The current time is 14:00,
and the weather outside is overcast.
Luna's mood seems tense today.ダイスベースのスキルチェック
これをナレーター指示エントリに入れます。
When {{user}} attempts something risky, use the roll result
to determine success. Roll: {{roll::1d20}}. 15+ = success,
10-14 = partial success, below 10 = failure.AIは実際の数値を受け取り、それに応じてナレーションできます。判断を「AIの裁量」からダイスへ移すことで、より公平でゲームらしくなります。
よくあるミス
ディレクティブが間違った場所にある。 AIは物語応答の末尾にディレクティブを書くべきで、文の途中ではありません。インラインに現れてもパースは機能しますが、デバッグが難しくなります。behavior rulesでAIに「すべてのディレクティブを応答末尾に置いて」と指示できます。
AIに計算させようとする。 AIは [health: -15] を書きます。エンジンが減算します。「新しいhealth値を計算」のようなbehavior rulesは書かないでください — AIは正しいディレクティブを発するだけで、エンジンが算術(min/maxクランプ含む)を扱います。
ディレクティブのJSON構文エラー。 不一致の波括弧や欠落した引用符でエンジンが静かにディレクティブをスキップします。merge や push ディレクティブが適用されない場合、JSONペイロードが妥当か確認してください。エンジンはクラッシュせず、ただ先に進みます。
{{random}} と {{pick}} の混同。 エントリを展開するたびにキャラクターの目の色が変わるなら、pick のつもりが random を使っています。安定属性には pick、意図的なバリエーションには random を。
JSON Patchで過剰設計。 配列からアイテムを削除する必要が特になければ、括弧ディレクティブを使い続けてください。シンプルで、AIがより一貫して生成し、デバッグもしやすいです。
{{user}} がペルソナに従うことを忘れる。 プレイヤーがペルソナを切り替えると、{{user}} は新ペルソナの名前に変わります。通常はそれが望ましいのですが、セッション中に変わるべきでない方法で {{user}} を使っている場合は注意してください。
関連項目
- ゲーム状態の設計 — 変数型の選択とbehavior rulesの書き方
- 優れたエントリの書き方 — エントリ内での
{{macros}}の使用と条件付きエントリ設計 - オーディオ設計 — 完全なオーディオディレクティブのリファレンス
ディレクティブ構文と操作仕様 → World Spec:変数
