AI 指令与宏
AI 写出故事。嵌入在故事中的是微小的指令——[health: -15]、[gold: +50]——引擎在玩家看到之前就把它们剥离了。玩家读到的是干净的叙事,游戏状态在幕后悄然变化。
这就是指令系统:故事叙述与游戏机制之间的桥梁。宏是它的搭档——像 {{char}} 和 {{user}} 这样的占位符,让你的条目自动适应上下文。
如果你还没读过变量——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]方括号内三个部分:哪个变量、做什么、用什么值。
全部九种操作
| 操作 | 语法 | 示例 | 效果 |
|---|---|---|---|
| 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] | [config: delete "old-field"] | 从对象中删除键,或按索引从数组中删除元素 |
值的语法规则
- 数字:直接写数字——
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,不是 add)开头的符号(-、+、*)决定操作。没有符号意味着 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>支持四种 patch 操作:
| 操作 | 功能 | 示例 |
|---|---|---|
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 适用于两种特定情况:
按索引删除数组元素——
[inventory: delete 0]可以工作,但 JSON Patch 的{"op": "remove", "path": "/0"}是某些 AI 模型在处理数组时更一致产出的格式。对单个变量的批量操作——当你需要一次性更新一个 JSON 变量的多个字段时,单个 JSON Patch 块比五个单独的方括号指令更简洁。
如果你在为 AI 编写行为规则,告诉它用哪种格式。大多数世界专用方括号指令。只在你确实需要数组删除且方括号语法不够可靠时才引入 JSON Patch。
引擎如何处理 AI 输出
当 AI 返回回复时,引擎在玩家看到之前会通过一个解析管线处理它。理解这个管线有助于你调试指令未被识别的情况。
步骤 1 — 剥离思维标签。 某些模型(如 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: Variables
宏——条目中的动态文本
宏是你在条目中编写的占位符,引擎在发送给 AI 之前会用真实值替换它们。看起来像这样:{{char}}、{{user}}、{{turnCount}}。
核心思想:写一次条目,它就能适应任何上下文。改了角色的名字,每个 {{char}} 自动更新。玩家选了一个人设,{{user}} 跟着变。
基本宏
| 宏 | 展开为 | 示例输出 |
|---|---|---|
{{char}} | 当前角色的名字 | Luna |
{{user}} | 玩家的名字(或活跃人设名) | Kai |
{{turnCount}} | 当前回合数 | 42 |
这三个覆盖大多数场景。你几乎在每个条目中都会用到 {{char}} 和 {{user}}。
随机宏
| 宏 | 行为 | 示例 |
|---|---|---|
{{random::a::b::c}} | 每次随机选一个 | {{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 每次展开宏时都重新随机,同一条目展开两次可能得到不同结果。pick 基于宏在条目中的位置和当前回合数使用稳定哈希——在同一回合内每次都给出相同结果,但下一回合可能变化。
用 random 制造变化(天气、人群描述)。用 pick 处理需要同一回合内保持一致的内容(角色的服装被引用两次应该相同)。用 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}} | 以上四项的组合 |
技巧:在 System Presets 中放一个叫「关于玩家」的条目,内容包含 {{persona}},AI 就能始终了解玩家扮演的角色。当他们切换人设时,AI 自动跟上。
{{user}} 会先检查是否有活跃人设——如果有,使用人设名称。否则回退到世界的 playerName 字段。使用 {{user}} 的旧条目能与人设系统无缝配合。
工具宏
| 宏 | 功能 |
|---|---|
{{// comment text}} | 注释。展开为空——让你在条目中留笔记而不向 AI 发送任何内容 |
{{trim}} | 消除周围空白。用于在宏留下尴尬间隙时精确控制格式 |
变量回退
如果宏名称不匹配任何内置宏,引擎会检查它是否匹配某个变量 ID。如果你有一个名为 mood 的变量,当前值为「suspicious」,那么 {{mood}} 会展开为「suspicious」。
如果既不匹配内置宏也不匹配变量,引擎保留 {{xxx}} 原样。没有报错,没有崩溃。
有效的模式
带比例伤害的战斗
定义 health(number,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。两个系统互不知道对方的存在——它们通过变量通信。
用宏实现动态场景描述
一个能适应上下文的条目:
{{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 做数学。 AI 写 [health: -15]。引擎做减法。不要写行为规则说「计算新的生命值」——AI 只需要发出正确的指令,引擎处理算术,包括最小/最大值限制。
指令中的 JSON 语法错误。 大括号不匹配或引号缺失会导致引擎静默跳过指令。如果 merge 或 push 指令没有被应用,检查 JSON 载荷是否有效。引擎不会崩溃——它只是跳过继续。
混淆 {{random}} 和 {{pick}}。 如果角色的眼睛颜色每次条目展开时都变,你用了 random 但应该用 pick。用 pick 处理稳定属性,random 处理有意的变化。
过度使用 JSON Patch。 除非你确实需要从数组中删除元素,否则坚持用方括号指令。它们更简单,AI 生成得更一致,也更容易调试。
忘记 {{user}} 跟随人设。 如果玩家切换人设,{{user}} 会变为新人设的名字。这通常是你想要的,但如果你以不应该在会话中途改变的方式使用 {{user}},要注意这一点。
延伸阅读
指令语法和操作规范 → World Spec: Variables
