> ## Documentation Index
> Fetch the complete documentation index at: https://guide.moyostory.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 规则值、表达式与输入

> 掌握宏嵌入文本、动态表达式、指令参数、DOM 输入和四种变量操作。

“更新变量”和“执行条件”都需要填写值。当前编辑器提供两种写法：**宏嵌入文本**和**动态表达式**。两者的结果类型不同，不能只看起来相似就互换。

## 先选对值的类型

| 类型    | 最终得到什么                      | 适合场景                         |
| ----- | --------------------------- | ---------------------------- |
| 宏嵌入文本 | 一段字符串                       | 固定文字、把参数嵌进一句话、整理发送给 AI 的自然语言 |
| 动态表达式 | 表达式计算结果，可以是字符串、数字、布尔值、数组或对象 | 计算数值、判断、复制参数原值、创建数组或对象       |

例如，指令参数“内容”的引用方式是 `$内容`：

```text theme={null}
宏嵌入文本：他说：{{$内容}}
结果：字符串“他说：晚上见。”

动态表达式：$内容
结果：参数“内容”原本的值
```

需要把一个参数原样写入变量时，优先使用动态表达式 `$参数名`。需要把多个值整理成一句话时，再使用宏嵌入文本。

## 宏嵌入文本

宏嵌入文本会保留你输入的普通文字，并替换其中的 `{{…}}`：

| 可引用内容    | 写法                                    | 示例结果                          |
| -------- | ------------------------------------- | ----------------------------- |
| 当前指令参数   | `{{$参数名}}`                            | `{{$角色}}：{{$内容}}`             |
| State 变量 | `{{state.变量名}}`                       | `路线：{{state.selected_route}}` |
| Frame 变量 | `{{frame.字段名}}`                       | `当前说话人：{{frame.speaker}}`     |
| 页面输入或属性  | `{{选择器::读取方式}}`                       | `{{#message-input::value}}`   |
| 当前匹配元素   | `{{:读取方式}}`                           | `{{:attr(data-prompt)}}`      |
| 时间       | `{{DATE}}`、`{{TIME}}`、`{{TIMESTAMP}}` | 当前日期、时间或 ISO 时间戳              |

完整示例：

```text theme={null}
玩家选择“{{:attr(data-label)}}”，当前路线是 {{state.selected_route}}。
```

宏嵌入文本始终得到字符串。输入 `true` 得到的是文字 `"true"`，不是布尔值；输入 `[]` 得到的是文字 `"[]"`，不是数组。

<Warning>
  指令参数必须带 `$`：写 `{{$内容}}`，不是 `{{内容}}`。页面代码也不使用这套宏读取普通变量；页面代码直接写 `frame.text` 或 `state.player_name`。
</Warning>

## 动态表达式

动态表达式使用 JavaScript 表达式计算一个值。它可以直接读取：

* `state.x`：State 变量；
* `frame.x`：当前 Frame 字段；
* `$参数名`：当前“解析指令”规则的具名参数；
* `Math`、`JSON`、`Array`、`Object`、`String`、`Number`、`Boolean`、`Date`、`RegExp` 等安全内置对象。

常用示例：

| 目的         | 动态表达式                                                                              |
| ---------- | ---------------------------------------------------------------------------------- |
| 原样读取指令参数   | `$内容`                                                                              |
| 好感度加 1     | `Number(state.affection ?? 0) + 1`                                                 |
| 根据角色决定空值   | `$角色 === "旁白" ? "" : $角色`                                                          |
| 创建空数组      | `[]`                                                                               |
| 创建一个选项对象   | `({ text: $文本, value: $路线, prompt: $提示 })`                                         |
| 合并并替换数组第一项 | `state.items.map((item, index) => index === 0 ? ({ ...item, done: true }) : item)` |

表达式只有一行时直接填写结果表达式，不写 `return`。对象字面量外层加圆括号，避免被解释成代码块。

虽然运行时也能把部分裸名称先按 State、再按 Frame 查找，例如 `affection + 1`，文档示例统一写完整的 `state.affection` 或 `frame.speaker`，避免同名字段造成歧义。

### 在表达式中读取页面输入

输入引用仍写在双花括号中。运行时会先把它替换成字符串，再计算表达式：

```text theme={null}
Number({{#amount-input::value}}) + 1
```

页面输入默认是字符串。参与加减前使用 `Number()`，否则 `"8" + 1` 会得到 `"81"`。

## 指令参数怎样进入规则

选择“解析指令”后，触发条件区域会列出每个参数的引用方式。例如指令“台词”定义：

* 单行参数：`角色`、`背景`、`立绘`；
* 跨行参数：`内容`。

规则中对应使用：

```text theme={null}
$角色
$背景
$立绘
$内容
```

参数名是引用契约。名称应使用中文、英文字母、数字或下划线，不能以数字开头，也不要包含空格、标点或 `$`。同一指令内不能重名。

修改参数名后，旧规则中的 `$旧名称` 不会自动变成 `$新名称`。

## 四种变量操作

