变量管理2.0
适用环境:SillyTavern + LittleWhiteBox
为什么要有 2.0?
1.0 版本需要写”录入”、“变更”、“追加”、“遗忘”这些指令词,结构控制也比较松散:
# 1.0 写法(旧)
<plot-log>
录入:
角色:
姓名: "苏晚"
变更:
角色:
生命值: -20
追加:
角色:
技能:
- 火球术
遗忘:
角色:
临时状态:
</plot-log>2.0 的设计思路:你看到的就是一个状态面板,改哪里就写哪里。
# 2.0 写法(新)
<state>
角色.生命值: -20
角色.技能: +"火球术"
角色.临时状态: null
</state>2.0 的改进:
- 不用写”录入/变更/追加/遗忘”,直接写就是设置
生命值: -20就是减,技能: +"火球术"往数组里加一项(字段不存在会自动创建空数组)- 新增
$schema结构模板,可以限制 AI 只能改哪些字段。2.0默认不守护。 - 原有
$range/$step/$enum/$ro等约束规则
一、三种数据类型
在开始之前,你需要知道变量里能放什么东西。
1. 普通值
就是一个单独的东西:数字、文字、真假。
生命值: 100 # 数字
姓名: "苏晚" # 文字(字符串)
是否存活: true # 真/假(布尔)2. 对象
想象成一个抽屉柜,每个抽屉都有标签(名字)。
角色:
姓名: "苏晚" # 抽屉1
生命值: 100 # 抽屉2
心情: "开心" # 抽屉3你要拿东西,就说”角色的姓名”,写成 角色.姓名。
3. 数组
想象成排队,没有名字,只有位置(从 0 开始数)。
技能:
- 火球术 # 第 0 个
- 冰冻术 # 第 1 个
- 治疗术 # 第 2 个你要拿东西,就说”技能的第 0 个”,写成 技能[0]。
二、六种操作
所有操作都写在 <state>...</state> 里面。
1. 设置/覆盖
直接写值,不管原来是什么,直接变成新的。
<state>
角色.地点: "图书馆"
角色.心情: "紧张"
角色.生命值: 80
</state>也可以用缩进写法(效果一样):
<state>
角色:
地点: "图书馆"
心情: "紧张"
生命值: 80
</state>2. 数字加减
在数字前面加 + 或 -。
<state>
角色.生命值: -20 # 扣 20 点血
角色.好感度: +5 # 加 5 点好感
角色.金币: +100 # 加 100 金币
</state>3. 直接设置负数
如果你想把一个值直接设成负数(而不是”减去多少”),用括号包起来:
<state>
温度: (-10) # 直接设成 -10 度
角色.债务: (-500) # 直接设成 -500(欠债)
</state>不加括号的 -10 意思是”减去 10”,加了括号的 (-10) 意思是”直接变成 -10”。
4. 往数组里加东西
在内容前面加 +。
<state>
# 加一个字符串
角色.技能: +"雷电术"
# 加多个
角色.技能: +["盾墙", "治疗"]
# 加一个对象
角色.背包: +{"名称": "回血药", "数量": 3}
</state>5. 从数组里删东西
按位置删(用 null):
<state>
角色.技能[0]: null # 删掉第 0 个
角色.背包[2]: null # 删掉第 2 个
</state>注意:删除后索引会重排(后面的元素往前移)。但系统内部做了倒序处理,所以 AI 这回合看到是
# [几],就删几,不需要心算重排后的结果。
按内容删(用 -):
<state>
角色.技能: -"火球术" # 删掉叫"火球术"的
角色.技能: -["火球术", "冰冻术"] # 删掉多个
</state>匹配规则:精确匹配(JSON 序列化后比较),只删第一个匹配项。如果没找到,静默成功(什么都不做)。
对象必须完全一致:包括字段顺序。写入时
{"数量": 3, "名称": "药"},删除时写{"名称": "药", "数量": 3}会找不到。所以对象数组强烈建议用索引删:
背包[0]: null
6. 删掉整个属性
把值设成 null。
<state>
角色.临时状态: null # 删掉这个属性
敌人: null # 删掉整个敌人对象
</state>空字符串 vs null:
姓名: ""→ 设成空字符串(字段还在,值是空)姓名: null→ 删除这个属性(字段消失)
三、同一字段多次操作
按行顺序执行,不是合并成一条:
<state>
角色.生命值: +10
角色.生命值: -5 # 在 +10 之后再 -5,最终 +5 效果
</state>假设原值 100:
- 执行
+10→ 110 - 执行
-5→ 105
四、类型要求
每种操作对字段类型有要求,类型不对会失败并记录到变量 LWB_STATE_ERRORS:
| 操作 | 要求的类型 | 字段不存在时 |
|---|---|---|
| 直接赋值 | 任意 | 自动创建 |
+N / -N(数字加减) | 数字(或可转成数字的字符串) | 当作 0 |
+"xxx" / +[...](数组添加) | 数组 | 自动创建空数组 |
-"xxx" / -[...](数组按值删除) | 数组 | 失败 |
[索引]: null(数组按位置删除) | 数组 | 失败 |
: null(删除属性) | 任意 | 静默成功(本来就没有) |
类型不匹配:如果
$schema定义了生命值: 0(数字),但写入字符串"满血",会被拒绝并记录到变量LWB_STATE_ERRORS。
五、行尾注释
可以在行尾用 # 写注释,不会影响执行:
<state>
角色.生命值: +10 # 吃了回血药
角色.金币: -50 # 买了东西
技能: +"火球术" # 学会了新技能
</state>注意:引号里的 # 不会被当成注释:
<state>
备注: "今天#很开心" # 这才是注释
</state>六、结构模板($schema)
核心思想:你先画一张图纸,AI 只能在图纸范围内写。
为什么需要?
假设你有一个”后宫”系统,以后会有张三、李四、王五……这些名字现在还不存在。但你希望:
- AI 可以随便加新人名
- 但每个人下面只能有生命值、好感度这些固定字段
- 不许 AI 乱加”心情”、“战力”这种你没设计的字段
用 $schema 就能做到。
基本写法
<state>
$schema 数据
角色:
姓名: ""
生命值: 0
技能: []
</state>重要:
$schema里的值只用来表示类型,不会真的写入。要初始化数据,需要另外写一段。- 路径必须从根变量名开始:上例中
数据是根变量名,会生成规则路径数据.角色.姓名等。
类型占位符
| 写法 | 表示的类型 |
|---|---|
"" | 字符串 |
0 | 数字 |
true 或 false | 布尔 |
[] | 数组(可以往里加东西) |
{} | 对象(可以随便加子字段) |
对象的三种形式
1) 空对象(完全自由)
$schema 数据
杂物: {}效果:杂物 下面可以加任何东西,没有限制。
2) 固定结构对象(白名单)
$schema 数据
角色:
姓名: ""
生命值: 0效果:角色 下面只能有 姓名 和 生命值,加别的会被拒绝。
3) 通配对象(任意 key,但内部结构固定)
$schema 数据
后宫:
"*":
生命值: 0
好感度: 0效果:
- ✅
后宫.张三.生命值: 100— 允许(张三是新人名,生命值在模板里) - ✅
后宫.李四.好感度: 50— 允许 - ❌
后宫.张三.心情: "开心"— 拒绝(心情不在模板里)
人话:可以加 N 个人,但每个人下面只能有固定的那几个 key。
通配符可以嵌套:
$schema 数据
区域:
"*": # 任意地点名
NPC:
"*": # 任意 NPC 名
好感度: 0数组的两种形式
1) 空数组(随便加)
$schema 数据
技能: []效果:可以 push 任何东西进去。
2) 带模板的数组(每个元素有固定结构)
$schema 数据
背包:
- 名称: ""
数量: 0效果:每个背包物品必须有 名称 和 数量,不能有别的字段。
七、约束规则
除了 $schema 限制结构,还可以给具体字段加约束。
写法
在 <state> 块里,用 $ 开头单独写一行(顶格,不要缩进):
<state>
$schema 数据
角色:
姓名: ""
生命值: 0
$ro 数据.角色.姓名
$range=[0,100] 数据.角色.生命值
$step=20 数据.角色.生命值
数据:
角色:
姓名: "苏晚"
生命值: 100
</state>路径必须写完整:从根变量名开始,比如
数据.角色.姓名,不能省略成角色.姓名。
规则速查表
| 写法 | 意思 | 效果 |
|---|---|---|
$ro 路径 | 只读 | 不许改这个字段 |
$range=[最小,最大] 路径 | 数值范围 | 超出范围自动限制 |
$step=N 路径 | 步长限制 | 每次最多变化 N |
$enum={值1;值2;值3} 路径 | 枚举 | 只能是这几个值之一 |
规则行为
$ro(只读)
$ro 数据.角色.姓名
# AI 写 数据.角色.姓名: "新名字" → 被拒绝$range(自动限制到范围内)
$range=[0,100] 数据.角色.生命值
# AI 写 角色.生命值: +200 → 自动限制到 100
# AI 写 角色.生命值: -999 → 自动限制到 0$step(自动限制单次变化量)
$step=20 数据.角色.生命值
# AI 写 角色.生命值: +50 → 自动限制到 +20
# AI 写 角色.生命值: -100 → 自动限制到 -20$enum(不在列表里直接拒绝)
$enum={晴;阴;雨;雪} 数据.天气
# AI 写 天气: "晴" → 允许
# AI 写 天气: "龙卷风" → 被拒绝规则的持久性
规则只需初始化时注册一次,之后一直生效。
规则存储在聊天元数据(chatMetadata.LWB_RULES_V2)里。每次执行 <state> 时会自动加载已有规则,所以 AI 日常回复只需要写数据操作。
如果 AI 在回复里又写了 $schema:
会合并,不是覆盖:
- 新路径的规则会被添加
- 同路径的规则会被更新
- 不会删除已有规则
# 初始化时
$schema 数据
角色:
姓名: ""
生命值: 0
# AI 后来又写
$schema 数据
角色:
新字段: ""
# 结果:角色 下现在有 姓名、生命值、新字段 三个允许的字段八、错误反馈
每次执行 <state> 后,系统会把错误/修正信息写入本地变量 LWB_STATE_ERRORS。
- 有问题:变量里有内容
- 没问题:变量被清空
在世界书里使用
{{getvar::LWB_STATE_ERRORS}}
如果上面有内容,说明你上回合的状态更新有问题,请注意修正。AI 会看到什么?
有问题时:
- 角色.心情: 字段不在结构模板中
- 角色.生命值: +100 超出范围,已限制到 100
- 角色.等级: +5 超出步长限制,已限制到 +1没问题时:(空的)
九、让 AI 看到当前状态
在世界书里用这个宏:
{{xbgetvar_yaml_idx::数据}}它会自动把变量展开成 YAML,并且给数组加上位置注释。
AI 看到的效果
姓名: "苏晚"
生命值: 100
技能:
- 火球术 # [0]
- 冰冻术 # [1]
背包:
- 名称: 回血药 # [0]
数量: 3有了 # [0] 这样的注释,AI 就知道要删第几个了。
十、初始化变量
完整初始化模板
在小白x的”循环任务”面板新建任务,触发时机选”角色卡初始化”:
<<taskjs>>
await LWB_StateV2.applyText(`
<state>
# ===== 1. 结构模板(只定义类型,不写入数据)=====
$schema 数据
角色:
姓名: ""
生命值: 0
魔法值: 0
金币: 0
技能: []
背包:
"*": 0
后宫:
"*":
生命值: 0
好感度: 0
当前地点: ""
# ===== 2. 约束规则 =====
$range=[0,999] 数据.角色.生命值
$range=[0,999] 数据.角色.魔法值
$step=50 数据.角色.生命值
$step=50 数据.角色.魔法值
# ===== 3. 初始数据(真正写入的值)=====
数据:
角色:
姓名: "苏晚"
生命值: 100
魔法值: 50
金币: 0
技能: []
背包: {}
当前地点: "新手村"
</state>
`, { silent: true });
<</taskjs>>说明:
<<taskjs>>...<</taskjs>>是小白x循环任务的 JS 语法{ silent: true }是配合总结系统初始化专用参数,照抄就行$schema只定义结构/类型,数据:段才是真正写入的初始值
十一、让 AI 在 RP 中使用变量系统
设计完变量结构后,还需要在世界书里告诉 AI 怎么用。
推荐的世界书配置
创建一个常驻条目,示例如下:
# 变量系统
## 当前状态
```yaml
{{xbgetvar_yaml_idx::数据}}
```
<varevent>
[event.showErrors]
condition: var(`LWB_STATE_ERRORS`) != val(``)
display: "
【上回合状态更新反馈】
{{getvar::LWB_STATE_ERRORS}}
⚠️ 上回合状态更新有误,请注意修正。
"
</varevent>
## 更新语法
每次回复末尾输出 `<state>` 块更新状态:
| 操作 | 写法 | 示例 |
|------|------|------|
| 设置 | `路径: 值` | `数据.角色.地点: "森林"` |
| 设负数 | `路径: (-N)` | `数据.温度: (-10)` |
| 加 | `路径: +N` | `数据.角色.金币: +100` |
| 减 | `路径: -N` | `数据.角色.生命值: -20` |
| 数组加 | `路径: +"项"` | `数据.技能: +"火球术"` |
| 数组删 | `路径[n]: null` | `数据.技能[0]: null` |
| 删属性 | `路径: null` | `数据.临时状态: null` |
注意:
- 冒号后必须有空格
- 删除数组元素时,索引参考上方状态里的 `# [n]` 注释
- 同一字段可多次操作,按顺序执行格式提醒(可选)
可以在低深度里加:
回复末尾务必输出状态更新:
<state>
# 本回合的状态变化
</state>约束提示(可选)
如果设置了 $range、$step 等规则,可以加一句让 AI 心里有数:
> 生命值范围 0-999,每次最多变化 50。超出会自动修正。完整示例
假设你的变量结构是:
数据:
角色:
姓名: "苏晚"
生命值: 100
金币: 50
技能:
- 火球术AI 回复末尾应该输出类似:
<state>
数据.角色.生命值: -15
数据.角色.金币: +30
数据.角色.技能: +"冰冻术"
</state>说明:
- 上面的varevent块代表有错误时,AI才看得到display部分,否则整个块对它隐藏
十二、边界细节
一条消息里可以有多个 <state> 吗?
可以,全部会执行。
文本里提到 <state> 这个词会误触发吗?
不会。系统会从每个 </state> 往前找最近的 <state>,配对成功才执行。
所以:
- ✅ 未闭合的
<state>会被忽略 - ✅ 被 HTML 注释包裹的也能执行
- ✅ 世界书里的
<state>不会执行(只有聊天消息里的才会)
十三、语法速查卡
<state>
# ===== 结构模板 =====
$schema 数据
角色:
姓名: "" # 字符串
生命值: 0 # 数字
技能: [] # 数组
背包:
"*": 0 # 物品名: 数量
后宫:
"*": # 任意人名
生命值: 0
好感度: 0
# ===== 约束规则(路径从根变量名开始)=====
$ro 数据.角色.姓名
$range=[0,100] 数据.角色.生命值
$step=20 数据.角色.生命值
$enum={晴;阴;雨} 数据.天气
# ===== 数据操作 =====
# 设置值
数据.角色.名字: "苏晚"
数据.角色.生命值: 100
# 设置负数(用括号)
数据.温度: (-10)
# 数字加减
数据.角色.生命值: -20 # 扣血
数据.角色.金币: +50 # 加钱
# 数组加东西
数据.角色.技能: +"火球术"
数据.角色.技能: +["冰冻", "雷电"]
数据.角色.背包: +{"名称": "药水", "数量": 1}
# 数组删东西
数据.角色.技能[0]: null
数据.角色.技能: -"火球术"
# 删除属性
数据.角色.临时状态: null
</state>十四、常见问题
Q:$schema 和约束规则有什么区别?
$schema:限制能有哪些字段(结构白名单)$ro/$range/$step/$enum:限制字段的值怎么改(值约束)
Q:规则可以组合用吗?
可以:
$range=[0,100] 数据.角色.生命值
$step=20 数据.角色.生命值执行顺序:先 step 限制变化量,再 range 限制最终值。
例如当前值 90,AI 写 +50:
+50超过 step=20 → 限制为+20- 90 + 20 = 110
- 110 超过 range max=100 → 限制为 100
最终值:100
Q:为什么我的 <state> 没生效?
检查:
- 是不是写在 AI 的回复里?(世界书里的不执行)
- 标签有没有配对?
<state>和</state>要成对 - 冒号后面有没有空格?
姓名:"苏晚"是错的,要写姓名: "苏晚"
Q:我不想限制结构,AI 随便写行不行?
可以。不写 $schema 就行,2.0 默认是自由的。
Q:规则注册后怎么删除?
目前没有删除规则的语法。如果需要重置,可以清空聊天或重新初始化角色卡。
Q:还是不懂?
把2.0教程全部复制给 AI 看,然后直接问:
查看所有,然后请为我的聊天变量系统生成初始化
<state>块,要求:
- 先写
$schema 数据结构模板(用""表示字符串、0表示数字、[]表示数组、{}表示自由对象)- 如果有”未来会出现的人名/物品名”,用
"*"通配- 再写
数据:初始值- 最后补上
$range、$step等约束规则(路径必须从根变量名开始,如数据.角色.生命值)我的需求:……(描述你的 RPG/背包/队友等)
附录:背包系统设计参考
这一节针对”背包”这个最常见的需求,列出几种设计方案供参考。
方式 1:纯名字列表
$schema 数据
背包: []
# 数据示例
背包:
- 回血药
- 蓝药
- 武器操作方式:
<state>
背包: +"新物品" # 加
背包[0]: null # 删第 0 个
背包: -"回血药" # 按名字删
</state>优点:简单。 缺点:没法记数量。
方式 2:对象数组(需要多个属性时推荐)
$schema 数据
背包:
- 名称: ""
数量: 0
# 数据示例
背包:
- 名称: 回血药 # [0]
数量: 3
- 名称: 蓝药 # [1]
数量: 5操作方式:
<state>
# 加物品
背包: +{"名称": "武器", "数量": 1}
# 改第 1 个的数量
背包[1].数量: +2
# 删第 0 个
背包[0]: null
</state>优点:每个物品可以有多个属性(名称、数量、描述等)。 缺点:操作时需要知道索引。
方式 3:物品名做 key(只需要数量时推荐)
$schema 数据
背包:
"*": 0
# 数据示例
背包:
回血药: 3
蓝药: 5
武器: 1操作方式:
<state>
# 加/改数量
背包.回血药: +1
背包.新武器: 1
# 删物品
背包.回血药: null
</state>优点:不用找索引,直接按名字改。 缺点:每个物品只能存一个值(数量)。
方式 4:物品名做 key,值是对象
$schema 数据
背包:
"*":
数量: 0
描述: ""
# 数据示例
背包:
回血药:
数量: 3
描述: "恢复 50 HP"
蓝药:
数量: 5
描述: "恢复 30 MP"操作方式:
<state>
背包.回血药.数量: +1
背包.新物品:
数量: 1
描述: "神秘的道具"
</state>优点:既能按名字访问,又能存多个属性。 缺点:结构稍复杂。
选择建议:
- 只需要记”有什么” → 方式 1
- 需要记”有几个” → 方式 3
- 需要记”有几个 + 其他属性” → 方式 2 或 4