feat: 清除几个旧的混用测试存档功能

This commit is contained in:
2026-07-23 15:42:21 +08:00
parent cd2c7f6406
commit 28e4214413
127 changed files with 26 additions and 8158 deletions
-3
View File
@@ -78,9 +78,6 @@ crashlytics-build.properties
# Local Save Files
/[Aa]ssets/[Ss]treamingAssets/SaveFiles/*
# DevSaveFiles lives outside Assets; Unity metadata from the old location is not needed.
/DevSaveFiles/**/*.meta
# Never ignore DLLs in the FMOD subfolder.
!/[Aa]ssets/Plugins/FMOD/**/lib/*
@@ -15,6 +15,7 @@ namespace AibisDream.DeveloperMode.Editor
{
private const string PrefabPath = "Assets/GameContent/Feature_MainUI/Prefabs/DeveloperModePanel.prefab";
private const string ScenePath = "Assets/Scenes/Persistence.unity";
private const string FontAssetPath = "Assets/Font/Assets/WenQuanYi Bitmap Song 14px SDF.asset";
private static readonly Color Background = new(0.025f, 0.045f, 0.065f, 0.97f);
private static readonly Color Surface = new(0.055f, 0.085f, 0.11f, 0.96f);
private static readonly Color Accent = new(0.16f, 0.75f, 0.88f, 1f);
@@ -82,7 +83,7 @@ namespace AibisDream.DeveloperMode.Editor
var tabs = CreateHorizontal("Tabs", root.transform, 4f, new RectOffset(4, 4, 3, 3));
SetAnchors(tabs.GetComponent<RectTransform>(), new Vector2(0f, 1f), new Vector2(1f, 1f),
new Vector2(0f, -88f), new Vector2(0f, -46f));
var tabNames = new[] { "概览", "日志", "Yarn 变量", "存档跳转", "显示与控制" };
var tabNames = new[] { "概览", "日志", "Yarn 变量", "存档槽位", "显示与控制" };
var tabButtons = tabNames.Select(name => CreateButton($"Tab {name}", tabs.transform, name, 0f, 34f)).ToArray();
foreach (var button in tabButtons)
{
@@ -214,31 +215,17 @@ namespace AibisDream.DeveloperMode.Editor
var page = CreateVertical("Saves Page", parent, 6f, new RectOffset(8, 8, 8, 8));
var toolbar = CreateHorizontal("Toolbar", page.transform, 6f, new RectOffset(0, 0, 0, 0));
AddLayout(toolbar, 38f);
var search = CreateInput("Save Search", toolbar.transform, "搜索开发检查点...", 350f);
var reload = CreateButton("Reload Saves", toolbar.transform, "重新扫描", 110f, 34f);
var status = CreateText("Save Status", toolbar.transform, "Ready", 14f);
AddFlexible(status.gameObject);
status.alignment = TextAlignmentOptions.MidlineRight;
var columns = CreateHorizontal("Save Columns", page.transform, 8f, new RectOffset(0, 0, 0, 0));
var columnsLayout = columns.AddComponent<LayoutElement>();
columnsLayout.flexibleHeight = 1f;
var slotColumn = CreateVertical("Official Slots", columns.transform, 4f, new RectOffset(0, 0, 0, 0));
var slotColumn = CreateVertical("Official Slots", page.transform, 4f, new RectOffset(0, 0, 0, 0));
var slotLayout = slotColumn.AddComponent<LayoutElement>();
slotLayout.preferredWidth = 330f;
slotLayout.flexibleHeight = 1f;
var slotTitle = CreateText("Title", slotColumn.transform, "正式槽位(点击读档)", 16f, FontStyles.Bold);
AddLayout(slotTitle.gameObject, 28f);
CreateList("Slot List", slotColumn.transform, 430f, out var slotContent, out var slotTemplate);
var devColumn = CreateVertical("Dev Saves", columns.transform, 4f, new RectOffset(0, 0, 0, 0));
var devLayout = devColumn.AddComponent<LayoutElement>();
devLayout.flexibleWidth = 1f;
devLayout.flexibleHeight = 1f;
var devTitle = CreateText("Title", devColumn.transform, "开发检查点(严格恢复)", 16f, FontStyles.Bold);
AddLayout(devTitle.gameObject, 28f);
CreateList("Dev Save List", devColumn.transform, 430f, out var devContent, out var devTemplate);
CreateList("Slot List", slotColumn.transform, 500f, out var slotContent, out var slotTemplate);
var paths1 = CreateHorizontal("Paths 1", page.transform, 5f, new RectOffset(0, 0, 0, 0));
AddLayout(paths1, 34f);
@@ -246,26 +233,17 @@ namespace AibisDream.DeveloperMode.Editor
var copySave = CreateButton("Copy Saves", paths1.transform, "复制存档路径", 130f, 30f);
var openLog = CreateButton("Open Logs", paths1.transform, "打开日志目录", 130f, 30f);
var copyLog = CreateButton("Copy Logs", paths1.transform, "复制日志路径", 130f, 30f);
var paths2 = CreateHorizontal("Paths 2", page.transform, 5f, new RectOffset(0, 0, 0, 0));
AddLayout(paths2, 34f);
var openDev = CreateButton("Open Dev Saves", paths2.transform, "打开检查点目录", 140f, 30f);
var copyDev = CreateButton("Copy Dev Saves", paths2.transform, "复制检查点路径", 140f, 30f);
var openCurrent = CreateButton("Open Current Log", paths2.transform, "定位当前日志", 130f, 30f);
var copyCurrent = CreateButton("Copy Current Log", paths2.transform, "复制当前日志", 130f, 30f);
var openCurrent = CreateButton("Open Current Log", paths1.transform, "定位当前日志", 130f, 30f);
var copyCurrent = CreateButton("Copy Current Log", paths1.transform, "复制当前日志", 130f, 30f);
refs.Add("saveSearchInput", search);
refs.Add("reloadSavesButton", reload.GetComponent<Button>());
refs.Add("saveStatusText", status);
refs.Add("slotContent", slotContent);
refs.Add("slotRowTemplate", slotTemplate);
refs.Add("devSaveContent", devContent);
refs.Add("devSaveRowTemplate", devTemplate);
refs.Add("openSaveFolderButton", openSave.GetComponent<Button>());
refs.Add("copySaveFolderButton", copySave.GetComponent<Button>());
refs.Add("openLogFolderButton", openLog.GetComponent<Button>());
refs.Add("copyLogFolderButton", copyLog.GetComponent<Button>());
refs.Add("openDevSaveFolderButton", openDev.GetComponent<Button>());
refs.Add("copyDevSaveFolderButton", copyDev.GetComponent<Button>());
refs.Add("openCurrentLogButton", openCurrent.GetComponent<Button>());
refs.Add("copyCurrentLogButton", copyCurrent.GetComponent<Button>());
return page;
@@ -407,7 +385,7 @@ namespace AibisDream.DeveloperMode.Editor
var go = CreateUiObject(name, typeof(CanvasRenderer), typeof(TextMeshProUGUI));
go.transform.SetParent(parent, false);
var text = go.GetComponent<TextMeshProUGUI>();
text.font = TMP_Settings.defaultFontAsset;
text.font = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(FontAssetPath);
text.text = value;
text.fontSize = size;
text.fontStyle = style;
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 8d4c5a2b82ac1ca459e228aaad3255c6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,536 +0,0 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using AibisDream.SaveSystem;
using AibisDream.Utility;
using Newtonsoft.Json;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using UnityEngine.TestTools;
namespace AibisDream.EditorTools
{
/// <summary>从本机 testsavs 选择章节、去重排序并提升为可提交测试档。</summary>
public sealed class DevSavePromoteWindow : EditorWindow
{
private readonly List<ArchiveGroup> groups = new();
private readonly List<Candidate> preview = new();
private int selectedGroup;
private string sectionId = string.Empty;
private string sectionTitle = string.Empty;
private string status = string.Empty;
private Vector2 scroll;
[MenuItem("Tools/Aibis/Dev Save Promote")]
public static void Open()
{
var window = GetWindow<DevSavePromoteWindow>("Dev Save Promote");
window.minSize = new Vector2(720f, 520f);
window.Show();
}
private void OnEnable() => RefreshGroups();
private void OnGUI()
{
EditorGUILayout.LabelField("测试存档提升与校验", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"来源是本机 testsavs;目标是项目根目录的 DevSaveFiles。该目录可提交,但不会被 Unity 自动打包。",
MessageType.Info);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("刷新本机归档", GUILayout.Height(26f))) RefreshGroups();
if (GUILayout.Button("校验全部已提交测试档", GUILayout.Height(26f))) ValidateCommittedCatalog();
}
if (groups.Count == 0)
{
EditorGUILayout.HelpBox("本机没有可用 testsavs。", MessageType.Warning);
DrawStatus();
return;
}
var labels = groups.Select(group =>
$"{group.SceneSoName} | {group.YarnProjectId} | {group.SceneName} ({group.Items.Count})").ToArray();
var nextGroup = EditorGUILayout.Popup("章节归档", Mathf.Clamp(selectedGroup, 0, labels.Length - 1), labels);
if (nextGroup != selectedGroup || preview.Count == 0)
{
selectedGroup = nextGroup;
SelectGroup(groups[selectedGroup]);
}
sectionId = EditorGUILayout.TextField("Section Id", sectionId);
sectionTitle = EditorGUILayout.TextField("显示名称", sectionTitle);
EditorGUILayout.LabelField("规则", "每个 Yarn 节点保留 savedAt 最新一份;默认按 savedAt 升序");
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
if (GUILayout.Button("提升当前章节", GUILayout.Width(180f), GUILayout.Height(28f))) Promote();
}
EditorGUILayout.Space(5f);
EditorGUILayout.LabelField($"阶段预览:{preview.Count}", EditorStyles.boldLabel);
scroll = EditorGUILayout.BeginScrollView(scroll);
for (var i = 0; i < preview.Count; i++)
{
var item = preview[i];
using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox))
{
EditorGUILayout.LabelField($"{i + 1:000}", GUILayout.Width(38f));
item.Label = EditorGUILayout.TextField(item.Label, GUILayout.Width(210f));
EditorGUILayout.LabelField(item.NodeName, GUILayout.MinWidth(180f));
EditorGUILayout.LabelField(item.SavedAt, GUILayout.Width(140f));
using (new EditorGUI.DisabledScope(i == 0))
{
if (GUILayout.Button("↑", GUILayout.Width(26f))) Move(i, i - 1);
}
using (new EditorGUI.DisabledScope(i == preview.Count - 1))
{
if (GUILayout.Button("↓", GUILayout.Width(26f))) Move(i, i + 1);
}
}
}
EditorGUILayout.EndScrollView();
DrawStatus();
}
private void DrawStatus()
{
if (!string.IsNullOrWhiteSpace(status))
EditorGUILayout.HelpBox(status, status.StartsWith("OK", StringComparison.Ordinal) ? MessageType.Info : MessageType.Warning);
}
private void RefreshGroups()
{
groups.Clear();
preview.Clear();
var root = ConstRef.TestAutoSaveArchivePath;
if (!Directory.Exists(root))
{
status = $"找不到本机归档:{root}";
return;
}
var candidates = new List<Candidate>();
foreach (var directory in Directory.GetDirectories(root))
{
var snapshotPath = Path.Combine(directory, $"{ConstRef.SaveSnapshotFileName}.json");
if (!File.Exists(snapshotPath)) continue;
try
{
var snapshot = SnapshotPersistence.Load(Path.Combine(directory, ConstRef.SaveSnapshotFileName));
if (snapshot?.anchor == null || string.IsNullOrWhiteSpace(snapshot.anchor.nodeName)) continue;
candidates.Add(new Candidate(
snapshotPath,
Path.Combine(directory, $"{ConstRef.SaveMetaFileName}.json"),
snapshot.scene?.sceneName,
snapshot.anchor.sceneSoName,
snapshot.anchor.yarnProjectId,
snapshot.anchor.nodeName,
snapshot.savedAt));
}
catch (Exception ex)
{
Debug.LogWarning($"[DevSavePromote] 跳过损坏归档 {snapshotPath}: {ex.Message}");
}
}
groups.AddRange(candidates
.GroupBy(item => $"{item.SceneName}\n{item.SceneSoName}\n{item.YarnProjectId}", StringComparer.Ordinal)
.Select(group => new ArchiveGroup(group.ToList()))
.OrderBy(group => group.SceneSoName, StringComparer.Ordinal));
selectedGroup = Mathf.Clamp(selectedGroup, 0, Math.Max(0, groups.Count - 1));
if (groups.Count > 0) SelectGroup(groups[selectedGroup]);
status = $"OK:读取 {candidates.Count} 份归档,分为 {groups.Count} 个章节。";
}
private void SelectGroup(ArchiveGroup group)
{
preview.Clear();
preview.AddRange(group.Items
.GroupBy(item => item.NodeName, StringComparer.Ordinal)
.Select(nodes => nodes.OrderByDescending(item => item.SavedAt, StringComparer.Ordinal).First())
.OrderBy(item => item.SavedAt, StringComparer.Ordinal));
sectionId = SanitizeIdentifier(group.SceneSoName);
sectionTitle = group.SceneSoName;
}
private void Move(int from, int to)
{
if (from < 0 || to < 0 || from >= preview.Count || to >= preview.Count) return;
var item = preview[from];
preview.RemoveAt(from);
preview.Insert(to, item);
GUI.FocusControl(null);
}
private void Promote()
{
if (preview.Count == 0 || string.IsNullOrWhiteSpace(sectionId))
{
status = "没有可提升阶段,或 Section Id 为空。";
return;
}
var validation = ValidateCandidates(preview);
if (validation.Count > 0)
{
status = string.Join("\n", validation);
return;
}
var group = groups[selectedGroup];
var root = ConstRef.TestSaveFilePath;
var targetSection = Path.Combine(root, sectionId);
if (Directory.Exists(targetSection)) Directory.Delete(targetSection, true);
Directory.CreateDirectory(targetSection);
var catalog = LoadCatalogForWrite();
catalog.sections.RemoveAll(section => string.Equals(section.id, sectionId, StringComparison.OrdinalIgnoreCase));
var sectionDto = new DevSaveCatalogSectionDto
{
order = FindDemoChapterOrder(group.SceneSoName),
id = sectionId,
title = string.IsNullOrWhiteSpace(sectionTitle) ? sectionId : sectionTitle,
expectedSceneName = group.SceneName,
expectedSceneSoName = group.SceneSoName,
expectedYarnProjectId = group.YarnProjectId
};
for (var i = 0; i < preview.Count; i++)
{
var item = preview[i];
var folder = $"{i + 1:000}_{SanitizeIdentifier(item.NodeName)}";
var destination = Path.Combine(targetSection, folder);
Directory.CreateDirectory(destination);
File.Copy(item.SnapshotPath, Path.Combine(destination, $"{ConstRef.SaveSnapshotFileName}.json"), true);
if (File.Exists(item.MetaPath))
File.Copy(item.MetaPath, Path.Combine(destination, $"{ConstRef.SaveMetaFileName}.json"), true);
sectionDto.entries.Add(new DevSaveCatalogEntryDto
{
order = i + 1,
label = string.IsNullOrWhiteSpace(item.Label) ? item.NodeName : item.Label,
path = $"{sectionId}/{folder}",
anchorNode = item.NodeName
});
}
catalog.sections.Add(sectionDto);
catalog.sections.Sort((left, right) => left.order.CompareTo(right.order));
WriteCatalog(catalog);
AssetDatabase.Refresh();
ValidateCommittedCatalog();
}
private static List<string> ValidateCandidates(IEnumerable<Candidate> candidates)
{
var errors = new List<string>();
var nodes = new HashSet<string>(StringComparer.Ordinal);
foreach (var item in candidates)
{
if (!nodes.Add(item.NodeName)) errors.Add($"重复节点:{item.NodeName}");
try
{
var snapshot = SnapshotPersistence.Load(Path.Combine(Path.GetDirectoryName(item.SnapshotPath)!, ConstRef.SaveSnapshotFileName));
if (snapshot.schemaVersion != SaveSnapshotSchema.CurrentVersion)
errors.Add($"{item.NodeName}: schema {snapshot.schemaVersion} 不兼容");
}
catch (Exception ex)
{
errors.Add($"{item.NodeName}: {ex.Message}");
}
}
return errors;
}
private void ValidateCommittedCatalog()
{
var sections = DevSaveCatalog.Load(ConstRef.TestSaveFilePath);
var errors = new List<string>();
if (!string.IsNullOrWhiteSpace(DevSaveCatalog.LastError)) errors.Add(DevSaveCatalog.LastError);
foreach (var section in sections)
{
var orders = new HashSet<int>();
var nodes = new HashSet<string>(StringComparer.Ordinal);
foreach (var entry in section.Entries)
{
if (!orders.Add(entry.Order)) errors.Add($"{section.Id}: 重复 order {entry.Order}");
if (!nodes.Add(entry.AnchorNode)) errors.Add($"{section.Id}: 重复节点 {entry.AnchorNode}");
if (!entry.IsValid) errors.Add($"{section.Id}/{entry.Label}: {entry.Error}");
}
}
var dto = LoadCatalogForWrite();
foreach (var section in dto.sections)
{
if (!SceneAddressExists(section.expectedSceneName))
errors.Add($"{section.id}: Addressable 场景不存在 {section.expectedSceneName}");
var sceneSo = FindTalkSceneSo(section.expectedSceneSoName);
if (sceneSo == null)
errors.Add($"{section.id}: TalkSceneSO 不存在 {section.expectedSceneSoName}");
else if (sceneSo.yarnProject == null || sceneSo.yarnProject.name != section.expectedYarnProjectId)
errors.Add($"{section.id}: TalkSceneSO 的 YarnProject 不匹配 {section.expectedYarnProjectId}");
else
{
var nodeNames = new HashSet<string>(sceneSo.yarnProject.NodeNames, StringComparer.Ordinal);
foreach (var entry in section.entries.Where(entry => !nodeNames.Contains(entry.anchorNode)))
errors.Add($"{section.id}: Yarn 节点不存在 {entry.anchorNode}");
}
}
status = errors.Count == 0
? $"OK{sections.Count} 个章节、{sections.Sum(s => s.Entries.Count)} 个阶段全部通过校验。"
: $"校验失败({errors.Count}):\n{string.Join("\n", errors)}";
if (errors.Count > 0) Debug.LogError($"[DevSavePromote] {status}");
else Debug.Log($"[DevSavePromote] {status}");
}
private static bool SceneAddressExists(string address)
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
return settings != null && settings.groups
.Where(group => group != null)
.SelectMany(group => group.entries)
.Any(entry => string.Equals(entry.address, address, StringComparison.Ordinal));
}
private static TalkSceneSO FindTalkSceneSo(string assetName)
{
foreach (var guid in AssetDatabase.FindAssets($"t:TalkSceneSO {assetName}"))
{
var asset = AssetDatabase.LoadAssetAtPath<TalkSceneSO>(AssetDatabase.GUIDToAssetPath(guid));
if (asset != null && asset.name == assetName) return asset;
}
return null;
}
private static int FindDemoChapterOrder(string sceneSoName)
{
const string firstChapterPath = "Assets/ScriptableObjects/SceneSO/Demo/Day0_Prologue.asset";
var chapter = AssetDatabase.LoadAssetAtPath<TalkSceneSO>(firstChapterPath);
var visited = new HashSet<TalkSceneSO>();
var order = 1;
while (chapter != null && visited.Add(chapter))
{
if (string.Equals(chapter.name, sceneSoName, StringComparison.Ordinal)) return order;
chapter = chapter.GetNextScene();
order++;
}
return int.MaxValue;
}
private static DevSaveCatalogDto LoadCatalogForWrite()
{
var path = Path.Combine(ConstRef.TestSaveFilePath, "catalog.json");
if (!File.Exists(path)) return new DevSaveCatalogDto();
try
{
return JsonConvert.DeserializeObject<DevSaveCatalogDto>(File.ReadAllText(path, Encoding.UTF8))
?? new DevSaveCatalogDto();
}
catch
{
return new DevSaveCatalogDto();
}
}
private static void WriteCatalog(DevSaveCatalogDto catalog)
{
Directory.CreateDirectory(ConstRef.TestSaveFilePath);
File.WriteAllText(
Path.Combine(ConstRef.TestSaveFilePath, "catalog.json"),
JsonConvert.SerializeObject(catalog, Formatting.Indented),
new UTF8Encoding(false));
}
private static string SanitizeIdentifier(string value)
{
if (string.IsNullOrWhiteSpace(value)) return "unnamed";
foreach (var character in Path.GetInvalidFileNameChars()) value = value.Replace(character, '_');
value = value.Replace(' ', '_');
return value.Length <= 48 ? value : value.Substring(0, 48);
}
private sealed class ArchiveGroup
{
public readonly List<Candidate> Items;
public string SceneName => Items[0].SceneName;
public string SceneSoName => Items[0].SceneSoName;
public string YarnProjectId => Items[0].YarnProjectId;
public ArchiveGroup(List<Candidate> items) => Items = items;
}
private sealed class Candidate
{
public readonly string SnapshotPath;
public readonly string MetaPath;
public readonly string SceneName;
public readonly string SceneSoName;
public readonly string YarnProjectId;
public readonly string NodeName;
public readonly string SavedAt;
public string Label;
public Candidate(string snapshotPath, string metaPath, string sceneName, string sceneSoName,
string yarnProjectId, string nodeName, string savedAt)
{
SnapshotPath = snapshotPath;
MetaPath = metaPath;
SceneName = sceneName;
SceneSoName = sceneSoName;
YarnProjectId = yarnProjectId;
NodeName = nodeName;
SavedAt = savedAt ?? string.Empty;
Label = nodeName;
}
}
}
[TestFixture]
public sealed class DevSaveCatalogTests
{
private string root;
[SetUp]
public void SetUp()
{
root = Path.Combine(Path.GetTempPath(), $"aibis-dev-save-{Guid.NewGuid():N}");
Directory.CreateDirectory(root);
}
[TearDown]
public void TearDown()
{
if (Directory.Exists(root)) Directory.Delete(root, true);
}
[Test]
public void CatalogUsesExplicitOrderAndKeepsInvalidEntriesVisible()
{
WriteCatalog(new DevSaveCatalogEntryDto { order = 20, label = "late", path = "S/late", anchorNode = "Late" },
new DevSaveCatalogEntryDto { order = 10, label = "early", path = "S/early", anchorNode = "Early" });
var section = DevSaveCatalog.Load(root).Single();
Assert.That(section.Entries.Select(entry => entry.Label), Is.EqualTo(new[] { "early", "late" }));
Assert.That(section.Entries.All(entry => !entry.IsValid), Is.True);
Assert.That(section.Entries.All(entry => entry.Error.Contains("缺失")), Is.True);
}
[Test]
public void CatalogRejectsPathTraversal()
{
WriteCatalog(new DevSaveCatalogEntryDto { order = 1, label = "unsafe", path = "../outside", anchorNode = "Start" });
var entry = DevSaveCatalog.Load(root).Single().Entries.Single();
Assert.That(entry.IsValid, Is.False);
Assert.That(entry.Error, Does.Contain("越过"));
}
[Test]
public void CatalogReportsCorruptSchemaAndAnchorMismatch()
{
WriteSnapshot("S/corrupt", "{");
WriteSnapshot("S/schema", JsonConvert.SerializeObject(BuildSnapshot("Node", schema: 99)));
WriteSnapshot("S/node", JsonConvert.SerializeObject(BuildSnapshot("Actual")));
WriteCatalog(
new DevSaveCatalogEntryDto { order = 1, label = "corrupt", path = "S/corrupt", anchorNode = "Node" },
new DevSaveCatalogEntryDto { order = 2, label = "schema", path = "S/schema", anchorNode = "Node" },
new DevSaveCatalogEntryDto { order = 3, label = "node", path = "S/node", anchorNode = "Expected" });
var entries = DevSaveCatalog.Load(root).Single().Entries;
Assert.That(entries[0].Error, Does.Contain("JSON"));
Assert.That(entries[1].Error, Does.Contain("版本"));
Assert.That(entries[2].Error, Does.Contain("节点不匹配"));
}
[Test]
public void CommittedCatalogContainsAllDemoChaptersWithValidUniqueEntries()
{
var sections = DevSaveCatalog.Load(ConstRef.TestSaveFilePath);
Assert.That(DevSaveCatalog.LastError, Is.Null);
Assert.That(sections.Select(section => section.Id), Is.EqualTo(new[]
{
"Day0Prologue", "Day1Begin", "Peipei1", "Day1Mid", "Day1Night", "Day1Sleep",
"Day2Begin", "Huoshan1", "Day2Mid", "Peipei2", "Day2Night", "Day2Sleep", "GeneralEnd"
}));
foreach (var section in sections)
{
Assert.That(section.Entries.All(entry => entry.IsValid), Is.True,
string.Join("\n", section.Entries.Where(entry => !entry.IsValid).Select(entry => $"{entry.Label}: {entry.Error}")));
Assert.That(section.Entries.Select(entry => entry.Order).Distinct().Count(), Is.EqualTo(section.Entries.Count));
Assert.That(section.Entries.Select(entry => entry.AnchorNode).Distinct().Count(), Is.EqualTo(section.Entries.Count));
}
}
[Test]
public void StrictRestoreContextTracksPhaseWarningsAndErrors()
{
var context = new SnapshotRestoreContext(new SaveSnapshot(), strictMode: true);
context.SetPhase("Provider restore");
context.Warn("optional state missing");
LogAssert.Expect(LogType.Error, "[SnapshotRestore] required state missing");
context.Error("required state missing");
Assert.That(context.StrictMode, Is.True);
Assert.That(context.CurrentPhase, Is.EqualTo("Provider restore"));
Assert.That(context.Warnings, Is.EqualTo(new[] { "optional state missing" }));
Assert.That(context.Errors, Is.EqualTo(new[] { "required state missing" }));
Assert.That(context.HasErrors, Is.True);
}
private void WriteCatalog(params DevSaveCatalogEntryDto[] entries)
{
var catalog = new DevSaveCatalogDto
{
sections = new List<DevSaveCatalogSectionDto>
{
new()
{
id = "S",
title = "Section",
expectedSceneName = "Scene/Test",
expectedSceneSoName = "TestSO",
expectedYarnProjectId = "TestYarn",
entries = entries.ToList()
}
}
};
File.WriteAllText(Path.Combine(root, "catalog.json"), JsonConvert.SerializeObject(catalog));
}
private void WriteSnapshot(string relativeDirectory, string json)
{
var directory = Path.Combine(root, relativeDirectory.Replace('/', Path.DirectorySeparatorChar));
Directory.CreateDirectory(directory);
File.WriteAllText(Path.Combine(directory, "snapshot.json"), json);
}
private static SaveSnapshot BuildSnapshot(string node, int schema = SaveSnapshotSchema.CurrentVersion)
{
return new SaveSnapshot
{
schemaVersion = schema,
scene = new SceneSnapshotDto { sceneName = "Scene/Test" },
anchor = new AnchorSnapshot
{
sceneSoName = "TestSO",
yarnProjectId = "TestYarn",
nodeName = node
}
};
}
}
}
#endif
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: b564a87c642e53646baa39708c8f72ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,669 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using AibisDream;
using AibisDream.SaveSystem;
using AibisDream.Utility;
using UnityEditor;
using UnityEngine;
namespace AibisDream.SaveSystem.Editor
{
/// <summary>
/// 存读档临时测试工具:触发存档、从槽位/测试归档读档、浏览 testsavs 历史。
/// </summary>
public class SaveSystemTestWindow : EditorWindow
{
private const string MenuPath = "AIBIS/存档测试工具";
private const string WindowTitle = "SaveSystem Test";
private Vector2 _scrollPosition;
private string _lastLog = string.Empty;
private int _restoreSlotIndex;
private int _manualSlotIndex = 1;
private bool _testArchiveEnabled;
private string _selectedArchiveEntryId;
private Texture2D _selectedThumbnail;
private List<TestSaveArchiveEntry> _archiveEntries = new();
private List<TestSaveArchiveEntry> _archiveEntriesSnapshot = new();
private double _lastArchiveRefreshTime;
private bool _repaintQueued;
private List<string> _slotOverviewLabels = new();
private List<string> _restoreLogSnapshot = new();
private string _displaySelectedArchiveEntryId;
private Texture2D _displayThumbnail;
private void RequestRepaint()
{
if (_repaintQueued)
{
return;
}
_repaintQueued = true;
EditorApplication.delayCall += () =>
{
_repaintQueued = false;
if (this != null)
{
Repaint();
}
};
}
[MenuItem(MenuPath)]
public static void Open()
{
var window = GetWindow<SaveSystemTestWindow>(WindowTitle);
window.minSize = new Vector2(480, 560);
}
private void OnEnable()
{
_testArchiveEnabled = EditorPrefs.GetBool(TestSaveArchive.EditorPrefsEnabledKey, true);
RefreshArchiveEntries(requestRepaint: false);
EditorApplication.update += OnEditorUpdate;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
private void OnDisable()
{
EditorApplication.update -= OnEditorUpdate;
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
ReleaseThumbnail();
}
private void OnEditorUpdate()
{
if (!Application.isPlaying)
{
return;
}
if (EditorApplication.timeSinceStartup - _lastArchiveRefreshTime < 1.0)
{
return;
}
RefreshArchiveEntries(requestRepaint: true);
}
private void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode || state == PlayModeStateChange.ExitingPlayMode)
{
RefreshArchiveEntries();
}
}
private void OnGUI()
{
if (Event.current.type == EventType.Layout)
{
SnapshotDynamicGuiData();
}
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
DrawHeader();
GUILayout.Space(8);
DrawTestArchiveSettings();
GUILayout.Space(8);
DrawSaveActions();
GUILayout.Space(8);
DrawSlotRestoreSection();
GUILayout.Space(8);
DrawTestArchiveSection();
GUILayout.Space(8);
DrawRestoreLogSection();
GUILayout.Space(8);
DrawUtilitySection();
GUILayout.Space(8);
DrawLogSection();
EditorGUILayout.EndScrollView();
}
/// <summary>
/// 在 Layout/Repaint 之前快照可变列表,避免同一 GUI 帧内控件数量不一致。
/// </summary>
private void SnapshotDynamicGuiData()
{
_displaySelectedArchiveEntryId = _selectedArchiveEntryId;
_displayThumbnail = _selectedThumbnail;
_archiveEntriesSnapshot.Clear();
for (var i = 0; i < _archiveEntries.Count; i++)
{
_archiveEntriesSnapshot.Add(_archiveEntries[i]);
}
_slotOverviewLabels.Clear();
foreach (var vm in SlotManager.GetSlotViewModels())
{
if (vm.IsEmpty)
{
continue;
}
var meta = SlotManager.LoadMeta(vm.SlotIndex);
_slotOverviewLabels.Add(
$"slot_{vm.SlotIndex}" +
(vm.IsAutoSlot ? " [自动]" : "") +
$" | {vm.SavedAt} | {meta?.nodeName ?? ""} | {vm.SceneName}");
}
_restoreLogSnapshot.Clear();
var restoreLog = SaveRestoreOrchestrator.LastRestoreLog;
for (var i = 0; i < restoreLog.Count; i++)
{
_restoreLogSnapshot.Add(restoreLog[i]);
}
}
private void DrawHeader()
{
EditorGUILayout.LabelField("存读档测试工具", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"用于 Play Mode 下验证自动存档、槽位读档与 testsavs 测试归档。\n" +
"正式存档目录: demo_saves/ | 测试归档: testsavs/(每次自动存档独立子文件夹)",
MessageType.Info);
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox("存档/读档操作需要在 Play Mode 下执行。", MessageType.Warning);
}
EditorGUILayout.LabelField(
$"状态: IsRestoring={SaveRestoreOrchestrator.IsRestoring}, " +
$"SuppressAutoSave={SaveRestoreOrchestrator.IsAutoSaveSuppressed}");
}
private void DrawTestArchiveSettings()
{
EditorGUILayout.LabelField("测试存档模式", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
_testArchiveEnabled = EditorGUILayout.Toggle("启用测试存档模式", _testArchiveEnabled);
if (EditorGUI.EndChangeCheck())
{
EditorPrefs.SetBool(TestSaveArchive.EditorPrefsEnabledKey, _testArchiveEnabled);
}
EditorGUILayout.LabelField($"testsavs 路径: {ConstRef.TestAutoSaveArchivePath}", EditorStyles.miniLabel);
EditorGUILayout.HelpBox(
"开启后:每次 AutoSave 仅在 testsavs 下新建一份独立存档,不写入、不覆盖 demo_saves/slot_0。\n" +
"存多少次就保留多少份;关闭后恢复正式行为(仅覆盖 slot_0)。",
MessageType.None);
}
private void DrawSaveActions()
{
EditorGUILayout.LabelField("存档", EditorStyles.boldLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("触发自动存档 (完整流程)"))
{
TriggerAutoSave();
}
if (GUILayout.Button("仅 Capture (不写盘)"))
{
CaptureInMemory();
}
}
if (GUILayout.Button("手动档: 复制 slot_0 → 上方指定槽位"))
{
CopyAutoToManual();
}
}
private void DrawSlotRestoreSection()
{
EditorGUILayout.LabelField("从正式槽位读档", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"demo_saves 路径: {ConstRef.SaveFilePath}", EditorStyles.miniLabel);
var latest = SlotManager.GetLatestSlotIndex();
EditorGUILayout.LabelField($"最近槽位: {(latest.HasValue ? $"slot_{latest.Value}" : "")}");
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("读档: Latest"))
{
RestoreLatestSlot();
}
if (GUILayout.Button("读档: slot_0"))
{
RestoreSlot(SlotIndex.Auto);
}
}
_restoreSlotIndex = EditorGUILayout.IntSlider("槽位", _restoreSlotIndex, 0, SlotIndex.ManualEnd);
if (GUILayout.Button($"读档: slot_{_restoreSlotIndex}"))
{
RestoreSlot(_restoreSlotIndex);
}
GUILayout.Space(4);
_manualSlotIndex = EditorGUILayout.IntSlider(
"复制到手动槽位",
_manualSlotIndex,
SlotIndex.ManualStart,
SlotIndex.ManualEnd);
DrawSlotBriefOverview();
}
private void DrawSlotBriefOverview()
{
foreach (var label in _slotOverviewLabels)
{
EditorGUILayout.LabelField(label, EditorStyles.miniLabel);
}
}
private void DrawTestArchiveSection()
{
EditorGUILayout.LabelField("testsavs 测试归档", EditorStyles.boldLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("刷新列表"))
{
RefreshArchiveEntries();
}
if (GUILayout.Button("清空 testsavs"))
{
ClearTestArchives();
}
if (GUILayout.Button("打开 testsavs 文件夹"))
{
OpenDirectory(ConstRef.TestAutoSaveArchivePath);
}
}
EditorGUILayout.LabelField($"共 {_archiveEntriesSnapshot.Count} 条", EditorStyles.miniLabel);
if (_archiveEntriesSnapshot.Count == 0)
{
EditorGUILayout.HelpBox(
"暂无测试归档。开启「testsavs 独立归档」后触发自动存档,或从正式 slot_0 手动归档。",
MessageType.None);
}
else
{
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
foreach (var entry in _archiveEntriesSnapshot)
{
DrawArchiveEntry(entry);
}
}
}
if (GUILayout.Button("将当前 slot_0 手动归档到 testsavs"))
{
ArchiveCurrentAutoSlot();
}
}
private void DrawArchiveEntry(TestSaveArchiveEntry entry)
{
var isSelected = _displaySelectedArchiveEntryId == entry.EntryId;
var boxStyle = isSelected ? EditorStyles.helpBox : EditorStyles.textArea;
using (new EditorGUILayout.VerticalScope(boxStyle))
{
EditorGUILayout.LabelField(entry.EntryId, EditorStyles.boldLabel);
EditorGUILayout.LabelField(
$"时间: {entry.Meta?.savedAt ?? ""} | 节点: {entry.Meta?.nodeName ?? ""}",
EditorStyles.miniLabel);
EditorGUILayout.LabelField(
$"场景: {entry.Meta?.sceneName ?? ""} | SO: {entry.Meta?.sceneSoName ?? ""}",
EditorStyles.miniLabel);
if (isSelected)
{
var maxWidth = EditorGUIUtility.currentViewWidth - 48f;
var height = 120f;
if (_displayThumbnail != null)
{
height = Mathf.Min(120f, _displayThumbnail.height * maxWidth / _displayThumbnail.width);
GUILayout.Label(_displayThumbnail, GUILayout.Width(maxWidth), GUILayout.Height(height));
}
else
{
EditorGUILayout.LabelField(
"(无缩略图)",
EditorStyles.miniLabel,
GUILayout.Width(maxWidth),
GUILayout.Height(height));
}
}
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("选中"))
{
SelectArchiveEntry(entry);
}
if (GUILayout.Button("读档"))
{
RestoreFromArchive(entry);
}
if (GUILayout.Button("删除"))
{
DeleteArchiveEntry(entry);
}
}
}
GUILayout.Space(4);
}
private void DrawRestoreLogSection()
{
EditorGUILayout.LabelField("读档 Pipeline 日志", EditorStyles.boldLabel);
if (_restoreLogSnapshot.Count == 0)
{
EditorGUILayout.LabelField("(暂无)", EditorStyles.miniLabel);
return;
}
foreach (var line in _restoreLogSnapshot)
{
EditorGUILayout.LabelField(line, EditorStyles.wordWrappedMiniLabel);
}
}
private void DrawUtilitySection()
{
EditorGUILayout.LabelField("工具", EditorStyles.boldLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("打开 demo_saves 文件夹"))
{
OpenDirectory(ConstRef.SaveFilePath);
}
if (GUILayout.Button("打开存档验证工具"))
{
SaveSystemValidationWindow.Open();
}
}
}
private void DrawLogSection()
{
EditorGUILayout.LabelField("操作结果", EditorStyles.boldLabel);
EditorGUILayout.SelectableLabel(_lastLog, EditorStyles.textArea, GUILayout.MinHeight(80));
}
private void RefreshArchiveEntries(bool requestRepaint = true)
{
var newEntries = TestSaveArchive.ListEntries();
var entriesChanged = !ArchiveEntriesEqual(newEntries, _archiveEntries);
_archiveEntries = newEntries;
_lastArchiveRefreshTime = EditorApplication.timeSinceStartup;
if (!string.IsNullOrEmpty(_selectedArchiveEntryId) &&
!_archiveEntries.Exists(e => e.EntryId == _selectedArchiveEntryId))
{
_selectedArchiveEntryId = null;
ReleaseThumbnail();
entriesChanged = true;
}
if (requestRepaint && entriesChanged)
{
RequestRepaint();
}
}
private static bool ArchiveEntriesEqual(List<TestSaveArchiveEntry> a, List<TestSaveArchiveEntry> b)
{
if (a.Count != b.Count)
{
return false;
}
for (var i = 0; i < a.Count; i++)
{
if (a[i].EntryId != b[i].EntryId)
{
return false;
}
}
return true;
}
private void SelectArchiveEntry(TestSaveArchiveEntry entry)
{
_selectedArchiveEntryId = entry.EntryId;
ReleaseThumbnail();
if (!string.IsNullOrEmpty(entry.ThumbnailPath) && File.Exists(entry.ThumbnailPath))
{
var bytes = File.ReadAllBytes(entry.ThumbnailPath);
_selectedThumbnail = new Texture2D(2, 2);
_selectedThumbnail.LoadImage(bytes);
}
RequestRepaint();
}
private void ReleaseThumbnail()
{
if (_selectedThumbnail != null)
{
DestroyImmediate(_selectedThumbnail);
_selectedThumbnail = null;
}
}
private void TriggerAutoSave()
{
if (!Application.isPlaying)
{
Log("需要在 Play Mode 下执行。");
return;
}
try
{
if (!SavePointEvaluator.CanAutoSave(out var reason))
{
Log($"CanAutoSave 拒绝: {reason}");
return;
}
var routine = SaveRestoreOrchestrator.AutoSaveRoutine();
while (routine.MoveNext())
{
}
RefreshArchiveEntries();
Log(TestSaveArchive.IsEnabled
? "测试存档模式:已写入 testsavs(未覆盖 slot_0)。"
: "正式模式:已写入 slot_0。");
}
catch (Exception ex)
{
Log($"自动存档失败: {ex}");
}
}
private void CaptureInMemory()
{
try
{
if (YarnVariableStorage.Instance == null)
{
Log("YarnVariableStorage 未初始化。");
return;
}
var snapshot = SnapshotService.Capture();
Log($"Capture 成功: 场景={snapshot.scene?.sceneName}, 节点={snapshot.anchor?.nodeName}");
}
catch (Exception ex)
{
Log($"Capture 失败: {ex.Message}");
}
}
private void CopyAutoToManual()
{
if (!SlotDirectory.Exists(SlotIndex.Auto))
{
Log("slot_0 不存在。");
return;
}
SlotManager.CopyAutoToManual(_manualSlotIndex);
Log($"已复制到 slot_{_manualSlotIndex}。");
}
private void RestoreLatestSlot()
{
var latest = SlotManager.GetLatestSlotIndex();
if (!latest.HasValue)
{
Log("没有 latest_slot。");
return;
}
RestoreSlot(latest.Value);
}
private void RestoreSlot(int slotIndex)
{
if (!Application.isPlaying)
{
Log("读档需要在 Play Mode 下执行。");
return;
}
if (GameManager.Instance == null)
{
Log("GameManager 未初始化。");
return;
}
if (!SlotDirectory.Exists(slotIndex))
{
Log($"slot_{slotIndex} 不存在。");
return;
}
GameManager.Instance.TryRestoreSlot(slotIndex);
Log($"已启动读档 slot_{slotIndex}。");
}
private void RestoreFromArchive(TestSaveArchiveEntry entry)
{
if (!Application.isPlaying)
{
Log("读档需要在 Play Mode 下执行。");
return;
}
if (GameManager.Instance == null)
{
Log("GameManager 未初始化。");
return;
}
GameManager.Instance.TryRestoreFile(
entry.SnapshotPath,
RestoreOptions.DevJump);
Log($"已启动读档: {entry.EntryId}");
}
private void ArchiveCurrentAutoSlot()
{
var snapshot = SlotManager.LoadSnapshot(SlotIndex.Auto);
if (snapshot == null)
{
Log("slot_0 无快照。");
return;
}
var thumbnail = SlotManager.LoadThumbnail(SlotIndex.Auto);
var entryId = TestSaveArchive.Archive(snapshot, thumbnail);
RefreshArchiveEntries();
Log($"已手动归档: {entryId}");
}
private void DeleteArchiveEntry(TestSaveArchiveEntry entry)
{
var entryId = entry.EntryId;
EditorApplication.delayCall += () => ConfirmDeleteArchiveEntry(entryId);
}
private void ConfirmDeleteArchiveEntry(string entryId)
{
if (!EditorUtility.DisplayDialog("删除测试归档", $"删除 {entryId}", "删除", "取消"))
{
return;
}
TestSaveArchive.DeleteEntry(entryId);
if (_selectedArchiveEntryId == entryId)
{
_selectedArchiveEntryId = null;
ReleaseThumbnail();
}
RefreshArchiveEntries();
Log($"已删除: {entryId}");
}
private void ClearTestArchives()
{
EditorApplication.delayCall += ConfirmClearTestArchives;
}
private void ConfirmClearTestArchives()
{
if (!EditorUtility.DisplayDialog("清空 testsavs", "删除所有测试归档?不可撤销。", "删除", "取消"))
{
return;
}
TestSaveArchive.ClearAll();
_selectedArchiveEntryId = null;
ReleaseThumbnail();
RefreshArchiveEntries();
Log("已清空 testsavs。");
}
private static void OpenDirectory(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
EditorUtility.RevealInFinder(path);
}
private void Log(string message)
{
_lastLog = $"[{DateTime.Now:HH:mm:ss}] {message}";
Debug.Log($"[SaveSystemTest] {message}");
RequestRepaint();
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 27c49345ec3a4e849af3f9da7045f7a3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,704 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AibisDream;
using AibisDream.SaveSystem;
using AibisDream.Utility;
using Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
namespace AibisDream.SaveSystem.Editor
{
/// <summary>
/// 存档系统 P1/P2/P3 存盘行为验证工具。
/// 不验证读档(P4),不验证复杂 tags 边界(P3 后续)。
/// </summary>
public class SaveSystemValidationWindow : EditorWindow
{
private const string MenuPath = "AIBIS/存档系统验证工具";
private const string WindowTitle = "SaveSystem Validator";
private const float SaveHistoryBoxMinHeight = 400f;
private Vector2 _scrollPosition;
private string _lastLog = string.Empty;
private int _manualSlotIndex = 1;
private int _deleteSlotIndex = 1;
private string _testNodeName = "SomeNode";
private string _testTags = "";
private int _restoreSlotIndex;
private readonly List<SaveHistoryEntry> _saveHistory = new();
private string _lastObservedSavedAt;
/// <summary>运行时自动存档历史条目,仅内存缓存。</summary>
private class SaveHistoryEntry
{
public string SavedAt;
public string NodeName;
public string SceneName;
}
[MenuItem(MenuPath)]
public static void Open()
{
var window = GetWindow<SaveSystemValidationWindow>(WindowTitle);
window.minSize = new Vector2(420, 640);
}
private void OnEnable()
{
EditorApplication.update += OnEditorUpdate;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
InitializeLastObservedSavedAt();
}
private void OnDisable()
{
EditorApplication.update -= OnEditorUpdate;
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
private void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode)
{
_saveHistory.Clear();
InitializeLastObservedSavedAt();
TrySeedHistoryFromCurrentAutoSlot();
Repaint();
return;
}
if (state == PlayModeStateChange.ExitingPlayMode)
{
_saveHistory.Clear();
_lastObservedSavedAt = null;
}
}
private void TrySeedHistoryFromCurrentAutoSlot()
{
var meta = SlotManager.LoadMeta(SlotIndex.Auto);
if (meta == null)
{
return;
}
_saveHistory.Add(CreateHistoryEntry(meta));
_lastObservedSavedAt = meta.savedAt;
}
private static SaveHistoryEntry CreateHistoryEntry(SlotMeta meta)
{
return new SaveHistoryEntry
{
SavedAt = meta.savedAt,
NodeName = string.IsNullOrEmpty(meta.nodeName) ? "(无节点)" : meta.nodeName,
SceneName = meta.sceneName
};
}
private void InitializeLastObservedSavedAt()
{
var meta = SlotManager.LoadMeta(SlotIndex.Auto);
_lastObservedSavedAt = meta?.savedAt;
}
/// <summary>
/// 监听 slot_0 的 meta 变化,运行时每次自动存档都会记录到内存列表中。
/// 纯 Editor 行为,不侵入运行时代码。
/// </summary>
private void OnEditorUpdate()
{
if (!Application.isPlaying)
{
return;
}
var meta = SlotManager.LoadMeta(SlotIndex.Auto);
if (meta == null)
{
return;
}
if (!string.IsNullOrEmpty(_lastObservedSavedAt) && _lastObservedSavedAt == meta.savedAt)
{
return;
}
_lastObservedSavedAt = meta.savedAt;
_saveHistory.Add(CreateHistoryEntry(meta));
Repaint();
}
private void OnGUI()
{
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
DrawHeader();
DrawSlotOverview();
GUILayout.Space(12);
DrawSaveHistorySection();
GUILayout.Space(12);
DrawDirectCaptureSection();
GUILayout.Space(12);
DrawAutoSaveSimulationSection();
GUILayout.Space(12);
DrawManualSlotSection();
GUILayout.Space(12);
DrawRestoreSection();
GUILayout.Space(12);
DrawSavePointEvaluationSection();
GUILayout.Space(12);
DrawUtilitySection();
GUILayout.Space(12);
DrawLogSection();
EditorGUILayout.EndScrollView();
}
#region Drawing
private void DrawHeader()
{
EditorGUILayout.LabelField("存档系统存盘验证", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"本工具仅验证 P1/P2/P3 的存盘行为:快照捕获、槽位落盘、元数据、可存点判定框架。\n" +
"不验证读档(P4)和复杂 tags 边界(P3 后续)。\n" +
"涉及运行时单例(DialogController / GameManager)的操作需要在 Play Mode 下执行。",
MessageType.Info);
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox("当前不在 Play Mode。Capture / TryAutoSave 等依赖运行时单例的操作可能失败。", MessageType.Warning);
}
}
private void DrawSlotOverview()
{
EditorGUILayout.LabelField("槽位概览", EditorStyles.boldLabel);
var latest = SlotManager.GetLatestSlotIndex();
EditorGUILayout.LabelField($"最近槽位: {(latest.HasValue ? latest.Value.ToString() : "")}");
EditorGUILayout.LabelField($"存档根目录: {ConstRef.SaveFilePath}");
EditorGUILayout.LabelField(
$"读档状态: IsRestoring={SaveRestoreOrchestrator.IsRestoring}, " +
$"SuppressAutoSave={SaveRestoreOrchestrator.IsAutoSaveSuppressed}");
var viewModels = SlotManager.GetSlotViewModels();
foreach (var vm in viewModels)
{
DrawSlotViewModel(vm, latest);
}
}
private void DrawSlotViewModel(SlotViewModel vm, int? latest)
{
var isLatest = latest.HasValue && latest.Value == vm.SlotIndex;
var nodeName = SlotManager.LoadMeta(vm.SlotIndex)?.nodeName;
var label = vm.IsEmpty
? $"[{vm.SlotIndex}] (空)"
: $"[{vm.SlotIndex}] {(vm.IsAutoSlot ? "[]" : "")} {(isLatest ? "[]" : "")}\n" +
$" 场景: {vm.SceneName}\n" +
$" SO: {vm.SceneSoName}\n" +
$" 节点: {nodeName}\n" +
$" 时间: {vm.SavedAt}\n" +
$" 缩略图: {(string.IsNullOrEmpty(vm.ThumbnailPath) ? "" : Path.GetFileName(vm.ThumbnailPath))}";
EditorGUILayout.HelpBox(label, vm.IsEmpty ? MessageType.None : MessageType.Info);
}
private void DrawDirectCaptureSection()
{
EditorGUILayout.LabelField("直接捕获快照", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"调用 SnapshotService.Capture() 组装快照,并在内存中检查结构。\n" +
"此操作不写盘,不经过 SavePointEvaluator。",
MessageType.None);
if (GUILayout.Button("Capture Snapshot (内存)"))
{
CaptureSnapshotInMemory();
}
}
private void DrawAutoSaveSimulationSection()
{
EditorGUILayout.LabelField("模拟自动存档", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"调用 SaveRestoreOrchestrator.TryAutoSave(),走完整的判定+捕获+落盘流程。\n" +
"需要在 Play Mode 下且有 DialogController 运行时实例。",
MessageType.None);
if (GUILayout.Button("TryAutoSave (完整流程)"))
{
TryAutoSaveFullFlow();
}
}
private void DrawSaveHistorySection()
{
EditorGUILayout.LabelField("自动存档历史 (运行时)", EditorStyles.boldLabel);
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox, GUILayout.MinHeight(SaveHistoryBoxMinHeight)))
{
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox(
"进入 Play Mode 后,每次 slot_0 自动存档都会在此记录节点名与时间。\n" +
"数据仅保存在当前窗口内存中,退出 Play Mode 后清空。",
MessageType.Info);
}
else if (_saveHistory.Count == 0)
{
EditorGUILayout.LabelField("等待自动存档…", EditorStyles.wordWrappedLabel);
EditorGUILayout.LabelField(
"触发 content / hub / linear 节点,或点击下方 TryAutoSave。",
EditorStyles.miniLabel);
}
else
{
EditorGUILayout.LabelField($"共 {_saveHistory.Count} 条记录", EditorStyles.miniLabel);
for (var i = 0; i < _saveHistory.Count; i++)
{
var entry = _saveHistory[i];
EditorGUILayout.LabelField(
$"{i + 1}. [{entry.SavedAt}] {entry.NodeName}",
EditorStyles.wordWrappedLabel);
EditorGUILayout.LabelField(
$" 场景: {entry.SceneName ?? "N/A"}",
EditorStyles.miniLabel);
}
GUILayout.FlexibleSpace();
if (GUILayout.Button("清空历史记录"))
{
_saveHistory.Clear();
}
}
}
}
private void DrawManualSlotSection()
{
EditorGUILayout.LabelField("手动档操作", EditorStyles.boldLabel);
_manualSlotIndex = EditorGUILayout.IntSlider("复制到槽位", _manualSlotIndex, 1, SlotIndex.ManualEnd);
if (GUILayout.Button("Copy Auto → Manual"))
{
CopyAutoToManual(_manualSlotIndex);
}
GUILayout.Space(8);
_deleteSlotIndex = EditorGUILayout.IntSlider("删除槽位", _deleteSlotIndex, 0, SlotIndex.ManualEnd);
if (GUILayout.Button("Delete Slot"))
{
DeleteSlot(_deleteSlotIndex);
}
GUILayout.Space(8);
if (GUILayout.Button("Clear All Saves"))
{
ClearAllSaves();
}
}
private void DrawSavePointEvaluationSection()
{
EditorGUILayout.LabelField("可存点判定框架检查", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"手动设置节点名和 tags,模拟 OnNodeStart 自动档判定(含 DetourResume)。\n" +
"InProgress 来自运行时 DialogController;全局门控按实际 Play Mode 状态取值。",
MessageType.None);
_testNodeName = EditorGUILayout.TextField("节点名", _testNodeName);
_testTags = EditorGUILayout.TextField("Tags (逗号分隔)", _testTags);
if (GUILayout.Button("Evaluate CanAutoSave"))
{
EvaluateSavePoint();
}
}
private void DrawRestoreSection()
{
EditorGUILayout.LabelField("读档验证 (P4)", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"触发 GameManager.TryRestoreSlot,验证预检、Phase + Barrier 编排、自动存档抑制与 Provider 还原日志。\n" +
"需要在 Play Mode 下执行;不代表正式玩家 UI。",
MessageType.None);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Restore Latest"))
{
RestoreLatestSlot();
}
if (GUILayout.Button("Restore slot_0"))
{
RestoreSlot(SlotIndex.Auto);
}
}
_restoreSlotIndex = EditorGUILayout.IntSlider("Restore 槽位", _restoreSlotIndex, 0, SlotIndex.ManualEnd);
if (GUILayout.Button($"Restore slot_{_restoreSlotIndex}"))
{
RestoreSlot(_restoreSlotIndex);
}
var restoreLog = SaveRestoreOrchestrator.LastRestoreLog;
if (restoreLog.Count > 0)
{
EditorGUILayout.LabelField("Restore Pipeline Log", EditorStyles.boldLabel);
foreach (var line in restoreLog)
{
EditorGUILayout.LabelField(line, EditorStyles.wordWrappedMiniLabel);
}
}
}
private void DrawUtilitySection()
{
EditorGUILayout.LabelField("工具", EditorStyles.boldLabel);
if (GUILayout.Button("打开存档目录"))
{
OpenSaveDirectory();
}
if (GUILayout.Button("刷新"))
{
Repaint();
Log("已刷新。");
}
}
private void DrawLogSection()
{
GUILayout.Space(12);
EditorGUILayout.LabelField("最后结果", EditorStyles.boldLabel);
EditorGUILayout.SelectableLabel(_lastLog, EditorStyles.textArea, GUILayout.MinHeight(120));
}
#endregion
#region Actions
private void CaptureSnapshotInMemory()
{
try
{
var storage = YarnVariableStorage.Instance;
if (storage == null)
{
Log("YarnVariableStorage 未初始化,尝试直接 new SaveSnapshot 仅做结构演示。");
var demo = new SaveSnapshot
{
gameVersion = Application.version,
savedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
scene = new SceneSnapshotDto { sceneName = "DemoScene" },
anchor = new AnchorSnapshot { nodeName = "DemoNode" },
yarnVariables = new YarnVariablesSnapshot()
};
PrintSnapshotSummary(demo, "演示快照(无运行时)");
return;
}
var snapshot = SnapshotService.Capture();
PrintSnapshotSummary(snapshot, "内存快照");
}
catch (Exception ex)
{
Log($"Capture 失败: {ex}");
}
}
private void TryAutoSaveFullFlow()
{
if (!Application.isPlaying)
{
Log("TryAutoSave 需要在 Play Mode 下执行。");
return;
}
try
{
var beforeLatest = SlotManager.GetLatestSlotIndex();
var beforeSnapshot = SlotManager.LoadSnapshot(0);
if (!SavePointEvaluator.CanAutoSave(out var rejectReason))
{
Log($"CanAutoSave 拒绝: {rejectReason}");
return;
}
var routine = SaveRestoreOrchestrator.AutoSaveRoutine();
while (routine.MoveNext())
{
}
var afterSnapshot = SlotManager.LoadSnapshot(0);
var afterLatest = SlotManager.GetLatestSlotIndex();
var summary = string.Empty;
if (afterSnapshot == null)
{
summary = "无快照写入(可能被 SavePointEvaluator 拒绝)。";
}
else
{
summary = $"落盘成功。\n" +
$"场景: {afterSnapshot.scene?.sceneName}\n" +
$"节点: {afterSnapshot.anchor?.nodeName}\n" +
$"SO: {afterSnapshot.anchor?.sceneSoName}\n" +
$"YarnProject: {afterSnapshot.anchor?.yarnProjectId}\n" +
$"Sections: {string.Join(", ", afterSnapshot.sections?.Keys ?? Enumerable.Empty<string>())}\n" +
$"latest_slot: {(afterLatest.HasValue ? afterLatest.Value.ToString() : "")}";
}
Log(summary);
}
catch (Exception ex)
{
Log($"TryAutoSave 失败: {ex}");
}
}
private void CopyAutoToManual(int slotIndex)
{
try
{
if (!SlotDirectory.Exists(SlotIndex.Auto))
{
Log("自动档不存在,无法复制手动档。");
return;
}
SlotManager.CopyAutoToManual(slotIndex);
Log($"已复制自动档到 slot_{slotIndex}。");
}
catch (Exception ex)
{
Log($"复制失败: {ex.Message}");
}
}
private void DeleteSlot(int slotIndex)
{
try
{
SlotManager.DeleteSlot(slotIndex);
Log($"已删除/清空 slot_{slotIndex}。");
}
catch (Exception ex)
{
Log($"删除失败: {ex.Message}");
}
}
private void ClearAllSaves()
{
if (!EditorUtility.DisplayDialog("确认", "删除所有存档槽位?此操作不可撤销。", "删除", "取消"))
{
return;
}
try
{
for (var i = SlotIndex.Auto; i <= SlotIndex.ManualEnd; i++)
{
SlotManager.DeleteSlot(i);
}
var latestPath = SlotDirectory.GetLatestSlotIndexPath();
if (File.Exists(latestPath))
{
File.Delete(latestPath);
}
Log("已清空所有槽位和 latest_slot 记录。");
}
catch (Exception ex)
{
Log($"清空失败: {ex.Message}");
}
}
private void EvaluateSavePoint()
{
if (!Application.isPlaying)
{
Log("SavePointEvaluator 需要运行时状态(IsRestoring / GameManager pause)。请在 Play Mode 下执行。");
return;
}
try
{
var projectId = DialogController.Instance?.DialogueRunner?.YarnProject?.name;
var tags = ParseTestTags(_testTags);
var inProgress = SavePointEvaluator.InProgressNodeNames;
if (SaveRestoreOrchestrator.IsAutoSaveSuppressed)
{
Log("全局门控: AutoSaveSuppressed");
return;
}
if (SaveRestoreOrchestrator.IsRestoring)
{
Log("全局门控: Restoring(读档重进走 OnRestoreEnterNode,不写盘)");
return;
}
if (GameManager.Instance != null && GameManager.Session.IsPaused)
{
Log("全局门控: GamePaused");
return;
}
var detourResume = !string.IsNullOrEmpty(_testNodeName)
&& inProgress.Contains(_testNodeName);
var tagOk = SavePointEvaluator.EvaluateNodeTagsForAutoSave(
_testNodeName, tags, out var tagReason);
var wouldSave = !detourResume && tagOk;
Log(
$"Project = {projectId ?? "(null)"}\n" +
$"InProgress = [{string.Join(", ", inProgress)}]\n" +
$"模拟节点 = {_testNodeName}, tags = [{string.Join(", ", tags)}]\n" +
$"DetourResume = {detourResume}\n" +
$"Tag 判定 = {tagOk}, TagReason = {tagReason}\n" +
$"OnNodeStart 会自动存 = {wouldSave}\n" +
$"(DialogController 当前上下文 CanAutoSave = {SavePointEvaluator.CanAutoSave(out var ctxReason)}, Reason = {ctxReason})");
}
catch (Exception ex)
{
Log($"判定失败: {ex.Message}");
}
}
private static string[] ParseTestTags(string raw)
{
if (string.IsNullOrWhiteSpace(raw))
{
return Array.Empty<string>();
}
return raw.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(tag => tag.Trim())
.Where(tag => tag.Length > 0)
.ToArray();
}
private void RestoreLatestSlot()
{
var latest = SlotManager.GetLatestSlotIndex();
if (!latest.HasValue)
{
Log("没有 latest_slot,无法读档。");
return;
}
RestoreSlot(latest.Value);
}
private void RestoreSlot(int slotIndex)
{
if (!Application.isPlaying)
{
Log("Restore 需要在 Play Mode 下执行。");
return;
}
if (GameManager.Instance == null)
{
Log("GameManager 未初始化,无法启动读档协程。");
return;
}
if (!SlotDirectory.Exists(slotIndex))
{
Log($"slot_{slotIndex} 不存在,无法读档。");
return;
}
GameManager.Instance.TryRestoreSlot(slotIndex);
Log($"已启动 Restore slot_{slotIndex}。请观察 Restore Pipeline Log 与场景状态。");
}
private void OpenSaveDirectory()
{
var path = ConstRef.SaveFilePath;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
EditorUtility.RevealInFinder(path);
}
#endregion
#region Helpers
private void PrintSnapshotSummary(SaveSnapshot snapshot, string label)
{
var json = SnapshotPersistence.Serialize(snapshot);
var lines = new List<string>
{
$"=== {label} ===",
$"gameVersion: {snapshot.gameVersion}",
$"savedAt: {snapshot.savedAt}",
$"schemaVersion: {snapshot.schemaVersion}",
$"scene: {snapshot.scene?.sceneName}",
$"anchor.sceneSoName: {snapshot.anchor?.sceneSoName}",
$"anchor.yarnProjectId: {snapshot.anchor?.yarnProjectId}",
$"anchor.nodeName: {snapshot.anchor?.nodeName}",
$"yarnVariables.floats: {snapshot.yarnVariables?.floats?.Count ?? 0} entries",
$"yarnVariables.strings: {snapshot.yarnVariables?.strings?.Count ?? 0} entries",
$"yarnVariables.bools: {snapshot.yarnVariables?.bools?.Count ?? 0} entries",
$"sections: {(snapshot.sections != null ? string.Join(", ", snapshot.sections.Keys) : "null")}",
};
if (snapshot.sections != null)
{
AppendFixSectionHint(lines, snapshot, SnapshotProviderIds.PunchTape);
AppendFixSectionHint(lines, snapshot, SnapshotProviderIds.Fix);
AppendFixSectionHint(lines, snapshot, SnapshotProviderIds.FixPanel);
AppendFixSectionHint(lines, snapshot, SnapshotProviderIds.BodyModule);
AppendFixSectionHint(lines, snapshot, SnapshotProviderIds.Eye);
}
lines.Add("JSON preview (first 1500 chars):");
lines.Add(json.Length > 1500 ? json.Substring(0, 1500) + "..." : json);
Log(string.Join("\n", lines));
}
private static void AppendFixSectionHint(List<string> lines, SaveSnapshot snapshot, string sectionId)
{
if (snapshot.sections.ContainsKey(sectionId))
{
lines.Add($" [fix-related] {sectionId}: captured");
}
}
private void Log(string message)
{
_lastLog = $"[{DateTime.Now:HH:mm:ss}] {message}";
Debug.Log($"[SaveSystemValidator] {message}");
Repaint();
}
#endregion
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 9719ab6f265683c4484a170f6c4dc843
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
-17
View File
@@ -19,19 +19,8 @@ namespace AibisDream.Utility
public const string WishlistURL = "https://store.steampowered.com/app/3473430/_All_Our_Broken_Parts?utm_source=playtest";
/// <summary>
/// 可提交的开发跳转存档根目录(DeveloperModePanel)。
/// 编辑器中位于项目根目录,Development Build 中位于可执行文件旁;不会被 Unity 自动打包。
/// </summary>
public static readonly string TestSaveFilePath =
Path.GetFullPath(Path.Combine(Application.dataPath, "..", "DevSaveFiles"));
public static readonly string SaveFilePath = Path.Combine(Application.persistentDataPath, "AllOurBrokenParts", "demo_saves");
/// <summary>测试阶段自动存档历史目录;每次自动存档独立子文件夹,不覆盖正式槽位。</summary>
public static readonly string TestAutoSaveArchivePath =
Path.Combine(Application.persistentDataPath, "AllOurBrokenParts", "testsavs");
/// <summary>P2 槽位相关常量。</summary>
public const int SaveSlotCount = 8;
/// <summary>存档槽位缩略图;关闭可避免主相机 Render + ReadPixels 的性能开销。</summary>
@@ -44,12 +33,6 @@ namespace AibisDream.Utility
public const string SaveThumbnailFileName = "thumbnail";
public const string LatestSlotFileName = "latest_slot";
/// <summary>P1 快照层自测落盘路径(P2 槽位层接管后迁移)。</summary>
public static readonly string SnapshotTestPath =
Path.Combine(Application.persistentDataPath, "AllOurBrokenParts", "snapshot_test");
public const string SnapshotTestFileName = "latest_snapshot";
public static readonly string ChapterProgressPath = Path.Combine(Application.persistentDataPath, "AllOurBrokenParts", "Config", "chapter.json");
public static readonly string CharacterConfigPath = Path.Combine(Application.streamingAssetsPath, "Config", "character.csv");
-19
View File
@@ -113,25 +113,6 @@ namespace AibisDream
return true;
}
public bool TryRestoreFile(
string path,
RestoreOptions options,
Action<RestoreResult> completed = null)
{
if (!CanBeginRestore() || !TryAcquireCommand(SessionCommandKind.Restore))
{
return false;
}
StartCoroutine(RestoreRoutine(
() => SaveRestoreOrchestrator.PrepareRestoreFromFile(
path,
_talkSceneIndex,
options ?? RestoreOptions.Default),
completed));
return true;
}
public bool TryPause()
{
if (!_session.CanPause)
@@ -15,10 +15,6 @@ namespace AibisDream.SaveSystem
StrictValidation = true
};
public static RestoreOptions DevJump => new()
{
StrictValidation = true
};
}
public sealed class RestoreResult
-8
View File
@@ -27,12 +27,6 @@ SaveRestoreOrchestrator.CreateManualSlot(slotIndex);
// 槽位读档:会话命令统一从 GameManager 进入
GameManager.Instance.TryRestoreSlot(slotIndex, result => { /* 处理结果 */ });
// 严格文件读档 / 开发跳转
GameManager.Instance.TryRestoreFile(
path,
RestoreOptions.DevJump,
result => { /* 处理结果 */ });
// 仅捕获内存快照;恢复仍须通过 GameManager,以保证会话互斥和失败清理
var snap = SnapshotService.Capture();
@@ -69,8 +63,6 @@ Yarn 脚本:
正式槽位路径:`persistentDataPath/AllOurBrokenParts/demo_saves/slot_x/{snapshot.json, meta.json, thumbnail.png}`
P1 测试落盘路径:`persistentDataPath/AllOurBrokenParts/snapshot_test/latest_snapshot.json`(首次访问槽位系统时会尝试迁移到 `slot_0`
## 目录结构
```
@@ -91,27 +91,18 @@ namespace AibisDream.SaveSystem
infoPanel?.HideSaveLoading();
if (TestSaveArchive.IsEnabled)
{
TestSaveArchive.Archive(snapshot, thumbnail);
Debug.Log("[SaveRestoreOrchestrator] 测试存档模式:已写入 testsavs(不覆盖 slot_0)。");
_isAutoSaving = false;
}
else
{
_ = SlotManager.SaveToAutoSlotAsync(snapshot, thumbnail).ContinueWith(
writeTask =>
_ = SlotManager.SaveToAutoSlotAsync(snapshot, thumbnail).ContinueWith(
writeTask =>
{
if (writeTask.IsFaulted)
{
if (writeTask.IsFaulted)
{
Debug.LogError(
$"[SaveRestoreOrchestrator] 自动存档写盘失败: {writeTask.Exception?.GetBaseException()}");
}
Debug.LogError(
$"[SaveRestoreOrchestrator] 自动存档写盘失败: {writeTask.Exception?.GetBaseException()}");
}
_isAutoSaving = false;
},
TaskContinuationOptions.ExecuteSynchronously);
}
_isAutoSaving = false;
},
TaskContinuationOptions.ExecuteSynchronously);
if (omitAnchor || (snapshot?.anchor != null && string.IsNullOrEmpty(snapshot.anchor.nodeName)))
{
@@ -167,28 +158,6 @@ namespace AibisDream.SaveSystem
return PrepareSnapshot(snapshot, sourceLabel, talkSceneIndex, options);
}
internal static RestorePreparation PrepareRestoreFromFile(
string savePath,
TalkSceneGraphIndex talkSceneIndex,
RestoreOptions options)
{
options ??= RestoreOptions.Default;
try
{
SaveSnapshot snapshot;
using (new CodeTimer("LoadSnapshot"))
{
snapshot = SnapshotPersistence.Load(savePath);
}
return PrepareSnapshot(snapshot, savePath, talkSceneIndex, options);
}
catch (Exception ex)
{
return FailedPreparation(savePath, "LoadSnapshot", ex.Message, options);
}
}
internal static IEnumerator ExecutePreparedRestore(
RestorePreparation preparation,
Func<bool> isCancellationRequested)
+1 -65
View File
@@ -10,17 +10,15 @@ using UnityEngine;
namespace AibisDream.SaveSystem
{
/// <summary>
/// 存档槽位业务核心:管理自动档、手动档、读档继续游戏与 P1 迁移
/// 存档槽位业务核心:管理自动档、手动档、读档继续游戏。
/// </summary>
public static class SlotManager
{
private static bool _migrated;
private static readonly object SlotWriteLock = new object();
/// <summary>将快照与缩略图写入指定槽位。</summary>
public static void SaveToSlot(int slotIndex, SaveSnapshot snapshot, byte[] thumbnailPng)
{
EnsureMigrated();
WriteSlot(slotIndex, snapshot, thumbnailPng);
}
@@ -37,8 +35,6 @@ namespace AibisDream.SaveSystem
/// </summary>
public static async Task SaveToAutoSlotAsync(SaveSnapshot snapshot, byte[] thumbnailPng)
{
EnsureMigrated();
await Task.Run(() =>
{
var json = SnapshotPersistence.Serialize(snapshot);
@@ -53,8 +49,6 @@ namespace AibisDream.SaveSystem
/// <summary>将当前自动档复制到指定手动档。</summary>
public static void CopyAutoToManual(int manualSlotIndex)
{
EnsureMigrated();
if (manualSlotIndex < SlotIndex.ManualStart || manualSlotIndex > SlotIndex.ManualEnd)
{
Debug.LogError($"[SlotManager] 手动槽位编号非法: {manualSlotIndex}");
@@ -99,8 +93,6 @@ namespace AibisDream.SaveSystem
/// <summary>删除指定槽位。</summary>
public static void DeleteSlot(int slotIndex)
{
EnsureMigrated();
if (slotIndex == SlotIndex.Auto)
{
// 自动档只清空内容,不删除目录
@@ -128,8 +120,6 @@ namespace AibisDream.SaveSystem
/// <summary>读取槽位的完整快照。</summary>
public static SaveSnapshot LoadSnapshot(int slotIndex)
{
EnsureMigrated();
var path = SlotDirectory.GetSnapshotPath(slotIndex);
if (!File.Exists(path))
{
@@ -151,7 +141,6 @@ namespace AibisDream.SaveSystem
/// <summary>是否存在可用的自动档(slot_0 meta + snapshot)。</summary>
public static bool HasAutoSave()
{
EnsureMigrated();
return SlotDirectory.Exists(SlotIndex.Auto)
&& File.Exists(SlotDirectory.GetSnapshotPath(SlotIndex.Auto));
}
@@ -200,8 +189,6 @@ namespace AibisDream.SaveSystem
/// <summary>获取最近写入的槽位编号;不存在返回 null。</summary>
public static int? GetLatestSlotIndex()
{
EnsureMigrated();
var path = SlotDirectory.GetLatestSlotIndexPath();
if (!File.Exists(path))
{
@@ -239,8 +226,6 @@ namespace AibisDream.SaveSystem
/// <summary>获取供 UI 展示的全部槽位视图模型。</summary>
public static SlotViewModel[] GetSlotViewModels()
{
EnsureMigrated();
var result = new List<SlotViewModel>();
for (var i = SlotIndex.Auto; i <= SlotIndex.ManualEnd; i++)
{
@@ -357,55 +342,6 @@ namespace AibisDream.SaveSystem
thumbnailPath: thumbnailPath);
}
private static void EnsureMigrated()
{
if (_migrated)
{
return;
}
_migrated = true;
TryMigrateP1TestSnapshot();
}
private static void TryMigrateP1TestSnapshot()
{
var testPath = Path.Combine(ConstRef.SnapshotTestPath, $"{ConstRef.SnapshotTestFileName}.json");
if (!File.Exists(testPath))
{
return;
}
if (SlotDirectory.Exists(SlotIndex.Auto))
{
return;
}
try
{
var snapshot = SnapshotPersistence.Load(testPath);
if (snapshot == null)
{
return;
}
SaveToAutoSlot(snapshot, null);
var migratedPath = testPath + ".migrated";
if (File.Exists(migratedPath))
{
File.Delete(migratedPath);
}
File.Move(testPath, migratedPath);
Debug.Log($"[SlotManager] 已将 P1 测试存档从 {testPath} 迁移到 slot_0。");
}
catch (Exception ex)
{
Debug.LogWarning($"[SlotManager] P1 测试存档迁移失败: {ex.Message}");
}
}
[Serializable]
private class LatestSlotRecord
{
@@ -1,5 +1,3 @@
using System.IO;
using AibisDream.Utility;
using Newtonsoft.Json;
using UnityEngine;
@@ -8,8 +6,8 @@ namespace AibisDream.SaveSystem
/// <summary>
/// 快照与磁盘的边界:序列化和读写 JSON 文件。
/// <para>
/// P2 起,业务存档统一通过 <see cref="SlotManager"/> 写入槽位目录;
/// 本类保留按路径读写的低级接口,供迁移和外部调试使用
/// 业务存档统一通过 <see cref="SlotManager"/> 写入槽位目录;
/// 本类只负责快照序列化、反序列化与槽位入口
/// </para>
/// </summary>
public static class SnapshotPersistence
@@ -21,12 +19,6 @@ namespace AibisDream.SaveSystem
NullValueHandling = NullValueHandling.Ignore
};
/// <summary>P1 自测落盘:<c>SnapshotTestPath/latest_snapshot.json</c>P2 迁移源)。</summary>
public static string GetTestFilePath()
{
return Path.Combine(ConstRef.SnapshotTestPath, ConstRef.SnapshotTestFileName);
}
/// <summary>将快照序列化并写入指定槽位。</summary>
public static void SaveToSlot(int slotIndex, SaveSnapshot snapshot)
{
@@ -58,26 +50,5 @@ namespace AibisDream.SaveSystem
return snapshot;
}
/// <summary>将快照序列化并写入 path(自动补 .json 后缀)。</summary>
public static void Save(SaveSnapshot snapshot, string pathWithoutExtension)
{
var json = Serialize(snapshot);
var path = JsonUtil.FormatAsJsonPath(pathWithoutExtension);
var dir = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(dir))
{
Directory.CreateDirectory(dir);
}
File.WriteAllText(path, json);
}
/// <summary>从 path 读取并反序列化为 <see cref="SaveSnapshot"/>。</summary>
public static SaveSnapshot Load(string pathWithoutExtension)
{
var json = File.ReadAllText(JsonUtil.FormatAsJsonPath(pathWithoutExtension));
return Deserialize(json);
}
}
}
@@ -1,218 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AibisDream.Utility;
using Newtonsoft.Json;
using UnityEngine;
namespace AibisDream.SaveSystem
{
/// <summary>
/// 测试存档模式:每次自动存档写入 <see cref="ConstRef.TestAutoSaveArchivePath"/> 下的独立子目录。
/// 开启后不写入 slot_0,存多少次就保留多少份,与正式 demo_saves/ 完全隔离。
/// </summary>
public static class TestSaveArchive
{
public const string EditorPrefsEnabledKey = "AibisDream.SaveSystem.TestArchiveEnabled";
/// <summary>测试存档模式是否开启(Editor 下由 EditorPrefs 控制,构建产物恒为 false)。</summary>
public static bool IsEnabled
{
get
{
#if UNITY_EDITOR
return UnityEditor.EditorPrefs.GetBool(EditorPrefsEnabledKey, true);
#else
return false;
#endif
}
}
private static readonly object ArchiveWriteLock = new object();
/// <summary>将快照写入 testsavs 新条目,返回文件夹名。每次调用均新建,不覆盖已有条目。</summary>
public static string Archive(SaveSnapshot snapshot, byte[] thumbnailPng)
{
if (snapshot == null)
{
return null;
}
var entryId = BuildUniqueEntryId(snapshot);
var entryDir = Path.Combine(ConstRef.TestAutoSaveArchivePath, entryId);
Directory.CreateDirectory(entryDir);
var json = SnapshotPersistence.Serialize(snapshot);
var meta = BuildMeta(entryId, snapshot);
lock (ArchiveWriteLock)
{
SlotAtomicWriter.WriteText(json, Path.Combine(entryDir, $"{ConstRef.SaveSnapshotFileName}.json"));
SlotAtomicWriter.WriteJson(meta, Path.Combine(entryDir, $"{ConstRef.SaveMetaFileName}.json"));
if (thumbnailPng != null)
{
SlotAtomicWriter.WriteBytes(
thumbnailPng,
Path.Combine(entryDir, $"{ConstRef.SaveThumbnailFileName}.png"));
}
}
Debug.Log($"[TestSaveArchive] 已归档测试存档: {entryId}");
return entryId;
}
public static List<TestSaveArchiveEntry> ListEntries()
{
var result = new List<TestSaveArchiveEntry>();
if (!Directory.Exists(ConstRef.TestAutoSaveArchivePath))
{
return result;
}
foreach (var dir in Directory.GetDirectories(ConstRef.TestAutoSaveArchivePath))
{
var entryId = Path.GetFileName(dir);
var snapshotPath = Path.Combine(dir, $"{ConstRef.SaveSnapshotFileName}.json");
if (!File.Exists(snapshotPath))
{
continue;
}
var metaPath = Path.Combine(dir, $"{ConstRef.SaveMetaFileName}.json");
var thumbPath = Path.Combine(dir, $"{ConstRef.SaveThumbnailFileName}.png");
SlotMeta meta = null;
if (File.Exists(metaPath))
{
try
{
meta = JsonConvert.DeserializeObject<SlotMeta>(File.ReadAllText(metaPath));
}
catch (Exception ex)
{
Debug.LogWarning($"[TestSaveArchive] 读取 meta 失败 ({entryId}): {ex.Message}");
}
}
result.Add(new TestSaveArchiveEntry(
entryId,
dir,
snapshotPath,
File.Exists(thumbPath) ? thumbPath : null,
meta));
}
return result
.OrderByDescending(e => e.Meta?.savedAt ?? string.Empty)
.ThenByDescending(e => e.EntryId, StringComparer.Ordinal)
.ToList();
}
public static void DeleteEntry(string entryId)
{
if (string.IsNullOrEmpty(entryId))
{
return;
}
var dir = Path.Combine(ConstRef.TestAutoSaveArchivePath, entryId);
if (Directory.Exists(dir))
{
Directory.Delete(dir, recursive: true);
}
}
public static void ClearAll()
{
if (!Directory.Exists(ConstRef.TestAutoSaveArchivePath))
{
return;
}
foreach (var dir in Directory.GetDirectories(ConstRef.TestAutoSaveArchivePath))
{
Directory.Delete(dir, recursive: true);
}
}
private static string BuildUniqueEntryId(SaveSnapshot snapshot)
{
var baseId = BuildEntryId(snapshot);
var entryId = baseId;
var suffix = 2;
while (Directory.Exists(Path.Combine(ConstRef.TestAutoSaveArchivePath, entryId)))
{
entryId = $"{baseId}_{suffix}";
suffix++;
}
return entryId;
}
private static string BuildEntryId(SaveSnapshot snapshot)
{
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
var nodeName = snapshot.anchor?.nodeName;
var safeNode = string.IsNullOrEmpty(nodeName) ? "no_node" : SanitizeEntryName(nodeName);
return $"{timestamp}_{safeNode}";
}
private static SlotMeta BuildMeta(string entryId, SaveSnapshot snapshot)
{
var summary = SaveSnapshotSummary.From(snapshot);
return new SlotMeta
{
slotIndex = -1,
savedAt = snapshot.savedAt,
sceneName = summary.SceneName,
sceneSoName = summary.SceneSoName,
yarnProjectId = summary.YarnProjectId,
nodeName = snapshot.anchor?.nodeName,
schemaVersion = snapshot.schemaVersion,
gameVersion = snapshot.gameVersion,
thumbnailFile = $"{ConstRef.SaveThumbnailFileName}.png"
};
}
private static string SanitizeEntryName(string name)
{
foreach (var c in Path.GetInvalidFileNameChars())
{
name = name.Replace(c, '_');
}
if (name.Length > 48)
{
name = name.Substring(0, 48);
}
return string.IsNullOrWhiteSpace(name) ? "no_node" : name;
}
}
public readonly struct TestSaveArchiveEntry
{
public readonly string EntryId;
public readonly string DirectoryPath;
public readonly string SnapshotPath;
public readonly string ThumbnailPath;
public readonly SlotMeta Meta;
public TestSaveArchiveEntry(
string entryId,
string directoryPath,
string snapshotPath,
string thumbnailPath,
SlotMeta meta)
{
EntryId = entryId;
DirectoryPath = directoryPath;
SnapshotPath = snapshotPath;
ThumbnailPath = thumbnailPath;
Meta = meta;
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 408acae5dfcc0a94094d87e33ba53cd4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -61,21 +61,16 @@ namespace AibisDream.UI
[SerializeField] private DeveloperModeListRow variableRowTemplate;
[Header("Saves")]
[SerializeField] private TMP_InputField saveSearchInput;
[SerializeField] private Button reloadSavesButton;
[SerializeField] private TMP_Text saveStatusText;
[SerializeField] private RectTransform slotContent;
[SerializeField] private DeveloperModeListRow slotRowTemplate;
[SerializeField] private RectTransform devSaveContent;
[SerializeField] private DeveloperModeListRow devSaveRowTemplate;
[Header("Paths")]
[SerializeField] private Button openSaveFolderButton;
[SerializeField] private Button copySaveFolderButton;
[SerializeField] private Button openLogFolderButton;
[SerializeField] private Button copyLogFolderButton;
[SerializeField] private Button openDevSaveFolderButton;
[SerializeField] private Button copyDevSaveFolderButton;
[SerializeField] private Button openCurrentLogButton;
[SerializeField] private Button copyCurrentLogButton;
@@ -94,8 +89,6 @@ namespace AibisDream.UI
private readonly List<DeveloperModeListRow> _logRows = new();
private readonly List<DeveloperModeListRow> _variableRows = new();
private readonly List<DeveloperModeListRow> _slotRows = new();
private readonly List<DeveloperModeListRow> _devSaveRows = new();
private VerText _verText;
private RuntimeLogRecord? _selectedLog;
private int _currentTab;
@@ -256,10 +249,6 @@ namespace AibisDream.UI
variableScopeButton?.onClick.AddListener(CycleVariableScope);
variableTypeButton?.onClick.AddListener(CycleVariableType);
saveSearchInput?.onValueChanged.AddListener(_ =>
{
_savesDirty = true;
});
reloadSavesButton?.onClick.AddListener(() =>
{
_savesDirty = true;
@@ -561,7 +550,6 @@ namespace AibisDream.UI
{
_savesDirty = false;
RefreshSlotRows();
RefreshDevSaveRows();
}
private void RefreshSlotRows()
@@ -592,47 +580,6 @@ namespace AibisDream.UI
HideRows(_slotRows, viewModels.Length);
}
private void RefreshDevSaveRows()
{
if (devSaveContent == null || devSaveRowTemplate == null) return;
var sections = DevSaveCatalog.Load(ConstRef.TestSaveFilePath);
var search = saveSearchInput?.text;
var rowIndex = 0;
foreach (var section in sections)
{
var entries = section.Entries.Where(entry => string.IsNullOrWhiteSpace(search)
|| Contains(section.Title, search)
|| Contains(entry.Label, search)
|| Contains(entry.AnchorNode, search)).ToArray();
if (entries.Length == 0) continue;
GetRow(_devSaveRows, devSaveRowTemplate, devSaveContent, rowIndex++)
.Bind($"── {section.Title} ({entries.Count(entry => entry.IsValid)}/{entries.Length}) ──", null, false,
new Color(0.35f, 0.9f, 1f));
foreach (var entry in entries)
{
var captured = entry;
var text = entry.IsValid
? $"{entry.Order:000} {entry.Label} | node={entry.AnchorNode} | scene={entry.Snapshot?.scene?.sceneName}"
: $"{entry.Order:000} {entry.Label} | 不可用:{entry.Error}";
GetRow(_devSaveRows, devSaveRowTemplate, devSaveContent, rowIndex++)
.Bind(text, () => RestoreDevSave(captured), entry.IsValid && CanStartRestore(),
entry.IsValid ? Color.white : new Color(1f, 0.5f, 0.45f), isError: !entry.IsValid);
}
}
if (sections.Count == 0)
{
GetRow(_devSaveRows, devSaveRowTemplate, devSaveContent, rowIndex++)
.Bind(DevSaveCatalog.LastError ?? "开发检查点清单为空。", null, false,
new Color(1f, 0.5f, 0.45f), isError: true);
}
HideRows(_devSaveRows, rowIndex);
}
private bool CanStartRestore()
{
if (_isRestorePending || SaveRestoreOrchestrator.IsRestoring || GameManager.Instance == null) return false;
@@ -658,24 +605,6 @@ namespace AibisDream.UI
_savesDirty = true;
}
private void RestoreDevSave(DevSaveEntry entry)
{
if (entry == null || !entry.IsValid || !CanStartRestore())
{
SetSaveStatus("开发检查点当前不可加载。", true);
return;
}
_isRestorePending = true;
SetSaveStatus($"正在跳转:{entry.Label}...", false);
if (!GameManager.Instance.TryRestoreFile(entry.SnapshotPathWithoutExtension, RestoreOptions.DevJump, OnRestoreCompleted))
{
_isRestorePending = false;
SetSaveStatus("跳转请求被 GameManager 拒绝。", true);
}
_savesDirty = true;
}
private void OnRestoreCompleted(RestoreResult result)
{
_isRestorePending = false;
@@ -699,8 +628,6 @@ namespace AibisDream.UI
copySaveFolderButton?.onClick.AddListener(() => CopyPath(ConstRef.SaveFilePath));
openLogFolderButton?.onClick.AddListener(() => RevealPath(ConstRef.LogFilePath, true));
copyLogFolderButton?.onClick.AddListener(() => CopyPath(ConstRef.LogFilePath));
openDevSaveFolderButton?.onClick.AddListener(() => RevealPath(ConstRef.TestSaveFilePath, false));
copyDevSaveFolderButton?.onClick.AddListener(() => CopyPath(ConstRef.TestSaveFilePath));
openCurrentLogButton?.onClick.AddListener(() => RevealPath(LogKit.CurrentLogFilePath, false));
copyCurrentLogButton?.onClick.AddListener(() => CopyPath(LogKit.CurrentLogFilePath));
@@ -708,7 +635,6 @@ namespace AibisDream.UI
{
if (openSaveFolderButton != null) openSaveFolderButton.interactable = false;
if (openLogFolderButton != null) openLogFolderButton.interactable = false;
if (openDevSaveFolderButton != null) openDevSaveFolderButton.interactable = false;
if (openCurrentLogButton != null) openCurrentLogButton.interactable = false;
}
}
-220
View File
@@ -1,220 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AibisDream.SaveSystem;
using AibisDream.Utility;
using Newtonsoft.Json;
namespace AibisDream
{
/// <summary>读取并严格校验项目内可提交的开发检查点清单。</summary>
public static class DevSaveCatalog
{
public const int CurrentVersion = 1;
private const string CatalogFileName = "catalog.json";
public static string LastError { get; private set; }
public static List<DevSaveSection> Load(string root)
{
LastError = null;
var result = new List<DevSaveSection>();
var catalogPath = Path.Combine(root ?? string.Empty, CatalogFileName);
if (string.IsNullOrWhiteSpace(root) || !File.Exists(catalogPath))
{
LastError = $"找不到测试存档清单:{catalogPath}";
return result;
}
DevSaveCatalogDto catalog;
try
{
catalog = JsonConvert.DeserializeObject<DevSaveCatalogDto>(File.ReadAllText(catalogPath));
}
catch (Exception ex)
{
LastError = $"清单 JSON 损坏:{ex.Message}";
return result;
}
if (catalog == null || catalog.catalogVersion != CurrentVersion)
{
LastError = $"不支持的清单版本:{catalog?.catalogVersion.ToString() ?? "none"}";
return result;
}
foreach (var sectionDto in (catalog.sections ?? new List<DevSaveCatalogSectionDto>())
.OrderBy(section => section.order))
{
if (string.IsNullOrWhiteSpace(sectionDto.id)) continue;
var entries = (sectionDto.entries ?? new List<DevSaveCatalogEntryDto>())
.OrderBy(entry => entry.order)
.Select(entry => BuildEntry(root, sectionDto, entry))
.ToList();
result.Add(new DevSaveSection(
sectionDto.id,
string.IsNullOrWhiteSpace(sectionDto.title) ? sectionDto.id : sectionDto.title,
entries));
}
return result;
}
private static DevSaveEntry BuildEntry(string root, DevSaveCatalogSectionDto section, DevSaveCatalogEntryDto dto)
{
var error = ValidateRelativePath(root, dto.path, out var entryDirectory);
var snapshotPath = error == null
? Path.Combine(entryDirectory, ConstRef.SaveSnapshotFileName)
: string.Empty;
var entry = new DevSaveEntry(
dto.order,
string.IsNullOrWhiteSpace(dto.label) ? dto.anchorNode : dto.label,
dto.anchorNode,
snapshotPath,
section.expectedSceneName,
section.expectedSceneSoName,
section.expectedYarnProjectId,
error);
entry.Validate();
return entry;
}
private static string ValidateRelativePath(string root, string relativePath, out string fullPath)
{
fullPath = null;
if (string.IsNullOrWhiteSpace(relativePath)) return "清单路径为空";
if (Path.IsPathRooted(relativePath)) return "清单路径必须是相对路径";
try
{
var rootFull = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
fullPath = Path.GetFullPath(Path.Combine(root, relativePath.Replace('/', Path.DirectorySeparatorChar)));
if (!fullPath.StartsWith(rootFull, StringComparison.OrdinalIgnoreCase))
return "清单路径越过 DevSaveFiles 根目录";
return null;
}
catch (Exception ex)
{
return $"清单路径无效:{ex.Message}";
}
}
}
public sealed class DevSaveSection
{
public string Id { get; }
public string Title { get; }
public List<DevSaveEntry> Entries { get; }
public DevSaveSection(string id, string title, List<DevSaveEntry> entries)
{
Id = id;
Title = title;
Entries = entries ?? new List<DevSaveEntry>();
}
}
public sealed class DevSaveEntry
{
public int Order { get; }
public string Label { get; }
public string AnchorNode { get; }
public string SnapshotPathWithoutExtension { get; }
public bool IsValid => string.IsNullOrEmpty(Error);
public string Error { get; private set; }
public SaveSnapshot Snapshot { get; private set; }
private readonly string _expectedSceneName;
private readonly string _expectedSceneSoName;
private readonly string _expectedYarnProjectId;
public DevSaveEntry(
int order,
string label,
string anchorNode,
string snapshotPathWithoutExtension,
string expectedSceneName,
string expectedSceneSoName,
string expectedYarnProjectId,
string initialError = null)
{
Order = order;
Label = label ?? string.Empty;
AnchorNode = anchorNode ?? string.Empty;
SnapshotPathWithoutExtension = snapshotPathWithoutExtension;
_expectedSceneName = expectedSceneName;
_expectedSceneSoName = expectedSceneSoName;
_expectedYarnProjectId = expectedYarnProjectId;
Error = initialError;
}
public void Validate()
{
if (!string.IsNullOrEmpty(Error)) return;
if (string.IsNullOrEmpty(SnapshotPathWithoutExtension)
|| !File.Exists(SnapshotPathWithoutExtension + ".json"))
{
Error = "存档文件缺失";
return;
}
try
{
Snapshot = SnapshotPersistence.Load(SnapshotPathWithoutExtension);
}
catch (Exception ex)
{
Error = $"JSON 无法读取:{ex.Message}";
return;
}
if (Snapshot == null || Snapshot.schemaVersion != SaveSnapshotSchema.CurrentVersion)
Error = $"存档版本不兼容:{Snapshot?.schemaVersion.ToString() ?? "none"}";
else if (Snapshot.scene == null || string.IsNullOrWhiteSpace(Snapshot.scene.sceneName))
Error = "存档缺少场景";
else if (Snapshot.anchor == null || string.IsNullOrWhiteSpace(Snapshot.anchor.nodeName))
Error = "存档缺少 Yarn 锚点";
else if (!Matches(_expectedSceneName, Snapshot.scene.sceneName))
Error = $"场景不匹配:{Snapshot.scene.sceneName}";
else if (!Matches(_expectedSceneSoName, Snapshot.anchor.sceneSoName))
Error = $"TalkSceneSO 不匹配:{Snapshot.anchor.sceneSoName}";
else if (!Matches(_expectedYarnProjectId, Snapshot.anchor.yarnProjectId))
Error = $"YarnProject 不匹配:{Snapshot.anchor.yarnProjectId}";
else if (!string.IsNullOrWhiteSpace(AnchorNode)
&& !string.Equals(AnchorNode, Snapshot.anchor.nodeName, StringComparison.Ordinal))
Error = $"节点不匹配:{Snapshot.anchor.nodeName}";
}
private static bool Matches(string expected, string actual) =>
string.IsNullOrWhiteSpace(expected) || string.Equals(expected, actual, StringComparison.Ordinal);
}
[Serializable]
public sealed class DevSaveCatalogDto
{
public int catalogVersion = DevSaveCatalog.CurrentVersion;
public List<DevSaveCatalogSectionDto> sections = new();
}
[Serializable]
public sealed class DevSaveCatalogSectionDto
{
public int order;
public string id;
public string title;
public string expectedSceneName;
public string expectedSceneSoName;
public string expectedYarnProjectId;
public List<DevSaveCatalogEntryDto> entries = new();
}
[Serializable]
public sealed class DevSaveCatalogEntryDto
{
public int order;
public string label;
public string path;
public string anchorNode;
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 260f8ad145646e94d9a1e4c9a935321b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-02 11:56:00",
"sceneName": "Scene/OpenFixScene",
"sceneSoName": "Day0_Prologue",
"yarnProjectId": "FP_Prologue",
"nodeName": "Start",
"schemaVersion": 1,
"gameVersion": "0.5.1.7-20260701-213941",
"thumbnailFile": "thumbnail.png"
}
@@ -1,65 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.7-20260701-213941",
"savedAt": "2026-07-02 11:56:00",
"scene": {
"sceneName": "Scene/OpenFixScene"
},
"anchor": {
"sceneSoName": "Day0_Prologue",
"yarnProjectId": "FP_Prologue",
"nodeName": "Start"
},
"yarnVariables": {
"floats": {},
"strings": {},
"bools": {}
},
"sections": {
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Clinic"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"fix": {
"state": "Dream",
"args": ""
},
"fixPanel": {
"isSystemOn": false,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": true,
"isTaskPanelVisible": false,
"tasks": []
},
"bodyModule": {
"moduleState": "Body",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": false,
"highlightedModules": [],
"alarmedModules": []
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": true
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:00:13",
"sceneName": "Scene/SubWay",
"sceneSoName": "Day1_begin",
"yarnProjectId": "FP_Day1_begin",
"nodeName": "地铁ver5",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:00:13","scene":{"sceneName":"Scene/SubWay"},"anchor":{"sceneSoName":"Day1_begin","yarnProjectId":"FP_Day1_begin","nodeName":"地铁ver5"},"yarnVariables":{"floats":{},"strings":{},"bools":{}},"sections":{"env":{"curTime":"Day","curWeather":"Sunny","isInitialized":true},"actor":{"actors":[{"actorName":"地铁医生","slotName":"左边","actorType":"Anima","stateName":"idle","stateNormalizedTime":1.742072,"alpha":1.0,"isTalking":false},{"actorName":"地铁火山","slotName":"右边","actorType":"Anima","stateName":"idle","stateNormalizedTime":1.742072,"alpha":1.0,"isTalking":false}]},"audio":{"musicPaths":{},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Subway"},"timeline":{"entries":[{"directorName":"地铁","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"subway":{"state":"Subway","args":""},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":false}}}
-11
View File
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:02:25",
"sceneName": "Scene/ClinicOut",
"sceneSoName": "Day1_mid",
"yarnProjectId": "FP_Day1_mid",
"nodeName": "佩佩",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:02:25","scene":{"sceneName":"Scene/ClinicOut"},"anchor":{"sceneSoName":"Day1_mid","yarnProjectId":"FP_Day1_mid","nodeName":"佩佩"},"yarnVariables":{"floats":{},"strings":{},"bools":{}},"sections":{"env":{"curTime":"Day","curWeather":"Sunny","isInitialized":true},"actor":{"actors":[{"actorName":"诊室外佩佩","slotName":"诊室外栏杆","actorType":"Anima","stateName":"HOODBACK","stateNormalizedTime":0.1621352,"alpha":1.0,"isTalking":false}]},"audio":{"musicPaths":{},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Outside"},"timeline":{"entries":[{"directorName":"诊室外头痛","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"诊室外","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"英里初登场","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"医生出地铁","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":true}}}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:02:36",
"sceneName": "Scene/酒吧场景",
"sceneSoName": "Day1_night",
"yarnProjectId": "FP_Day1_night",
"nodeName": "酒吧6",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:02:36","scene":{"sceneName":"Scene/酒吧场景"},"anchor":{"sceneSoName":"Day1_night","yarnProjectId":"FP_Day1_night","nodeName":"酒吧6"},"yarnVariables":{"floats":{},"strings":{},"bools":{}},"sections":{"env":{"curTime":"Evening","curWeather":"Snowy","isInitialized":true},"actor":{"actors":[{"actorName":"酒吧医生","slotName":"吧台左侧","actorType":"Anima","stateName":"Idle","stateNormalizedTime":10.8863344,"alpha":1.0,"isTalking":false},{"actorName":"酒吧调酒妹","slotName":"吧台内","actorType":"Anima","stateName":"1","stateNormalizedTime":2.857307,"alpha":0.0,"isTalking":false},{"actorName":"酒保","slotName":"酒保左侧","actorType":"Anima","stateName":"é\u0085’吧å†\u0085","stateNormalizedTime":6.01455,"alpha":0.0,"isTalking":false}]},"audio":{"musicPaths":{"event:/Music/music_bar":"event:/Music/music_bar"},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Bar"},"timeline":{"entries":[{"directorName":"调酒蒙太奇","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"酒吧","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":false}}}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:02:53",
"sceneName": "Scene/梦境",
"sceneSoName": "Day1_sleep",
"yarnProjectId": "FP_Day1_sleep",
"nodeName": "开场",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:02:53","scene":{"sceneName":"Scene/梦境"},"anchor":{"sceneSoName":"Day1_sleep","yarnProjectId":"FP_Day1_sleep","nodeName":"开场"},"yarnVariables":{"floats":{},"strings":{},"bools":{}},"sections":{"audio":{"musicPaths":{"event:/Amb/amb_beachsample":"event:/Amb/amb_beachsample","event:/Music/music_bar":"event:/Music/music_bar"},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Dream"},"timeline":{"entries":[{"directorName":"姐姐","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"惊醒","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"星空到海浪","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"醒来","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":true}}}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:02:55",
"sceneName": "Scene/梦境",
"sceneSoName": "Day1_sleep",
"yarnProjectId": "FP_Day1_sleep",
"nodeName": "教室苏醒",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:02:55","scene":{"sceneName":"Scene/梦境"},"anchor":{"sceneSoName":"Day1_sleep","yarnProjectId":"FP_Day1_sleep","nodeName":"教室苏醒"},"yarnVariables":{"floats":{},"strings":{},"bools":{}},"sections":{"audio":{"musicPaths":{},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Dream"},"timeline":{"entries":[{"directorName":"姐姐","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"惊醒","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"星空到海浪","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"醒来","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":false}}}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:02:56",
"sceneName": "Scene/梦境",
"sceneSoName": "Day1_sleep",
"yarnProjectId": "FP_Day1_sleep",
"nodeName": "教室",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:02:56","scene":{"sceneName":"Scene/梦境"},"anchor":{"sceneSoName":"Day1_sleep","yarnProjectId":"FP_Day1_sleep","nodeName":"教室"},"yarnVariables":{"floats":{},"strings":{},"bools":{}},"sections":{"audio":{"musicPaths":{"event:/Music/mus_dream1":"event:/Music/mus_dream1"},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Dream"},"timeline":{"entries":[{"directorName":"姐姐","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"惊醒","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"星空到海浪","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"醒来","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":false}}}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:03:12",
"sceneName": "Scene/梦境",
"sceneSoName": "Day1_sleep",
"yarnProjectId": "FP_Day1_sleep",
"nodeName": "走廊",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:03:12","scene":{"sceneName":"Scene/梦境"},"anchor":{"sceneSoName":"Day1_sleep","yarnProjectId":"FP_Day1_sleep","nodeName":"走廊"},"yarnVariables":{"floats":{"$gameStage":2.0,"$scj":1.0,"$ppStage":2.0},"strings":{},"bools":{"$Yarn.Internal.Once.507f30d8":true,"$Yarn.Internal.Once.4bd3a162":true,"$Yarn.Internal.Once.90392df9":true}},"sections":{"audio":{"musicPaths":{"event:/Music/mus_dream1":"event:/Music/mus_dream1"},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Dream"},"timeline":{"entries":[{"directorName":"姐姐","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"惊醒","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"星空到海浪","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"醒来","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":false}}}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:03:15",
"sceneName": "Scene/梦境",
"sceneSoName": "Day1_sleep",
"yarnProjectId": "FP_Day1_sleep",
"nodeName": "楼梯间",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:03:15","scene":{"sceneName":"Scene/梦境"},"anchor":{"sceneSoName":"Day1_sleep","yarnProjectId":"FP_Day1_sleep","nodeName":"楼梯间"},"yarnVariables":{"floats":{"$gameStage":2.0,"$scj":1.0,"$ppStage":2.0},"strings":{},"bools":{"$Yarn.Internal.Once.507f30d8":true,"$Yarn.Internal.Once.4bd3a162":true,"$Yarn.Internal.Once.90392df9":true}},"sections":{"audio":{"musicPaths":{"event:/Music/mus_dream1":"event:/Music/mus_dream1"},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Dream"},"timeline":{"entries":[{"directorName":"姐姐","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"惊醒","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"星空到海浪","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"醒来","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":false}}}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:03:18",
"sceneName": "Scene/梦境",
"sceneSoName": "Day1_sleep",
"yarnProjectId": "FP_Day1_sleep",
"nodeName": "天台",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:03:18","scene":{"sceneName":"Scene/梦境"},"anchor":{"sceneSoName":"Day1_sleep","yarnProjectId":"FP_Day1_sleep","nodeName":"天台"},"yarnVariables":{"floats":{"$gameStage":2.0,"$scj":1.0,"$ppStage":2.0},"strings":{},"bools":{"$Yarn.Internal.Once.507f30d8":true,"$Yarn.Internal.Once.4bd3a162":true,"$Yarn.Internal.Once.90392df9":true}},"sections":{"audio":{"musicPaths":{"event:/Music/mus_dream1":"event:/Music/mus_dream1"},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Dream"},"timeline":{"entries":[{"directorName":"姐姐","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"惊醒","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"星空到海浪","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"醒来","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":false}}}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:03:24",
"sceneName": "Scene/梦境",
"sceneSoName": "Day1_sleep",
"yarnProjectId": "FP_Day1_sleep",
"nodeName": "天文台",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:03:24","scene":{"sceneName":"Scene/梦境"},"anchor":{"sceneSoName":"Day1_sleep","yarnProjectId":"FP_Day1_sleep","nodeName":"天文台"},"yarnVariables":{"floats":{"$gameStage":3.0,"$scj":1.0,"$ppStage":2.0},"strings":{},"bools":{"$Yarn.Internal.Once.d66d59be":true,"$Yarn.Internal.Once.507f30d8":true,"$Yarn.Internal.Once.4bd3a162":true,"$Yarn.Internal.Once.90392df9":true}},"sections":{"audio":{"musicPaths":{"event:/Music/mus_dream1":"event:/Music/mus_dream1"},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Dream"},"timeline":{"entries":[{"directorName":"姐姐","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"惊醒","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"星空到海浪","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"醒来","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":false}}}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:03:26",
"sceneName": "Scene/梦境",
"sceneSoName": "Day1_sleep",
"yarnProjectId": "FP_Day1_sleep",
"nodeName": "海边",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:03:26","scene":{"sceneName":"Scene/梦境"},"anchor":{"sceneSoName":"Day1_sleep","yarnProjectId":"FP_Day1_sleep","nodeName":"海边"},"yarnVariables":{"floats":{"$gameStage":3.0,"$scj":1.0,"$ppStage":2.0},"strings":{},"bools":{"$Yarn.Internal.Once.d66d59be":true,"$Yarn.Internal.Once.507f30d8":true,"$Yarn.Internal.Once.4bd3a162":true,"$Yarn.Internal.Once.90392df9":true}},"sections":{"audio":{"musicPaths":{},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Dream"},"timeline":{"entries":[{"directorName":"姐姐","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"惊醒","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"星空到海浪","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"醒来","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":false}}}
-11
View File
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-13 13:53:21",
"sceneName": "Scene/ClinicOut",
"sceneSoName": "Day2_mid",
"yarnProjectId": "FP_Day2_mid",
"nodeName": "Start",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-13 13:53:21","scene":{"sceneName":"Scene/ClinicOut"},"anchor":{"sceneSoName":"Day2_mid","yarnProjectId":"FP_Day2_mid","nodeName":"Start"},"yarnVariables":{"floats":{},"strings":{},"bools":{}},"sections":{"env":{"curTime":"Day","curWeather":"Sunny","isInitialized":true},"actor":{"actors":[{"actorName":"诊室外火山","slotName":"诊室外栏杆","actorType":"Anima","stateName":"背影IDLE","stateNormalizedTime":0.2660912,"alpha":1.0,"isTalking":false}]},"audio":{"musicPaths":{},"sfxPaths":{},"ambState":"Outside"},"timeline":{"entries":[{"directorName":"诊室外","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"医生出地铁","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false},{"directorName":"诊室外头痛","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":true}}}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:31:23",
"sceneName": "Scene/ClinicOut",
"sceneSoName": "Day2_night",
"yarnProjectId": "FP_Day2_night",
"nodeName": "Start",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,63 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:31:23",
"scene": {
"sceneName": "Scene/ClinicOut"
},
"anchor": {
"sceneSoName": "Day2_night",
"yarnProjectId": "FP_Day2_night",
"nodeName": "Start"
},
"yarnVariables": {
"floats": {},
"strings": {},
"bools": {
"$global_isPeipeiBE": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": []
},
"audio": {
"musicPaths": {
"music": "event:/Music/mus_peipei"
},
"sfxPaths": {},
"ambState": "Outside"
},
"timeline": {
"entries": [
{
"directorName": "诊室外",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "医生出地铁",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "诊室外头痛",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": true
}
}
}
-11
View File
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:31:23",
"sceneName": "Scene/ClinicOut",
"sceneSoName": "Day2_night",
"yarnProjectId": "FP_Day2_night",
"nodeName": "BE",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,63 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:31:23",
"scene": {
"sceneName": "Scene/ClinicOut"
},
"anchor": {
"sceneSoName": "Day2_night",
"yarnProjectId": "FP_Day2_night",
"nodeName": "BE"
},
"yarnVariables": {
"floats": {},
"strings": {},
"bools": {
"$global_isPeipeiBE": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": []
},
"audio": {
"musicPaths": {
"music": "event:/Music/mus_peipei"
},
"sfxPaths": {},
"ambState": "Outside"
},
"timeline": {
"entries": [
{
"directorName": "诊室外",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "医生出地铁",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "诊室外头痛",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": true
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-07-15 20:03:30",
"sceneName": "Scene/SubWay",
"sceneSoName": "GeneralEnd",
"yarnProjectId": "Test_GeneralEnd",
"nodeName": "Start",
"schemaVersion": 1,
"gameVersion": "0.5.2.1-20260705-010933",
"thumbnailFile": "thumbnail.png"
}
@@ -1 +0,0 @@
{"schemaVersion":1,"gameVersion":"0.5.2.1-20260705-010933","savedAt":"2026-07-15 20:03:30","scene":{"sceneName":"Scene/SubWay"},"anchor":{"sceneSoName":"GeneralEnd","yarnProjectId":"Test_GeneralEnd","nodeName":"Start"},"yarnVariables":{"floats":{},"strings":{},"bools":{}},"sections":{"env":{"curTime":"Night","curWeather":"Snowy","isInitialized":true},"actor":{"actors":[{"actorName":"地铁医生","slotName":"左边","actorType":"Anima","stateName":"idle","stateNormalizedTime":0.050316032,"alpha":1.0,"isTalking":false}]},"audio":{"musicPaths":{"event:/Music/music_ending_temp":"event:/Music/music_ending_temp"},"sfxPaths":{"event:/Amb/amb":"event:/Amb/amb"},"ambState":"Subway"},"timeline":{"entries":[{"directorName":"地铁","isActive":true,"phase":"Stopped","hasEverAppliedPlayback":false}]},"subway":{"state":"Subway","args":""},"screen":{"saturationWeight":0.0,"isFadeScreenCovered":true}}}
-11
View File
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:17:32",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "开场对话",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,95 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:17:32",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "开场对话"
},
"yarnVariables": {
"floats": {},
"strings": {},
"bools": {}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Clinic"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "Default",
"args": ""
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": true,
"isTaskPanelVisible": false,
"tasks": []
},
"bodyModule": {
"moduleState": "Body",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": false,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": true
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:17:37",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进门后",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,95 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:17:37",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进门后"
},
"yarnVariables": {
"floats": {},
"strings": {},
"bools": {}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Clinic"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "Default",
"args": ""
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": true,
"isTaskPanelVisible": false,
"tasks": []
},
"bodyModule": {
"moduleState": "Body",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": false,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:17:45",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "第一次身体检查_检查UF",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,114 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:17:45",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "第一次身体检查_检查UF"
},
"yarnVariables": {
"floats": {},
"strings": {
"$gameStage": "UF检查"
},
"bools": {
"$global.IsPlugHighLight": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Sideeye",
"stateNormalizedTime": 10.7053051,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"pluggedModuleName": "UF",
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:01dade5",
"text": "● 定位佩佩的UF模块"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:17:47",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "UF深入检查",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,117 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:17:47",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "UF深入检查"
},
"yarnVariables": {
"floats": {
"$PlugCount": 1.0
},
"strings": {
"$gameStage": "UF检查"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Sideeye",
"stateNormalizedTime": 19.032732,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"pluggedModuleName": "UF",
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:04c146f",
"text": "● 检查UF模块"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:17:51",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "第一次身体检查_检查视觉",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,120 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:17:51",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "第一次身体检查_检查视觉"
},
"yarnVariables": {
"floats": {
"$PlugCount": 1.0,
"$Yarn.Internal.Visiting.UF深入检查": 1.0
},
"strings": {
"$gameStage": "视觉检查"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Sideeye",
"stateNormalizedTime": 33.75063,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"pluggedModuleName": "Eye",
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:01fada9",
"text": "● 在视觉模块寻找线索"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [
"Eye"
],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:18:06",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入记忆检查",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,154 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:18:06",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入记忆检查"
},
"yarnVariables": {
"floats": {
"$PlugCount": 2.0,
"$Yarn.Internal.Visiting.UF深入检查": 1.0,
"$Yarn.Internal.Visiting.IntoEyeView": 4.0,
"$Yarn.Internal.Visiting.FirstDialogue": 1.0,
"$eyeTargetTriggerCount": 3.0,
"$Yarn.Internal.Visiting.佩佩告诉你可以想象颜色": 1.0,
"$Yarn.Internal.Visiting.Eye_plant": 1.0,
"$Yarn.Internal.Visiting.Eye_poster": 1.0
},
"strings": {
"$gameStage": "记忆检查",
"$currentEyeTarget": "poster"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true,
"$eyeChecked": true,
"$eyenocolorKnown": true,
"$peipeiReadyToShow": true,
"$eyecolorKnown": true,
"$isFinishedEyeCheck": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Sideeye",
"stateNormalizedTime": 92.29114,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": [
{
"Category": "EyeView",
"ClueName": "l10n.天空",
"MemoryIndex": "liuying1-3",
"used": false
},
{
"Category": "EyeView",
"ClueName": "l10n.植物",
"MemoryIndex": "liuying1-2",
"used": false
},
{
"Category": "EyeView",
"ClueName": "l10n.海报",
"MemoryIndex": "liuying1-1",
"used": false
}
]
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:01fada9",
"text": "● 在视觉模块寻找线索"
},
{
"lineId": "line:043e26d",
"text": "● 检查“色彩数据”记忆"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [
"Memory"
],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CanImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:18:07",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "检查记忆",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,156 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:18:07",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "检查记忆"
},
"yarnVariables": {
"floats": {
"$PlugCount": 2.0,
"$Yarn.Internal.Visiting.UF深入检查": 1.0,
"$Yarn.Internal.Visiting.IntoEyeView": 4.0,
"$Yarn.Internal.Visiting.FirstDialogue": 1.0,
"$eyeTargetTriggerCount": 3.0,
"$Yarn.Internal.Visiting.佩佩告诉你可以想象颜色": 1.0,
"$Yarn.Internal.Visiting.Eye_plant": 1.0,
"$Yarn.Internal.Visiting.Eye_poster": 1.0,
"$Yarn.Internal.Visiting.MemoryPlugIn": 1.0
},
"strings": {
"$gameStage": "记忆检查",
"$currentEyeTarget": "poster"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true,
"$eyeChecked": true,
"$eyenocolorKnown": true,
"$peipeiReadyToShow": true,
"$eyecolorKnown": true,
"$isFinishedEyeCheck": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Sideeye",
"stateNormalizedTime": 96.79041,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": [
{
"Category": "EyeView",
"ClueName": "l10n.天空",
"MemoryIndex": "liuying1-3",
"used": false
},
{
"Category": "EyeView",
"ClueName": "l10n.植物",
"MemoryIndex": "liuying1-2",
"used": false
},
{
"Category": "EyeView",
"ClueName": "l10n.海报",
"MemoryIndex": "liuying1-1",
"used": false
}
]
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"pluggedModuleName": "Memory",
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:01fada9",
"text": "● 在视觉模块寻找线索"
},
{
"lineId": "line:043e26d",
"text": "● 检查“色彩数据”记忆"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [
"Memory"
],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CanImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:19:47",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入分析",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,140 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:19:47",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入分析"
},
"yarnVariables": {
"floats": {
"$PlugCount": 2.0,
"$Yarn.Internal.Visiting.UF深入检查": 1.0,
"$Yarn.Internal.Visiting.IntoEyeView": 4.0,
"$Yarn.Internal.Visiting.FirstDialogue": 1.0,
"$eyeTargetTriggerCount": 3.0,
"$Yarn.Internal.Visiting.佩佩告诉你可以想象颜色": 1.0,
"$Yarn.Internal.Visiting.Eye_plant": 1.0,
"$Yarn.Internal.Visiting.Eye_poster": 1.0,
"$Yarn.Internal.Visiting.MemoryPlugIn": 1.0,
"$memoryCount": 3.0,
"$Yarn.Internal.Visiting.天空": 1.0,
"$Yarn.Internal.Visiting.植物": 1.0,
"$Yarn.Internal.Visiting.海报": 1.0,
"$ufDiscussCount": 3.0,
"$eyeDiscussCount": 2.0
},
"strings": {
"$gameStage": "分析",
"$currentEyeTarget": "poster"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true,
"$eyeChecked": true,
"$eyenocolorKnown": true,
"$peipeiReadyToShow": true,
"$eyecolorKnown": true,
"$isFinishedEyeCheck": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Sideeye",
"stateNormalizedTime": 481.14978,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": ""
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:01fada9",
"text": "● 在视觉模块寻找线索"
},
{
"lineId": "line:02a3a37",
"text": "● 分析病理与治疗方案"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CanImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:19:53",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入术前谈话",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,138 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:19:53",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入术前谈话"
},
"yarnVariables": {
"floats": {
"$PlugCount": 2.0,
"$Yarn.Internal.Visiting.UF深入检查": 1.0,
"$Yarn.Internal.Visiting.IntoEyeView": 4.0,
"$Yarn.Internal.Visiting.FirstDialogue": 1.0,
"$eyeTargetTriggerCount": 3.0,
"$Yarn.Internal.Visiting.佩佩告诉你可以想象颜色": 1.0,
"$Yarn.Internal.Visiting.Eye_plant": 1.0,
"$Yarn.Internal.Visiting.Eye_poster": 1.0,
"$Yarn.Internal.Visiting.MemoryPlugIn": 1.0,
"$memoryCount": 3.0,
"$Yarn.Internal.Visiting.天空": 1.0,
"$Yarn.Internal.Visiting.植物": 1.0,
"$Yarn.Internal.Visiting.海报": 1.0,
"$ufDiscussCount": 3.0,
"$eyeDiscussCount": 2.0
},
"strings": {
"$gameStage": "术前谈话",
"$currentEyeTarget": "poster"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true,
"$eyeChecked": true,
"$eyenocolorKnown": true,
"$peipeiReadyToShow": true,
"$eyecolorKnown": true,
"$isFinishedEyeCheck": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "1I",
"stateNormalizedTime": 0.0,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {
"event:/Music/music_peipei_1": "event:/Music/music_peipei_1"
},
"sfxPaths": {},
"ambState": "Clinic"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "Clinic",
"args": ""
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:01fada9",
"text": "● 在视觉模块寻找线索"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CanImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": true
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:19:57",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入手术",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,138 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:19:57",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入手术"
},
"yarnVariables": {
"floats": {
"$PlugCount": 2.0,
"$Yarn.Internal.Visiting.UF深入检查": 1.0,
"$Yarn.Internal.Visiting.IntoEyeView": 4.0,
"$Yarn.Internal.Visiting.FirstDialogue": 1.0,
"$eyeTargetTriggerCount": 3.0,
"$Yarn.Internal.Visiting.佩佩告诉你可以想象颜色": 1.0,
"$Yarn.Internal.Visiting.Eye_plant": 1.0,
"$Yarn.Internal.Visiting.Eye_poster": 1.0,
"$Yarn.Internal.Visiting.MemoryPlugIn": 1.0,
"$memoryCount": 3.0,
"$Yarn.Internal.Visiting.天空": 1.0,
"$Yarn.Internal.Visiting.植物": 1.0,
"$Yarn.Internal.Visiting.海报": 1.0,
"$ufDiscussCount": 3.0,
"$eyeDiscussCount": 2.0
},
"strings": {
"$gameStage": "手术",
"$currentEyeTarget": "poster"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true,
"$eyeChecked": true,
"$eyenocolorKnown": true,
"$peipeiReadyToShow": true,
"$eyecolorKnown": true,
"$isFinishedEyeCheck": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2",
"stateNormalizedTime": 2.13779116,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {
"event:/Music/music_peipei_1": "event:/Music/music_peipei_1"
},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:0deda99",
"text": "● 执行视觉-记忆桥截断术"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CanImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:20:00",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "CutLine",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,136 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:20:00",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "CutLine"
},
"yarnVariables": {
"floats": {
"$PlugCount": 2.0,
"$Yarn.Internal.Visiting.UF深入检查": 1.0,
"$Yarn.Internal.Visiting.IntoEyeView": 4.0,
"$Yarn.Internal.Visiting.FirstDialogue": 1.0,
"$eyeTargetTriggerCount": 3.0,
"$Yarn.Internal.Visiting.佩佩告诉你可以想象颜色": 1.0,
"$Yarn.Internal.Visiting.Eye_plant": 1.0,
"$Yarn.Internal.Visiting.Eye_poster": 1.0,
"$Yarn.Internal.Visiting.MemoryPlugIn": 1.0,
"$memoryCount": 3.0,
"$Yarn.Internal.Visiting.天空": 1.0,
"$Yarn.Internal.Visiting.植物": 1.0,
"$Yarn.Internal.Visiting.海报": 1.0,
"$ufDiscussCount": 3.0,
"$eyeDiscussCount": 2.0
},
"strings": {
"$gameStage": "手术",
"$currentEyeTarget": "poster"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true,
"$eyeChecked": true,
"$eyenocolorKnown": true,
"$peipeiReadyToShow": true,
"$eyecolorKnown": true,
"$isFinishedEyeCheck": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2",
"stateNormalizedTime": 11.7810183,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:0deda99",
"text": "● 执行视觉-记忆桥截断术"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CanImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:20:00",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入复查",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,131 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:20:00",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入复查"
},
"yarnVariables": {
"floats": {
"$PlugCount": 2.0,
"$Yarn.Internal.Visiting.UF深入检查": 1.0,
"$Yarn.Internal.Visiting.IntoEyeView": 4.0,
"$Yarn.Internal.Visiting.FirstDialogue": 1.0,
"$eyeTargetTriggerCount": 0.0,
"$Yarn.Internal.Visiting.佩佩告诉你可以想象颜色": 1.0,
"$Yarn.Internal.Visiting.Eye_plant": 1.0,
"$Yarn.Internal.Visiting.Eye_poster": 1.0,
"$Yarn.Internal.Visiting.MemoryPlugIn": 1.0,
"$memoryCount": 3.0,
"$Yarn.Internal.Visiting.天空": 1.0,
"$Yarn.Internal.Visiting.植物": 1.0,
"$Yarn.Internal.Visiting.海报": 1.0,
"$ufDiscussCount": 3.0,
"$eyeDiscussCount": 2.0
},
"strings": {
"$gameStage": "复查",
"$currentEyeTarget": "poster"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true,
"$eyeChecked": true,
"$eyenocolorKnown": true,
"$peipeiReadyToShow": true,
"$eyecolorKnown": true,
"$isFinishedEyeCheck": false
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2",
"stateNormalizedTime": 12.9643087,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"isTaskPanelVisible": true,
"tasks": []
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:20:02",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "复查_检查视觉",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,139 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:20:02",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "复查_检查视觉"
},
"yarnVariables": {
"floats": {
"$PlugCount": 2.0,
"$Yarn.Internal.Visiting.UF深入检查": 1.0,
"$Yarn.Internal.Visiting.IntoEyeView": 4.0,
"$Yarn.Internal.Visiting.FirstDialogue": 1.0,
"$eyeTargetTriggerCount": 0.0,
"$Yarn.Internal.Visiting.佩佩告诉你可以想象颜色": 1.0,
"$Yarn.Internal.Visiting.Eye_plant": 1.0,
"$Yarn.Internal.Visiting.Eye_poster": 1.0,
"$Yarn.Internal.Visiting.MemoryPlugIn": 1.0,
"$memoryCount": 3.0,
"$Yarn.Internal.Visiting.天空": 1.0,
"$Yarn.Internal.Visiting.植物": 1.0,
"$Yarn.Internal.Visiting.海报": 1.0,
"$ufDiscussCount": 3.0,
"$eyeDiscussCount": 2.0
},
"strings": {
"$gameStage": "复查",
"$currentEyeTarget": "poster"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true,
"$eyeChecked": false,
"$eyenocolorKnown": true,
"$peipeiReadyToShow": true,
"$eyecolorKnown": true,
"$isFinishedEyeCheck": false
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2",
"stateNormalizedTime": 19.3081741,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"pluggedModuleName": "Eye",
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:037c944",
"text": "● 检查视觉模块"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [
"Eye"
],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-29 19:20:10",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入结束对话",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,137 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-29 19:20:10",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day1_FIRST_PP1",
"yarnProjectId": "FP_Peipei1",
"nodeName": "进入结束对话"
},
"yarnVariables": {
"floats": {
"$PlugCount": 2.0,
"$Yarn.Internal.Visiting.UF深入检查": 1.0,
"$Yarn.Internal.Visiting.IntoEyeView": 7.0,
"$Yarn.Internal.Visiting.FirstDialogue": 1.0,
"$eyeTargetTriggerCount": 2.0,
"$Yarn.Internal.Visiting.佩佩告诉你可以想象颜色": 1.0,
"$Yarn.Internal.Visiting.Eye_plant": 1.0,
"$Yarn.Internal.Visiting.Eye_poster": 1.0,
"$Yarn.Internal.Visiting.MemoryPlugIn": 1.0,
"$memoryCount": 3.0,
"$Yarn.Internal.Visiting.天空": 1.0,
"$Yarn.Internal.Visiting.植物": 1.0,
"$Yarn.Internal.Visiting.海报": 1.0,
"$ufDiscussCount": 3.0,
"$eyeDiscussCount": 2.0,
"$Yarn.Internal.Visiting.Eye_posterAfterSurgery": 1.0,
"$Yarn.Internal.Visiting.Eye_plantAfterSurgery": 1.0
},
"strings": {
"$gameStage": "结束对话",
"$currentEyeTarget": "plant"
},
"bools": {
"$global.IsPlugHighLight": true,
"$UfChecked": true,
"$eyeChecked": true,
"$eyenocolorKnown": true,
"$peipeiReadyToShow": true,
"$eyecolorKnown": true,
"$isFinishedEyeCheck": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "窗外佩佩",
"slotName": "窗外",
"actorType": "Sprite",
"stateName": "Idle",
"stateNormalizedTime": 0.0,
"alpha": 0.0,
"isTalking": false
},
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2H",
"stateNormalizedTime": 1.22089159,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {
"event:/Amb/amb_beachsample": "event:/Amb/amb_beachsample"
},
"sfxPaths": {},
"ambState": "Clinic"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "Clinic",
"args": ""
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"isTaskPanelVisible": true,
"tasks": []
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": [
"Eye"
]
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
-11
View File
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:29:06",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "Start",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,85 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:29:06",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "Start"
},
"yarnVariables": {
"floats": {},
"strings": {},
"bools": {}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": []
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Clinic"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "Default",
"args": ""
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": true,
"isTaskPanelVisible": false,
"tasks": []
},
"bodyModule": {
"moduleState": "Body",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": false,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": true
}
}
}
-11
View File
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:29:06",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入佩佩开场对话",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,85 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:29:06",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入佩佩开场对话"
},
"yarnVariables": {
"floats": {},
"strings": {},
"bools": {}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": []
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Clinic"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "Default",
"args": ""
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": true,
"isTaskPanelVisible": false,
"tasks": []
},
"bodyModule": {
"moduleState": "Body",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": false,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": true
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:29:15",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入第一次发热",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,97 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:29:15",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入第一次发热"
},
"yarnVariables": {
"floats": {},
"strings": {
"$gameStage": "第一次发热"
},
"bools": {}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Pouting",
"stateNormalizedTime": 25.1401443,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Clinic"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtEnd",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "Default",
"args": ""
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": true,
"isTaskPanelVisible": false,
"tasks": []
},
"bodyModule": {
"moduleState": "Body",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": false,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:29:18",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "系统发热",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,107 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:29:18",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "系统发热"
},
"yarnVariables": {
"floats": {},
"strings": {
"$gameStage": "第一次发热"
},
"bools": {
"$global.IsPlugHighLight": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Pouting",
"stateNormalizedTime": 36.1157532,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"pluggedModuleName": "UF",
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:06d0c20",
"text": "● 检查UF模块"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [
"UF"
],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:29:21",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入身体检查",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,110 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:29:21",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入身体检查"
},
"yarnVariables": {
"floats": {
"$feverMessageSeen": 0.0,
"$feverCount": 1.0
},
"strings": {
"$gameStage": "身体检查"
},
"bools": {
"$global.IsPlugHighLight": true,
"$finishFirstCoolDown": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Pouting",
"stateNormalizedTime": 49.9799,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:06d0c20",
"text": "● 检查UF模块"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [
"UF"
],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:29:23",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "检查UF_不可检查",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,111 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:29:23",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "检查UF_不可检查"
},
"yarnVariables": {
"floats": {
"$feverMessageSeen": 0.0,
"$feverCount": 1.0
},
"strings": {
"$gameStage": "身体检查"
},
"bools": {
"$global.IsPlugHighLight": true,
"$finishFirstCoolDown": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Pouting",
"stateNormalizedTime": 58.4737473,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"pluggedModuleName": "UF",
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:06d0c20",
"text": "● 检查UF模块"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [
"UF"
],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:29:28",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入UF拆壳",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,112 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:29:28",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入UF拆壳"
},
"yarnVariables": {
"floats": {
"$feverMessageSeen": 0.0,
"$feverCount": 1.0,
"$PlugCount": 1.0,
"$Yarn.Internal.Visiting.UF深入失败": 1.0
},
"strings": {
"$gameStage": "UF拆壳"
},
"bools": {
"$global.IsPlugHighLight": true,
"$finishFirstCoolDown": true,
"$UfChecked": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Pouting",
"stateNormalizedTime": 74.33931,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"pluggedModuleName": "UF",
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:073a567",
"text": "● 找到影响UF的“未知来源”"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": false,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:29:31",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入UF检查",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,115 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:29:31",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入UF检查"
},
"yarnVariables": {
"floats": {
"$feverMessageSeen": 0.0,
"$feverCount": 1.0,
"$PlugCount": 1.0,
"$Yarn.Internal.Visiting.UF深入失败": 1.0
},
"strings": {
"$gameStage": "UF检查"
},
"bools": {
"$global.IsPlugHighLight": true,
"$finishFirstCoolDown": true,
"$UfChecked": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Pouting",
"stateNormalizedTime": 88.156395,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "Body"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "BodyModule",
"args": "Head"
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:073a567",
"text": "● 找到影响UF的“未知来源”"
},
{
"lineId": "line:0a431e3",
"text": "● 深入检查UF模块"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": true,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:29:38",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "IntoEyeView",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}
@@ -1,116 +0,0 @@
{
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"savedAt": "2026-06-30 14:29:38",
"scene": {
"sceneName": "Scene/PeipeiFixScene"
},
"anchor": {
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "IntoEyeView"
},
"yarnVariables": {
"floats": {
"$feverMessageSeen": 0.0,
"$feverCount": 1.0,
"$PlugCount": 1.0,
"$Yarn.Internal.Visiting.UF深入失败": 1.0
},
"strings": {
"$gameStage": "UF检查",
"$currentEyeTarget": "plant"
},
"bools": {
"$global.IsPlugHighLight": true,
"$finishFirstCoolDown": true,
"$UfChecked": true
}
},
"sections": {
"env": {
"curTime": "Day",
"curWeather": "Sunny",
"isInitialized": true
},
"actor": {
"actors": [
{
"actorName": "佩佩",
"slotName": "clinic",
"actorType": "Anima",
"stateName": "2Pouting",
"stateNormalizedTime": 115.150826,
"alpha": 1.0,
"isTalking": false
}
]
},
"audio": {
"musicPaths": {},
"sfxPaths": {},
"ambState": "UF"
},
"timeline": {
"entries": [
{
"directorName": "引擎仓面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
},
{
"directorName": "进入维修间",
"isActive": true,
"phase": "AtStart",
"hasEverAppliedPlayback": true
},
{
"directorName": "插线面板",
"isActive": true,
"phase": "Stopped",
"hasEverAppliedPlayback": false
}
]
},
"punchTape": {
"favorites": []
},
"fix": {
"state": "UF",
"args": ""
},
"fixPanel": {
"isSystemOn": true,
"currentRepairSystemType": "BodyModuleSystem",
"isCableRetracted": false,
"isTaskPanelVisible": true,
"tasks": [
{
"lineId": "line:073a567",
"text": "● 找到影响UF的“未知来源”"
},
{
"lineId": "line:0a431e3",
"text": "● 深入检查UF模块"
}
]
},
"bodyModule": {
"moduleState": "Head",
"isUFCoverRemoved": true,
"isUFRemoved": false,
"isColorRemoved": false,
"isChipRemoved": true,
"highlightedModules": [],
"alarmedModules": []
},
"eye": {
"eyeColorState": "CannotImagineColor"
},
"screen": {
"saturationWeight": 0.0,
"isFadeScreenCovered": false
}
}
}
@@ -1,11 +0,0 @@
{
"slotIndex": -1,
"savedAt": "2026-06-30 14:29:56",
"sceneName": "Scene/PeipeiFixScene",
"sceneSoName": "Day2_NEXT_PP2",
"yarnProjectId": "FP_Peipei2",
"nodeName": "进入分析",
"schemaVersion": 1,
"gameVersion": "0.5.1.1-20260430-002845",
"thumbnailFile": "thumbnail.png"
}

Some files were not shown because too many files have changed in this diff Show More