变量目标必须以 `frame.` 或 `state.` 开头。

| 操作       | 目标原本应是什么 | 执行结果           |
| -------- | -------- | -------------- |
| `set`    | 任意类型     | 用新值替换原值        |
| `append` | 数组或尚不存在  | 把一个值加到数组末尾     |
| `merge`  | 对象或尚不存在  | 把新对象的第一层字段合并进去 |
| `remove` | 数组或尚不存在  | 删除与给定值匹配的数组项   |

### set

设置文字：

```text theme={null}
目标：frame.text
操作：set
类型：动态表达式
值：$内容
```

设置数组：

```text theme={null}
目标：frame.choices
操作：set
类型：动态表达式
值：[]
```

### append

把同一帧中的多条“选项”指令保存成数组：

```text theme={null}
目标：frame.choices
操作：append
类型：动态表达式
值：({ text: $文本, value: $路线, prompt: $提示 })
```

目标已经存在但不是数组时，`append` 不会执行。先在“进入帧”规则中把瞬态列表设为 `[]`，再追加内容。

### merge

把部分字段合并进对象：

```text theme={null}
目标：state.profile
操作：merge
类型：动态表达式
值：({ affection: 5, chapter: 2 })
```

`merge` 只合并对象的第一层，不会递归合并内部对象。

### remove

从数组删除一个基本值：

```text theme={null}
目标：state.clues
操作：remove
类型：宏嵌入文本
值：旧库区钥匙
```

从对象数组删除匹配项：

```text theme={null}
目标：state.todos
操作：remove
类型：动态表达式
值：({ id: 1 })
```

对象匹配是第一层字段比较。上例会删除所有 `id` 等于 `1` 的项目。

### 不要直接写数组下标路径

不要把目标写成 `state.items[0].done`。当前规则写入器不把数组下标当作可编辑路径。需要修改数组内部某项时，使用 `set` 替换整个数组：

```text theme={null}
目标：state.items
操作：set
类型：动态表达式
值：state.items.map((item, index) => index === 0 ? ({ ...item, done: true }) : item)
```

## 从页面元素读取值

“用户交互”规则先用 Element Selector 找到触发元素，再按值中的输入引用采集内容。

| 输入引用                             | 读取内容         |
| -------------------------------- | ------------ |
| `{{#message-input::value}}`      | 指定输入框的值      |
| `{{#choice::text}}`              | 指定元素的纯文字     |
| `{{#card::html}}`                | 指定元素的内部 HTML |
| `{{#choice::attr(data-prompt)}}` | 指定元素的属性      |
| `{{:attr(data-prompt)}}`         | 本次匹配到的按钮自身属性 |

例如页面代码提供：

```tsx theme={null}
<button
  className="story-choice"
  data-prompt="我决定去旧库区。"
  type="button"
>
  去旧库区
</button>
```

点击规则的 Element Selector 填 `.story-choice`，触发 AI 回复的“自定义用户输入”填：

```text theme={null}
{{:attr(data-prompt)}}
```

这样显示文案和发送给 AI 的自然语言行动可以分别维护。

## 执行条件

“+ 执行条件”只控制它所在的那一个动作。条件左侧虽然标为“变量名”，实际可以填写变量或表达式；右侧仍可选择“宏嵌入文本”或“动态表达式”。

| 运算符                     | 含义                |
| ----------------------- | ----------------- |
| `==` / `!=`             | 相等 / 不相等          |
| `>` / `<` / `>=` / `<=` | 数值比较；无法转成数字时按文本比较 |
| `contains`              | 左侧文本是否包含右侧文本      |
| `includes`              | 左侧数组是否包含右侧单项      |

布尔值和数字比较时，右侧使用动态表达式：

```text theme={null}
左侧：state.has_key
运算符：==
右侧类型：动态表达式
右侧值：true
```

如果右侧改成宏嵌入文本 `true`，比较的是字符串，不是布尔值。

## 动作顺序

一条规则中的动作从上到下执行，后面的表达式可以读取前面刚写入的值。例如：

1. 更新 `state.selected_route`；
2. 跳转到目标页面节点；
3. 触发 AI 回复。

“触发 AI 回复”在同一规则中至多一次，并且必须是最后一个动作。

## 快速排错

| 现象           | 优先检查                             |
| ------------ | -------------------------------- |
| 数组显示成文字 `[]` | 是否误用了“宏嵌入文本”                     |
| 对象创建失败       | 动态表达式的对象外层是否有圆括号                 |
| 指令参数为空       | 是否写成 `$参数名`，且名称与注册表完全一致          |
| 数字相加变成拼接     | 页面输入或 State 值是否需要 `Number()`     |
| 按钮点击无反应      | Element Selector 是否匹配，规则组是否被节点选中 |
| AI 没收到按钮含义   | 自定义用户输入是否读取了正确的 `data-*` 属性      |

<Tip>
  MoMo：先问结果要的是字符串、数字、数组还是对象。类型确定后，再选“宏嵌入文本”或“动态表达式”。
</Tip>
