feat(dev-save): 强化 Huoshan 阶段存档提升与跳转校验

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-15 13:57:21 +08:00
co-authored by Cursor
parent 4ab2f83395
commit 423a3954dd
3 changed files with 300 additions and 40 deletions
@@ -17,6 +17,22 @@ namespace AibisDream.EditorTools
/// </summary>
public sealed class DevSavePromoteWindow : EditorWindow
{
private const string HuoshanSectionId = "Huoshan1";
private const string HuoshanSceneName = "Scene/HuoShanFixScene";
private const string HuoshanYarnProjectId = "FP_Huoshan1";
private static readonly (string NodeName, string Label, string FolderName)[] HuoshanCheckpoints =
{
("开头对话", "Stage1 开场", "01_Stage1"),
("Stage2就绪", "Stage2 初检", "02_Stage2"),
("Stage3就绪", "Stage3 滤波器", "03_Stage3"),
("Stage4就绪", "Stage4 表达深入", "04_Stage4"),
("Stage5就绪", "Stage5 中场对话", "05_Stage5"),
("Stage6就绪", "Stage6 LOG 释放", "06_Stage6"),
("Stage7就绪", "Stage7 最终检查", "07_Stage7"),
("结束对话", "Stage8 结束", "08_Stage8"),
};
private string sectionId = "Huoshan1";
private string yarnProjectFilter = "FP_Huoshan1";
private string sceneFilter = "Scene/HuoShanFixScene";
@@ -100,8 +116,19 @@ namespace AibisDream.EditorTools
return;
}
if (IsHuoshanSection()
&& HuoshanCheckpoints.Any(checkpoint => candidates.All(c => c.NodeName != checkpoint.NodeName)))
{
var missing = HuoshanCheckpoints
.Where(checkpoint => candidates.All(c => c.NodeName != checkpoint.NodeName))
.Select(checkpoint => checkpoint.NodeName);
status = $"Huoshan1 缺少阶段存档:{string.Join(", ", missing)}。请完整跑通流程后再提升。";
preview = candidates;
return;
}
var sectionDir = Path.Combine(ConstRef.TestSaveFilePath, sectionId.Trim());
if (clearSectionFirst && Directory.Exists(sectionDir))
if ((clearSectionFirst || IsHuoshanSection()) && Directory.Exists(sectionDir))
Directory.Delete(sectionDir, recursive: true);
Directory.CreateDirectory(sectionDir);
@@ -109,11 +136,12 @@ namespace AibisDream.EditorTools
int copied = 0;
int index = 1;
foreach (var item in candidates.OrderBy(c => c.SavedAt, StringComparer.Ordinal)
.ThenBy(c => c.NodeName, StringComparer.Ordinal))
foreach (var item in OrderCandidates(candidates))
{
var safeNode = SanitizeFolderName(string.IsNullOrEmpty(item.NodeName) ? "no_node" : item.NodeName);
var folderName = $"{index:00}_{safeNode}";
var folderName = IsHuoshanSection()
? HuoshanCheckpoints.First(checkpoint => checkpoint.NodeName == item.NodeName).FolderName
: $"{index:00}_{safeNode}";
var destDir = Path.Combine(sectionDir, folderName);
Directory.CreateDirectory(destDir);
@@ -176,13 +204,42 @@ namespace AibisDream.EditorTools
}
if (!keepLatestPerNode)
return list.OrderBy(c => c.SavedAt, StringComparer.Ordinal).ToList();
return FilterHuoshanCheckpoints(list.OrderBy(c => c.SavedAt, StringComparer.Ordinal).ToList());
return list
var latestPerNode = list
.GroupBy(c => string.IsNullOrEmpty(c.NodeName) ? c.FolderName : c.NodeName, StringComparer.Ordinal)
.Select(g => g.OrderByDescending(c => c.SavedAt, StringComparer.Ordinal).First())
.OrderBy(c => c.SavedAt, StringComparer.Ordinal)
.ToList();
return FilterHuoshanCheckpoints(latestPerNode);
}
private bool IsHuoshanSection()
{
return string.Equals(sectionId?.Trim(), HuoshanSectionId, StringComparison.OrdinalIgnoreCase);
}
private List<Candidate> FilterHuoshanCheckpoints(List<Candidate> candidates)
{
if (!IsHuoshanSection())
return candidates;
var allowedNodes = new HashSet<string>(
HuoshanCheckpoints.Select(checkpoint => checkpoint.NodeName),
StringComparer.Ordinal);
return candidates.Where(candidate => allowedNodes.Contains(candidate.NodeName)).ToList();
}
private IEnumerable<Candidate> OrderCandidates(List<Candidate> candidates)
{
if (!IsHuoshanSection())
{
return candidates.OrderBy(c => c.SavedAt, StringComparer.Ordinal)
.ThenBy(c => c.NodeName, StringComparer.Ordinal);
}
return HuoshanCheckpoints.Select(checkpoint =>
candidates.First(candidate => candidate.NodeName == checkpoint.NodeName));
}
private static void EnsureCatalogSection(string sectionId)
@@ -210,18 +267,35 @@ namespace AibisDream.EditorTools
}
catalog.sections ??= new List<CatalogSectionDto>();
if (catalog.sections.All(s => !string.Equals(s.id, sectionId, StringComparison.OrdinalIgnoreCase)))
var section = catalog.sections.FirstOrDefault(
s => string.Equals(s.id, sectionId, StringComparison.OrdinalIgnoreCase));
if (section == null)
{
catalog.sections.Add(new CatalogSectionDto
section = new CatalogSectionDto
{
id = sectionId,
title = sectionId
});
File.WriteAllText(
catalogPath,
JsonConvert.SerializeObject(catalog, Formatting.Indented),
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
};
catalog.sections.Add(section);
}
if (string.Equals(sectionId, HuoshanSectionId, StringComparison.OrdinalIgnoreCase))
{
section.title = "Huoshan 1";
section.expectedSceneName = HuoshanSceneName;
section.expectedYarnProjectId = HuoshanYarnProjectId;
section.allowedNodes = HuoshanCheckpoints.Select(checkpoint => checkpoint.NodeName).ToList();
section.entries = HuoshanCheckpoints.Select(checkpoint => new CatalogEntryDto
{
label = checkpoint.Label,
path = $"{HuoshanSectionId}/{checkpoint.FolderName}"
}).ToList();
}
File.WriteAllText(
catalogPath,
JsonConvert.SerializeObject(catalog, Formatting.Indented),
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
}
private static void CopyIfExists(string src, string dest)
@@ -285,6 +359,17 @@ namespace AibisDream.EditorTools
{
public string id;
public string title;
public List<CatalogEntryDto> entries;
public string expectedSceneName;
public string expectedYarnProjectId;
public List<string> allowedNodes;
}
[Serializable]
private class CatalogEntryDto
{
public string label;
public string path;
}
}
}