fix: 存档功能修理

This commit is contained in:
2026-07-27 16:00:30 +08:00
parent 3e6fab9762
commit 31e6ae87be
7 changed files with 278 additions and 181 deletions
+3
View File
@@ -49,6 +49,8 @@ tags: content save_on_exit
- `interaction` / `save_on_exit` 进入时不存档;对应节点完成后若直接结束本轮 Dialogue,则自动保存 state-only 档。
- 若执行路径继续 `jump` / `detour` 到其他 Yarn 节点,退出档会取消并记录告警。
- run / flow / current-node 稳定性只约束 `DialogueExit`:任何后续 Yarn 节点(包括 `detour``no_save`)都会取消节点末尾存档。
- 普通 `NodeEnter` 和兼容命令 `<<save>>` 不受后续节点流转影响;它们始终使用请求触发时冻结的 node / YarnProject / TalkScene 生成 anchor,并继续进入串行写盘队列。
- 放在目标节点**末尾**:所有状态命令(`switch_fix_system_to``hide_dialog``play_timeline` 等)执行完毕之后,`<<jump>>` / `<<detour>>` 之前。
- `anchor.nodeName` 始终保留来源节点;`anchor.startDialogueOnRestore=false`,读档时只加载 YarnProject,不启动节点。
- 退出档在 `DialogEnd` 同步初始化后等待一帧捕获;这一帧由 scoped interaction lock 保持输入锁定,捕获进入写盘队列后再交出控制权。
@@ -153,6 +155,7 @@ Postflight 校验
- **核心层**`scene``anchor``yarnVariables`)由 `SnapshotRestore` 框架直管,不通过 Provider 注册。
- **表现层**`sections` 内各子系统)通过 `ISnapshotProvider` 扩展;`RestoreOrder` 表示同 Phase 内的建议顺序或软依赖。
- **逐项还原(D1)**指各子系统各自写回状态,**不是** Provider 之间逐步 `yield return`
- Postflight 只核对 Scene、TalkSceneSO、YarnProject 与必要 Provider readiness;不要求 RestartNode 仍停在 anchor,也不校验 StateOnly 结束后的 DialogueRunner 状态。
Provider 使用 `ISyncSnapshotProvider``IAsyncSnapshotProvider` 显式声明同步/异步恢复;异步 Provider 必须把等待过程返回给编排层,禁止内部 fire-and-forget。
@@ -422,18 +422,6 @@ namespace AibisDream.SaveSystem
return false;
}
if (dialog.DialogueRunId != request.DialogueRunId)
{
reason = $"dialogue run changed ({request.DialogueRunId} -> {dialog.DialogueRunId})";
return false;
}
if (dialog.DialogueFlowVersion != request.FlowRevision)
{
reason = $"flow revision changed ({request.FlowRevision} -> {dialog.DialogueFlowVersion})";
return false;
}
if (!string.Equals(runner.YarnProject?.name, request.YarnProjectId, StringComparison.Ordinal))
{
reason = $"YarnProject changed to {runner.YarnProject?.name ?? "(none)"}";
@@ -454,33 +442,51 @@ namespace AibisDream.SaveSystem
}
var (currentNodeName, _) = dialog.GetCurrentNodeContext();
if (request.Trigger == SaveTrigger.DialogueExit)
{
if (runner.IsDialogueRunning)
{
reason = "DialogueRunner is still running";
return false;
}
return ValidateDialogueTiming(
request,
dialog.DialogueRunId,
dialog.DialogueFlowVersion,
currentNodeName,
runner.IsDialogueRunning,
out reason);
}
if (!string.IsNullOrEmpty(currentNodeName))
{
reason = $"a current Yarn node is still present: {currentNodeName}";
return false;
}
internal static bool ValidateDialogueTiming(
SaveRequest request,
long currentDialogueRunId,
long currentFlowRevision,
string currentNodeName,
bool isDialogueRunning,
out string reason)
{
if (request.Trigger != SaveTrigger.DialogueExit)
{
reason = null;
return true;
}
else
{
if (!runner.IsDialogueRunning)
{
reason = "DialogueRunner stopped before capture";
return false;
}
if (!string.Equals(currentNodeName, request.SourceNodeName, StringComparison.Ordinal))
{
reason = $"current node changed to {currentNodeName ?? "(none)"}";
return false;
}
if (currentDialogueRunId != request.DialogueRunId)
{
reason = $"dialogue run changed ({request.DialogueRunId} -> {currentDialogueRunId})";
return false;
}
if (currentFlowRevision != request.FlowRevision)
{
reason = $"flow revision changed ({request.FlowRevision} -> {currentFlowRevision})";
return false;
}
if (isDialogueRunning)
{
reason = "DialogueRunner is still running";
return false;
}
if (!string.IsNullOrEmpty(currentNodeName))
{
reason = $"a current Yarn node is still present: {currentNodeName}";
return false;
}
reason = null;
@@ -775,23 +781,6 @@ namespace AibisDream.SaveSystem
var runner = DialogController.Instance?.DialogueRunner;
if (!string.Equals(runner?.YarnProject?.name, snapshot.anchor.yarnProjectId, StringComparison.Ordinal))
context.Error($"YarnProject mismatch: {runner?.YarnProject?.name ?? "none"}.");
if (snapshot.anchor.startDialogueOnRestore)
{
var (currentNodeName, _) = DialogController.Instance != null
? DialogController.Instance.GetCurrentNodeContext()
: (null, null);
if (runner == null || !runner.IsDialogueRunning)
context.Error($"Yarn node did not start: {snapshot.anchor.nodeName}.");
else if (!string.Equals(currentNodeName, snapshot.anchor.nodeName, StringComparison.Ordinal))
context.Error(
$"Yarn node mismatch: expected {snapshot.anchor.nodeName}, " +
$"actual {currentNodeName ?? "none"}.");
}
else if (runner != null && runner.IsDialogueRunning)
{
context.Error(
$"StateOnly restore unexpectedly started Yarn node {snapshot.anchor.nodeName}.");
}
if (snapshot.sections != null
&& snapshot.sections.ContainsKey(SnapshotProviderIds.Fix)