AI ディレクティブとマクロ
AI は物語を書きます。その物語の中には [health: -15] や [gold: +50] のような短い指示が埋め込まれていて、プレイヤーの目に入る前にエンジンが取り除きます。プレイヤーはきれいな物語を読み、その裏でゲームの状態が変わっていきます。
マクロはその逆向きです。エントリに書いた {{char}} や {{user}} のようなプレースホルダーを、AI が読む前にエンジンが埋めます。
変数 をまだ読んでいないなら、先にそちらを読んでください。ディレクティブは、何を変えているのかがわかってはじめて意味を持ちます。
ディレクティブを使う変数
精密トラッキング がオンの変数(新しく作った数値とスイッチの変数は最初からオン)は、返信のたびにトラッキング用のアシスタントが変えます。ディレクティブでは変わりません。AI からは読み取り専用に見え、AI がその変数にディレクティブを書いても捨てられます。ディレクティブは、AI が書き込んでいいそれ以外の変数を変えるためのものです。精密トラッキングがオフの変数、選択肢のない文字の変数、リスト/表の変数がこれにあたります。AI アクセス を AI は読み取り専用 や エンジン専用 にした変数も、ディレクティブを受け付けません。
ディレクティブがある理由
テーブルトーク RPG なら、ゲームマスターが「ゴブリンが腕を切りつけた。15 ダメージ」と言い、プレイヤーがキャラクターシートの数字を書き換えます。Yumina では AI がゲームマスターで、エンジンがキャラクターシートです。
AI に「プレイヤーの体力は 85 になった」と書かせるだけ、というやり方もありえます。でもそれだと、エンジンが自然な文章を読み解いて、どの変数が変わったかを突き止め、新しい値を計算しなければなりません。壊れやすく、当てになりません。
そこで AI は、物語の最後に決まった形の括弧を書きます。
The goblin's claw rakes across your forearm, drawing a hot line of pain.
[health: -15]エンジンは [health: -15] を見つけると、15 を引き、最小・最大の範囲に収め、文章からディレクティブを取り除きます。プレイヤーには物語だけが見え、ステータスパネルが更新されます。
AI が計算をしたり、範囲を覚えておいたりする必要はありません。どちらもエンジンがやります。
ディレクティブの書き方
ディレクティブはすべて同じ形です。
[variable-id: operation value]括弧の中は 3 つの部分でできています。どの変数を、どうするか、どの値で。
9 つの操作
| 操作 | 書き方 | 例 | 何が起きるか |
|---|---|---|---|
| Set | [var: set value] または [var: value] | [location: set "forest"] | 変数の値をまるごと置き換える |
| Add | [var: add N] または [var: +N] | [gold: +50] | 数値の変数に N を足す |
| Subtract | [var: subtract N] または [var: -N] | [health: -15] | 数値の変数から N を引く |
| Multiply | [var: multiply N] または [var: *N] | [damage: *2] | 数値の変数を N 倍する |
| Toggle | [var: toggle] | [has-key: toggle] | スイッチを反転する(true は false に、false は true に) |
| Append | [var: append "text"] | [log: append ", found key"] | 文字の変数の末尾に文字を足す |
| Merge | [var: merge {...}] | [stats: merge {"level": 2}] | JSON 変数にオブジェクトを浅くマージする |
| 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 になる。 |
JSON 変数の中へのパス。 [relations.chars.lin: merge {...}] は JSON 変数の内側に書き込みます。キーがまだなければ、書き込むときに作られます。merge ならオブジェクトが、push なら要素 1 つのリストができ、add/subtract は 0 から数えます。ただの値を通り抜けるパス(hp が数値なのに hp.max を指す、など)は、上書きされずに飛ばされます。配列の要素は list.0.hp か list[0].hp で指定します。
値の書き方
- 数値:数字をそのまま。
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 です。 なので [health: 50] は 50 を足すのではなく、いまの値を 50 に置き換えます。
マイナスの値をはっきり設定したいとき(めったにありませんが)は、[health: set -10] と書きます。
名前の指定:ID か表示名か
ディレクティブでは、変数を ID(player-hp)でも 表示名(Player HP)でも指定できます。エンジンは名前と ID の対応表を持っていて、どちらでも解決します。ID のほうが確実で、表示名にスペースが入っているときは特にそうです。
JSON 変数の入れ子のパス
入れ子の構造を持つ JSON 変数なら、AI はドット記法で特定のフィールドだけを狙えます。
[relationships.aria.trust: +10]
[game-state.factions.ember-court.affinity: set 75]
[inventory.0.durability: -1]パスはエンジンが自動でたどります。途中のオブジェクトがなければ作ります。なので AI は、複雑なオブジェクトの奥にあるフィールドをひとつだけ、ほかに触れずに更新できます。
オーディオディレクティブ
オーディオも同じような括弧の書き方 [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 と括弧のディレクティブの使い分け
たいていは括弧のディレクティブのほうが単純で、AI も安定して書けます。JSON Patch が役に立つのは、次の 2 つの場合です。
配列の要素を位置で取り除く:
[inventory: delete 0]でもできますが、配列を扱うときは JSON Patch の{"op": "remove", "path": "/0"}のほうが安定して書けるモデルもあります。ひとつの変数をまとめて操作する:ひとつの JSON 変数のフィールドをいくつも一度に更新したいなら、括弧のディレクティブを 5 つ並べるより、JSON Patch のブロックひとつのほうがすっきりします。
AI 向けのビヘイビアルールを書くなら、どちらの形式を使うか書いておきましょう。ほとんどのワールドは括弧のディレクティブだけを使っています。JSON Patch を持ち込むのは、配列から取り除く操作がどうしても必要で、括弧の書き方では安定しないときだけにしてください。
エンジンが AI の出力を処理する流れ
AI が返信を返すと、エンジンはプレイヤーに見せる前に、それを決まった手順で読み取ります。この順番を知っておくと、ディレクティブが拾われないときの原因を探しやすくなります。
ステップ 1:思考タグを取り除く。 一部のモデル(Gemini など)は、内部の推論を <thinking>...</thinking> タグで出力します。エンジンはまずこれを取り除きます。
ステップ 2:JSON Patch のブロックを取り出す。 <UpdateVariable> の XML ブロックを探し、内部の効果の操作に変換します。モデルによってはディレクティブを json のコードブロックで囲んでくることがあり、それもこの段階で拾います。
ステップ 3:オーディオディレクティブを取り出す。 [audio: ...] に一致するものをすべて取り出し、オーディオのしくみに渡します。
ステップ 4:JSON のディレクティブを取り出す。 JSON の値を持つ括弧のディレクティブ(set、merge、push、delete)を取り出します。これを先にやるのは、JSON の中身に単純な正規表現を惑わせる文字が入っていることがあるからです。[image: …] の埋め込みには触れません。
ステップ 5:ふつうのディレクティブを取り出す。 [var: op value] に一致するもの、つまり add、subtract、set、toggle などの単純な操作です。
ステップ 6:文章を整える。 取り出したディレクティブをすべて消し、余分な空行を詰めます。きれいな物語の文章と、効果の一覧が残ります。
そのあと、返答の処理 のルールがあれば、整えた文章に対して動きます。エンジンは効果を(精密トラッキングが決めた分と合わせて)適用し、ビヘイビアを確かめます。プレイヤーには整えた文章が表示されます。
読み取りの順番が大事な理由
JSON のディレクティブをふつうのディレクティブより先に取り出すのは、[stats: merge {"health": 50, "mana": 30}] のようなディレクティブにはコロンや数字が入っていて、ふつうの正規表現が読み違えるからです。JSON の形を先に処理すれば、誤って一致するのを防げます。
ディレクティブが認識されないとき、いちばん多い原因は JSON の書き間違いです。波括弧が閉じていない、引用符が抜けている、など。エンジンは読み取れないディレクティブを何も言わずに飛ばし、止まったりはしません。
全体のリファレンス → World Spec:変数
マクロ:エントリの中で変わる文字
マクロはエントリに書くプレースホルダーで、AI に送る前にエンジンが実際の値に置き換えます。{{char}}、{{user}}、{{turnCount}} のような形です。
エントリは一度書けば、状況に合わせて変わってくれます。キャラクターの名前を変えれば、{{char}} は全部自動で変わります。プレイヤーがペルソナを選べば、{{user}} がそれに合わせます。
基本のマクロ
| マクロ | 置き換わるもの | 出力の例 |
|---|---|---|
{{char}} | いまのキャラクターの名前 | Luna |
{{user}} | プレイヤーの名前(または使用中のペルソナの名前) | Kai |
{{turnCount}} | いまのターン数 | 42 |
たいていはこの 3 つで足ります。{{char}} と {{user}} は、ほとんどのエントリで使うことになります。
ランダムのマクロ
| マクロ | 動き | 例 |
|---|---|---|
{{random::a::b::c}} | 毎回ランダムにひとつ選ぶ | {{random::sunny::cloudy::rainy}} が「cloudy」になる、など |
{{pick::a::b::c}} | 選んだものが変わらない。同じターンの中では同じ結果 | {{pick::red::blue::green}} は同じターンならいつも同じ色 |
{{roll::NdS+M}} | サイコロ:S 面のサイコロを N 個振り、M を足す | {{roll::2d6+3}} が 11 になる、など |
random と pick の違い:random はマクロが展開されるたびに振り直すので、同じエントリを 2 回展開すると結果が違うことがあります。pick は、エントリの中でのマクロの位置といまのターン数から決まる値を使うので、同じターンの中では何度展開しても同じ結果になり、次のターンでは変わることがあります。
バリエーションがほしいとき(天気、人混みの描写など)は random。同じターンの中でそろっていてほしいとき(キャラクターの服装は 2 回出てきても同じであるべき)は pick。AI が反応できるサイコロのしくみがほしいときは roll を使います。
時刻と日付のマクロ
| マクロ | 置き換わるもの | 出力の例 |
|---|---|---|
{{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 つをまとめたもの。ペルソナに追加の項目があればそれも含む |
ヒント:「About the Player」という キャラクターと世界 のエントリに {{persona}} を入れておけば、AI はプレイヤーが誰を演じているかをいつも知っています。ペルソナを切り替えれば、AI も自動でついてきます。
{{user}} は使用中のペルソナの名前になり、ペルソナがないときはプレイヤーのユーザー名になります。どちらもわからないときは、カードのデフォルトのプレイヤー名、それもなければ「Player」になります。
補助のマクロ
| マクロ | 何をするか |
|---|---|
{{// comment text}} | コメント。何にも置き換わらないので、AI には何も送らずにエントリにメモを残せる |
{{trim}} | 前後の空白を消す。マクロのせいで変なすき間ができたときの細かい調整に |
変数へのフォールバック
マクロの名前が組み込みのマクロのどれにも一致しないと、エンジンは変数の ID に一致するかを確かめます。ID が mood でいまの値が "suspicious" の変数があれば、{{mood}} は "suspicious" になります。これが使えるのは、英字・数字・アンダースコアだけでできた ID のときです。{{player_hp}} は動き、{{player-hp}} は動きません。キャンバスで追加した変数にはハイフン入りのランダムな ID が付くので、マクロとして使う前に、その変数の設定の 詳細設定 で ID をシンプルなものに変えておいてください。
組み込みのマクロにも変数にも一致しなければ、エンジンは {{xxx}} をそのまま残します。エラーにも、止まることにもなりません。
うまくいくパターン
ダメージに幅のある戦闘
health(数値、0-100)を定義し、ビヘイビアルールで武器の種類ごとのダメージの幅を決めておきます。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 — 警戒」(条件:
aria-trust < 30):かしこまっていて、距離を置く - エントリ「Aria — 打ち解けはじめ」(条件:
aria-trust >= 30 AND < 60):意見を話す、ときどき笑う - エントリ「Aria — 親密」(条件:
aria-trust >= 60):弱さを見せる、守ろうとする、内輪のジョーク
AI はディレクティブで数字を動かします。やさしいやりとりのあとに [aria-trust: +5]、という具合です(精密トラッキングがオンなら、代わりにトラッキング用のアシスタントが動かします)。AI がどの Aria を見るかは、エントリのしくみが決めます。2 つのしくみはお互いを知らず、変数を通じてやりとりしています。
マクロで場面の描写を変える
状況に合わせて変わるエントリ:
{{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 に伝えています。それでも文中に書いてくるモデルもあり、その場合も読み取りはできますが、原因を探すのが難しくなります。
AI に計算させようとする。 AI は [health: -15] と書き、引き算はエンジンがやります。「新しい体力の値を計算して」のようなビヘイビアルールは書かないでください。AI は正しいディレクティブを出すだけでよく、最小・最大に収めるところまで含めて、計算はエンジンがやります。
ディレクティブの JSON の書き間違い。 波括弧が合っていない、引用符が抜けている、といったミスがあると、エンジンはそのディレクティブを何も言わずに飛ばします。merge や push のディレクティブが反映されないときは、JSON の中身が正しいか確かめてください。エンジンは止まらず、ただ先に進みます。
{{random}} と {{pick}} を取り違える。 エントリが展開されるたびにキャラクターの目の色が変わるなら、pick を使うつもりで random を使っています。変わらない特徴には pick、わざと変化をつけたいところには random を使います。
JSON Patch でやりすぎる。 配列から要素を取り除く必要が特にないなら、括弧のディレクティブを使いましょう。単純で、AI も安定して書けて、原因も探しやすいです。
{{user}} がペルソナに合わせて変わるのを忘れる。 プレイヤーがペルソナを切り替えると、{{user}} は新しいペルソナの名前になります。たいていはそれで問題ありませんが、セッションの途中で変わっては困る使い方をしているなら気をつけてください。
関連項目
- ゲーム状態の設計 — 変数の型の選び方とビヘイビアルールの書き方
- 優れたエントリの書き方 — エントリの中での
{{macros}}の使い方と、条件付きのエントリの設計 - オーディオ設計 — オーディオディレクティブの全体のリファレンス
ディレクティブの書き方と操作の仕様 → World Spec:変数
