docs(save): 补充瞬跳节点与存档边界设计说明

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-18 14:35:03 +08:00
co-authored by Cursor
parent fbb9e65338
commit 4d8707cf4e
3 changed files with 120 additions and 42 deletions
+104 -30
View File
@@ -15,9 +15,9 @@
| 类型 | 标签 | 职责 | 跳转权限 | 内容限制 |
| ------------- | ------------- | ------------ | ----------------- | ------------------------------------- |
| `start` | `start` | 引擎入口 | 单出口 `<<jump>>` | 无叙事文本,仅做环境初始化与跳转 |
| `init` | `init` | 变量声明与初始状态 | 仅单出口 `<<jump>>` | 仅 `<<declare>>` / `<<set>>` / 全局参数初始化 |
| ------------- | ------------- | ------------ | ----------------- | ------------------------------------------------------ |
| `start` | `start` | 引擎入口 | 单出口 `<<jump>>` | 无叙事文本;负责最小环境初始化,并 `<<detour>>` 调用 `init` 后跳转到 `Center` |
| `init` | `init` | 变量声明与初始状态 | 出口 | 仅 `<<declare>>` / `<<set>>` / 全局参数初始化;本身不跳转 |
| `center` | `center` | 阶段调度中枢 | 可多出口 `<<jump>>` | 仅按 `$gameStage` 分发,无文本、无选项 |
| `content` | `content` | 阶段内容节点 | 通常单出口 | 可含叙事文本、局部选项、小游戏指令 |
| `performance` | `performance` | 原子化线性演出节点 | 单出口 | timeline、cutscene 等,禁止选项 |
@@ -30,6 +30,8 @@
- `deprecated`:已废弃节点,运行时不可达。
- `wip`:待编辑/占位节点,运行时不可达。
- `no_save`:该节点**不可作为保存边界**。即使节点类型通常允许保存(如 `content`),附加此标签也表示此处不应触发保存。
- **瞬跳节点**:见 [§8 瞬跳节点与存档边界](#8-瞬跳节点与存档边界)。所有瞬跳节点均不可作为保存边界。
---
@@ -64,15 +66,17 @@ tags: start
<<init_actor 地铁医生 左边>>
<<show_actor 地铁医生>>
<<jump VarsInit>>
<<detour VarsInit>>
<<jump Center>>
===
```
**约束清单:**
- 固定命名为 `Start`,标签 `start`
- 整个项目的开始阶段固定命名为 `Start`,标签 `start`
- 单个yarn脚本的开始阶段不能命名为`Start`,但标签为`start`
- 不写叙事文本、不声明变量、不含选项。
- 仅做最小必要的环境初始化,然后跳转到 `init`
- 仅做最小必要的环境初始化。
---
@@ -80,6 +84,8 @@ tags: start
集中声明维修流程所需变量,并设置初始全局参数。与梦境规范中的 `InitParams` 属于同一类型,只是节点名可按项目习惯选择 `VarsInit``数据初始化`
`init` 节点本身**不包含任何跳转出口**,由 `Start` 节点通过 `<<detour>>` 调用;执行完毕后控制流自动返回 `Start`,再由 `Start` 统一跳转到 `Center`
```yarn
title: VarsInit
tags: init
@@ -95,8 +101,6 @@ tags: init
// 小游戏相关状态
<<declare $salesTurnIndex=0>>
<<declare $knobFailCount=0>>
<<jump Center>>
===
```
@@ -104,7 +108,7 @@ tags: init
- 标签 `init`,固定命名为 `VarsInit`(或统一使用 `数据初始化`)。
- 仅包含 `<<declare>>``<<set>>``<<set_global_param>>` 等初始化指令。
- 禁止叙事文本、选项`<<jump>>` 到非 `center` 节点
- 禁止叙事文本、选项和任何 `<<jump>>`
- 同一 project 内,`init` 节点的命名必须一致。
---
@@ -140,6 +144,7 @@ tags: center
- 仅按 `$gameStage``if` 分发,不写叙事文本、不含选项。
- 维修终止条件统一在此处理(如 `$gameStage == 999` 时跳转到 `end`)。
- 是同一 project 内**唯一**应该读取 `$gameStage` 的节点。
- **瞬跳节点**`Center` 只做阶段分发,进入后立刻 `<<jump>>``StageN` 等目标,不向玩家停留;**不得作为存档边界**(tag 上已属禁止保存类型,编写时勿把可存逻辑写在此节点)。
---
@@ -181,6 +186,7 @@ hs: 医——生——! #line:0bade9e
- 阶段末尾通常做两件事:设置 `$gameStage`、跳回 `Center`
- 允许内部嵌套局部选项,但选项应服务于当前阶段内容,不做跨阶段跳转。
- 不应读取 `$gameStage` 做复杂分发(这是 `center` 的职责)。
- **瞬跳 content**:若节点内无台词、无选项、进入后仅做 `<<set>>` / 条件判断并立刻 `<<jump>>`(如 `UF检查状态`),属于瞬跳节点,须附加 `no_save`,不得依赖默认的 content 可存语义。详见 [§8](#8-瞬跳节点与存档边界)。
---
@@ -203,9 +209,9 @@ tags: performance
- 标签 `performance`
- **禁止分支选项**,必须单出口。
- **禁止叙事文本、角色台词与旁白**;
- 保存点只能出现在 `performance` 节点的**开始之前**或**结束之后**,节点内部不允许保存。
- 包含 timeline 播放、镜头运动、动画演出等线性指令。
- 不宜包含需要玩家选择的选项或小游戏交互。
- 包含 timeline 播放、镜头运动、动画演出等线性指令。
---
@@ -278,7 +284,7 @@ tags: function
### 3.8 end 节点
维修流程的收尾节点,负责结束演出并触发下一段 Yarn
维修流程的收尾节点,负责结束当前脚本
```yarn
title: End
@@ -297,9 +303,9 @@ me: 今天就到这里吧。 #line:xxxxxxxx
**约束清单:**
- 标签 `end`
- 标签 `end`,建议同时附加 `no_save`:维修流程已结束,控制权即将交还 C#,通常不应作为保存边界
- 可含少量结束演出与收尾对话。
- 最终必须调用 `<<NextYarn>>`,将控制权交还 C#。
- 可能跳回center,也可能进入<<NextYarn>>
- 不要在一个维修脚本里设置多个 `end` 节点,除非剧情明确需要分支结局。
---
@@ -307,10 +313,16 @@ me: 今天就到这里吧。 #line:xxxxxxxx
## 4. 控制流图
```
Start ──<<jump>>──> VarsInit / 数据初始化
Start ──<<detour>>──> VarsInit / 数据初始化
│ │
│ │(自动返回 Start)
│ ▼
│ Start
│ │
▼ │
Center ◀─────────────────────┘
Center ──<<jump>>──> Stage1content/performance 入口)
├──<<jump>>──> Stage1content/performance 入口)
│ │
│ ├── content 子节点 ── ...
│ │
@@ -339,31 +351,93 @@ Center ──<<jump>>──> Stage1content/performance 入口)
| 检查项 | start | init | center | content | performance | event | function | end |
| ------------------ | ----- | ----- | ------ | ------- | ----------- | ----- | -------- | ----------------- |
| 是否包含叙事文本? | 禁止 | 禁止 | 禁止 | 允许 | 允许(演出文本) | 禁止 | 禁止 | 允许(少量) |
| ------------------ | ----- | ---- | ------ | ------- | ------------------------ | ------------------- | -------- | ----------------- |
| 是否包含叙事文本? | 禁止 | 禁止 | 禁止 | 允许 | 禁止 | 禁止 | 禁止 | 允许(少量) |
| 是否包含选项? | 禁止 | 禁止 | 禁止 | 允许 | 禁止 | 禁止 | 禁止 | 禁止 |
| 是否有 `<<jump>>` | 仅一个出口 | 仅一个出口 | 可多出口 | 通常一个出口 | 单出口 | 可有 | 禁止 | 最终 `<<NextYarn>>` |
| 是否有 `<<jump>>` | 仅一个出口 | 禁止 | 可多出口 | 通常一个出口 | 单出口 | 可有 | 禁止 | 最终 `<<NextYarn>>` |
| 是否修改 `$gameStage`? | 禁止 | 可初始化 | 禁止 | 允许 | 禁止 | 尽量避免 | 禁止 | 禁止 |
| 是否被 C# 调用? | 否 | 否 | 否 | 否 | 否 | 是 | 否 | 否 |
| 是否可作为保存边界? | 否 | 否 | 否 | 否 | 是(仅开头/结尾) | 否 | 否 | 是 |
| 是否可作为保存边界? | 否 | 否 | 否(瞬跳) | 是(**瞬跳 content 除外**,须 `no_save` | 仅开头/结尾(节点本身建议 `no_save` | 否(可附加 `no_save` 强调) | 否 | 否(建议 `no_save` |
---
## 7. 废弃与占位节点
## 7. 附加标记
- 废弃或待编辑节点必须附加 `deprecated``wip` 标签
- 任何活跃节点不得 `<<jump>>``deprecated` / `wip` 节点
- 任何 C# 代码不得调用 `deprecated` / `wip` 节点
- 建议:`deprecated` 用于已废弃的旧节点,`wip` 用于尚未完成的占位节点。
- `deprecated`:已废弃节点,运行时不可达
- `wip`:待编辑/占位节点,运行时不可达
- `no_save`:该节点不可作为保存边界,运行时经过此处不应触发保存
### 7.1 `no_save` 使用场景
`no_save` 用于显式声明某节点处**不允许保存**,常用于以下情况:
- `end` 节点:维修流程已结束,控制权即将交还 C#,通常附加 `no_save`
- `performance` 节点:演出期间不能保存,建议在 `tags:` 中附加 `no_save`
- 关键 `event` 节点:事件路由节点通常不保存,可附加 `no_save` 强调。
- **瞬跳 `content` 节点**:无玩家可感知停留、进入后立即跳转的路由/状态检查节点(如 `UF检查状态`),必须附加 `no_save`。完整定义与排查清单见 [§8](#8-瞬跳节点与存档边界)。
- 特殊 `content` 节点:极少数剧情上明确不希望保存的 `content` 节点,可附加 `no_save` 覆盖默认可保存语义。
任何活跃节点不得 `<<jump>>``deprecated` / `wip` 节点,也不得在标记为 `no_save` 的节点处触发自动保存。
---
## 8. 设计原则
## 8. 瞬跳节点与存档边界
- `**start` 负责启动**:最小初始化 + 跳转到 `init`
- `**init` 负责配置**:所有变量声明集中管理。
- `**center` 负责调度**:打开 `center` 即可一览整个维修流程阶段
### 8.1 定义
**瞬跳节点**:进入后**不向玩家展示可感知内容**(无台词、无选项、无需要玩家推进的对话行),在同一轮 Yarn 执行中**很快**通过 `<<jump>>` 离开,或仅承担路由 / 变量写入 / 阶段切换的节点
典型特征(满足多数即可视为瞬跳):
- 节点体无角色台词、无 `->` 选项;
- 仅有 `<<if>>` + `<<set>>` + `<<jump>>`,或少量 `<<wait>>` 后立即跳转;
- 玩家不会在屏幕上「停」在这个节点完成一次交互。
### 8.2 与存档的关系
**所有瞬跳节点均不得作为存档边界。** 原因:
1. **语义**:存档点应对应玩家能感知的进度位置;瞬跳节点只是控制流中转,不是叙事/玩法上的「停点」。
2. **实现**:瞬跳节点常在同一帧内连跳(如 `content``end``Center``start`);若在瞬跳节点触发保存,锚点容易漂移或落到 `start` / `center` 等错误节点(参见 `UF检查状态``Stage3` 一类问题)。
### 8.3 按 tag 的默认策略
| 类型 | 是否瞬跳 | 是否可存 | 说明 |
| ---- | -------- | -------- | ---- |
| `start` | 是 | 否 | tag 已禁止 |
| `init` | 是 | 否 | tag 已禁止 |
| **`center`** | **是** | **否** | **阶段调度,仅分发 `<<jump>>`;编写与排查时明确视为瞬跳** |
| `function` | 是 | 否 | tag 已禁止 |
| `event` | 多为瞬跳 | 否 | tag 已禁止;路由型 event 一律不可存 |
| `end` | 多为瞬跳 | 否 | 建议 `end no_save` |
| `performance` | 否(演出中) | 否 | 节点本身不可存;边界在演出前后 |
| **`content`** | **视内容而定** | **仅非瞬跳 content 可存** | 有台词/选项/玩家停留 → 可存;纯路由 → **须 `no_save`** |
### 8.4 维修脚本中的常见瞬跳 content(待排查)
以下模式在现有脚本中已出现或容易出现,**应加 `no_save` 或改写为不可触发的保存路径**(全项目逐步排查,非一次性改完):
- `*检查状态``*状态`:条件满足后立刻 `<<jump>>` 到结束/下一阶段(如 `UF检查状态``视觉检查状态``$isFinished*==true` 分支);
- 阶段 `StageN` 入口若 tag 为 `start`:已禁止,勿改为 `content` 除非有真实停点;
- 任何「只写 `$gameStage` 并跳 `Center` / `结束*`」的短节点。
### 8.5 编写与审查 checklist
- [ ] 该节点玩家是否会看到对话或做出选择?若否 → 瞬跳 → **不可存**,加 `no_save`
- [ ] 是否为 `Center` 或等价调度节点?→ **不可存**
- [ ] 进入后是否在**同一轮控制流**内连跳多个节点?→ 链上所有节点都不应单独成为存档边界;可存点应落在链**之后**第一个玩家会停住的 `content` 上。
- [ ] 新加 `content` 节点时,默认「可存」;仅当确认有玩家停点时才省略 `no_save`
> **TODO(脚本排查)**:对 `Assets/Yarn/FP/` 下各 project 扫描瞬跳 `content` 节点,补 `no_save` 或调整跳转结构。优先级可参考存档验证工具中误报节点(如 Peipei `UF检查状态`)。
---
## 9. 设计原则
- `**start` 负责启动**:最小初始化 + `<<detour>>` 调用 `init` + 唯一 `<<jump>>``Center`
- `**init` 负责配置**:所有变量声明集中管理;无跳转出口,执行完毕后返回 `Start`
- `**center` 负责调度**:打开 `center` 即可一览整个维修流程阶段;**瞬跳、不可存**。
- `**content` 负责内容**:阶段之间的叙事、选项、小游戏交互隔离,便于单独调整。
- `**performance` 负责原子化演出**timeline、cutscene 等必须完整播放,保存边界清晰。
- `**event` 负责桥接**:把 C# 事件翻译成 Yarn 内部跳转,不掺杂叙事。
+4
View File
@@ -105,6 +105,8 @@
- **落点时机**:P3 可存点判定通过后(当前 P1:`SnapshotCapture` 在进入节点写档时捕获 `GetCurrentNodeContext()`)。
- **瞬跳节点(规范层,待脚本排查 + 实现补强)**:凡进入后不向玩家停留、仅作路由/阶段切换的节点,均不得作为存档边界。维修流程中 **`center` 明确属于瞬跳**`start` / `init` / `event` / `end` 等 tag 已在 `SavePointEvaluator` 黑名单。部分 **`content` 瞬跳节点**(如 `UF检查状态`)须 Yarn 侧标 `no_save`,详见 [Yarn 维修节点类型规范 §8](Yarn维修节点类型规范.md#8-瞬跳节点与存档边界)。另:若判定通过后在 settle 帧内 Yarn 已连跳,锚点 nodeName 可能漂移——属实现层已知问题,与瞬跳规范一并处理。
- **重入方式**:读档后 `SnapshotRestore.RestoreAnchor``StartDialogue(节点名)`
@@ -475,6 +477,8 @@ P7 横切
- 各深度维修是否支持阶段存档的逐个清单(P5)。
- **维修 Yarn 瞬跳 `content` 节点**:按 [Yarn 维修节点类型规范 §8.4](Yarn维修节点类型规范.md#84-维修脚本中的常见瞬跳-content待排查) 全项目补 `no_save`Peipei `UF检查状态` 等优先)。
- 全局 / 跨周目数据与单局存档的边界划分。
- 截图的具体规格(P2)。
+1 -1
View File
@@ -32,7 +32,7 @@
分情况讨论:
1. **诊所内维修**:仅在诊所中的对话和插线维修时保存;每个角色各自的深度维修过程中**不保存**。但深度维修可能会记录一些**阶段终点状态**,以处理"多段深度维修中间穿插诊所对话"的情况。
1. **诊所内维修**:仅在诊所中的对话和插线维修时保存;每个角色各自的深度维修过程中**不保存**。但深度维修可能会记录一些**阶段终点状态**,以处理"多段深度维修中间穿插诊所对话"的情况。维修 Yarn 的节点类型与**瞬跳节点**规则见 [Yarn 维修节点类型规范 §8](Yarn维修节点类型规范.md#8-瞬跳节点与存档边界)`center` 及所有瞬跳节点不可存;默认可存的 `content` 若仅为路由/状态检查,须标 `no_save`
2. **诊所外对话**(天桥、酒吧、诊室外等):每个节点都保存。
3. **梦境状态**:情况较复杂,此时 Yarn 结构遵循 [Yarn 节点类型规范](Yarn节点类型规范.md)。在这种情况下,`hub` 节点与 `linear` 节点保存,`detour` 节点与 `function` 节点不保存。