diff --git a/Assets/Editor/SaveSystemValidation/DevSavePromoteWindow.cs b/Assets/Editor/SaveSystemValidation/DevSavePromoteWindow.cs index c7a6b9d94..4499f3743 100644 --- a/Assets/Editor/SaveSystemValidation/DevSavePromoteWindow.cs +++ b/Assets/Editor/SaveSystemValidation/DevSavePromoteWindow.cs @@ -288,7 +288,8 @@ namespace AibisDream.EditorTools section.entries = HuoshanCheckpoints.Select(checkpoint => new CatalogEntryDto { label = checkpoint.Label, - path = $"{HuoshanSectionId}/{checkpoint.FolderName}" + path = $"{HuoshanSectionId}/{checkpoint.FolderName}", + anchorNode = checkpoint.NodeName }).ToList(); } @@ -370,6 +371,7 @@ namespace AibisDream.EditorTools { public string label; public string path; + public string anchorNode; } } } diff --git a/Assets/Scripts/Utility/DevSaveJumpTool.cs b/Assets/Scripts/Utility/DevSaveJumpTool.cs index 3e4864298..a5cc38d80 100644 --- a/Assets/Scripts/Utility/DevSaveJumpTool.cs +++ b/Assets/Scripts/Utility/DevSaveJumpTool.cs @@ -131,7 +131,7 @@ namespace AibisDream { GUI.Label( new Rect(0f, y, MenuWidth - 32f, RowHeight), - "(empty — promote saves first)", + "(empty — run Test Mode or promote saves)", statusStyle); y += RowHeight; } @@ -442,6 +442,11 @@ namespace AibisDream if (explicitEntries != null && explicitEntries.Count > 0 && root != null) { + var latestTestArchives = BuildLatestTestArchiveIndex( + expectedSceneName, + expectedYarnProjectId, + allowedNodes); + foreach (var entryDto in explicitEntries) { if (string.IsNullOrWhiteSpace(entryDto.path)) @@ -462,6 +467,26 @@ namespace AibisDream allowedNodes); if (!entry.TryLoadSnapshot(out _, out var error)) { + if (error == "file missing" + && !string.IsNullOrWhiteSpace(entryDto.anchorNode) + && latestTestArchives.TryGetValue(entryDto.anchorNode, out var archiveSnapshot)) + { + entry = new DevSaveEntry( + label: string.IsNullOrWhiteSpace(entryDto.label) + ? entryDto.anchorNode + : entryDto.label, + snapshotPathWithoutExtension: archiveSnapshot, + expectedSceneName, + expectedYarnProjectId, + allowedNodes); + + if (entry.TryLoadSnapshot(out _, out _)) + { + entries.Add(entry); + continue; + } + } + Debug.LogWarning($"[DevSaveJumpTool] 跳过无效存档 {entryDto.path}: {error}"); continue; } @@ -494,6 +519,44 @@ namespace AibisDream return new DevSaveSection(id, title, entries); } + private static Dictionary BuildLatestTestArchiveIndex( + string expectedSceneName, + string expectedYarnProjectId, + List allowedNodes) + { + var result = new Dictionary(StringComparer.Ordinal); + var savedAtByNode = new Dictionary(StringComparer.Ordinal); + var archiveRoot = ConstRef.TestAutoSaveArchivePath; + if (!Directory.Exists(archiveRoot) || allowedNodes == null || allowedNodes.Count == 0) + return result; + + foreach (var archiveDir in Directory.GetDirectories(archiveRoot)) + { + var snapshotPath = Path.Combine(archiveDir, ConstRef.SaveSnapshotFileName); + var candidate = new DevSaveEntry( + string.Empty, + snapshotPath, + expectedSceneName, + expectedYarnProjectId, + allowedNodes); + if (!candidate.TryLoadSnapshot(out var snapshot, out _)) + continue; + + var nodeName = snapshot.anchor.nodeName; + var savedAt = snapshot.savedAt ?? string.Empty; + if (savedAtByNode.TryGetValue(nodeName, out var currentSavedAt) + && string.CompareOrdinal(savedAt, currentSavedAt) <= 0) + { + continue; + } + + savedAtByNode[nodeName] = savedAt; + result[nodeName] = snapshotPath; + } + + return result; + } + private static string FormatFolderLabel(string folderName) { if (string.IsNullOrEmpty(folderName)) @@ -524,6 +587,7 @@ namespace AibisDream { public string label; public string path; + public string anchorNode; } } diff --git a/Assets/StreamingAssets/TestSaveFiles/catalog.json b/Assets/StreamingAssets/TestSaveFiles/catalog.json index cd7d5af89..66103121f 100644 --- a/Assets/StreamingAssets/TestSaveFiles/catalog.json +++ b/Assets/StreamingAssets/TestSaveFiles/catalog.json @@ -26,35 +26,43 @@ "entries": [ { "label": "Stage1 开场", - "path": "Huoshan1/01_Stage1" + "path": "Huoshan1/01_Stage1", + "anchorNode": "开头对话" }, { "label": "Stage2 初检", - "path": "Huoshan1/02_Stage2" + "path": "Huoshan1/02_Stage2", + "anchorNode": "Stage2就绪" }, { "label": "Stage3 滤波器", - "path": "Huoshan1/03_Stage3" + "path": "Huoshan1/03_Stage3", + "anchorNode": "Stage3就绪" }, { "label": "Stage4 表达深入", - "path": "Huoshan1/04_Stage4" + "path": "Huoshan1/04_Stage4", + "anchorNode": "Stage4就绪" }, { "label": "Stage5 中场对话", - "path": "Huoshan1/05_Stage5" + "path": "Huoshan1/05_Stage5", + "anchorNode": "Stage5就绪" }, { "label": "Stage6 LOG 释放", - "path": "Huoshan1/06_Stage6" + "path": "Huoshan1/06_Stage6", + "anchorNode": "Stage6就绪" }, { "label": "Stage7 最终检查", - "path": "Huoshan1/07_Stage7" + "path": "Huoshan1/07_Stage7", + "anchorNode": "Stage7就绪" }, { "label": "Stage8 结束", - "path": "Huoshan1/08_Stage8" + "path": "Huoshan1/08_Stage8", + "anchorNode": "结束对话" } ] },