Skip to content

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"]从对象中删除键,或按索引从数组中删除元素

值的语法规则

  • 数字:直接写数字——103.5-7
  • 字符串:双引号包裹——"forest""magic sword"
  • 布尔值:裸关键字——truefalse
  • 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 或显示名称

指令可以通过 IDplayer-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]——支持 playstopcrossfadevolume 等动作和曲目链。完整的音频指令参考请参阅音频设计


JSON Patch 格式

对于复杂操作——尤其是从数组中删除元素——引擎支持 XML 包装的 JSON Patch 格式。这也是从 SillyTavern 迁移的世界所使用的格式。

xml
<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 适用于两种特定情况:

  1. 按索引删除数组元素——[inventory: delete 0] 可以工作,但 JSON Patch 的 {"op": "remove", "path": "/0"} 是某些 AI 模型在处理数组时更一致产出的格式。

  2. 对单个变量的批量操作——当你需要一次性更新一个 JSON 变量的多个字段时,单个 JSON Patch 块比五个单独的方括号指令更简洁。

如果你在为 AI 编写行为规则,告诉它用哪种格式。大多数世界专用方括号指令。只在你确实需要数组删除且方括号语法不够可靠时才引入 JSON Patch。


引擎如何处理 AI 输出

当 AI 返回回复时,引擎在玩家看到之前会通过一个解析管线处理它。理解这个管线有助于你调试指令未被识别的情况。

步骤 1 — 剥离思维标签。 某些模型(如 Gemini)会在 <thinking>...</thinking> 标签中输出内部推理。引擎首先移除这些。

步骤 2 — 提取 JSON Patch 块。 引擎扫描 <UpdateVariable> XML 块并将其转换为内部效果操作。

步骤 3 — 提取音频指令。 匹配 [audio: ...] 的内容被提取出来并排入音频系统队列。

步骤 4 — 提取 JSON 指令。 包含 JSON 值的方括号指令(mergepush、带对象/数组的 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

randompick 的区别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 语法错误。 大括号不匹配或引号缺失会导致引擎静默跳过指令。如果 mergepush 指令没有被应用,检查 JSON 载荷是否有效。引擎不会崩溃——它只是跳过继续。

混淆 {{random}}{{pick}} 如果角色的眼睛颜色每次条目展开时都变,你用了 random 但应该用 pick。用 pick 处理稳定属性,random 处理有意的变化。

过度使用 JSON Patch。 除非你确实需要从数组中删除元素,否则坚持用方括号指令。它们更简单,AI 生成得更一致,也更容易调试。

忘记 {{user}} 跟随人设。 如果玩家切换人设,{{user}} 会变为新人设的名字。这通常是你想要的,但如果你以不应该在会话中途改变的方式使用 {{user}},要注意这一点。


延伸阅读

指令语法和操作规范 → World Spec: Variables