중간 저장

This commit is contained in:
2026-04-22 13:30:24 +09:00
parent 7cedeef5a9
commit 31857815d7
69 changed files with 7211 additions and 148 deletions

View File

@@ -10,8 +10,9 @@ public sealed class KarismaSceneVariableCatalog
{
private static readonly string[] PreferredReportNames =
[
"TSCN_VARIABLE_DISCOVERY_ELECT2026_NORMAL.md",
"TSCN_VARIABLE_DISCOVERY_E_DRIVE.md",
"TSCN_VARIABLE_DISCOVERY_ELECT2026_NORMAL.md",
"TSCN_VARIABLE_DISCOVERY_ONE.md",
"TSCN_VARIABLE_DISCOVERY.md"
];
@@ -28,8 +29,8 @@ public sealed class KarismaSceneVariableCatalog
public static KarismaSceneVariableCatalog Load(LogService logService)
{
var reportPath = FindDiscoveryReportPath();
if (string.IsNullOrWhiteSpace(reportPath) || !File.Exists(reportPath))
var reportPaths = FindDiscoveryReportPaths().ToArray();
if (reportPaths.Length == 0)
{
logService.Warning("Karisma scene variable catalog report was not found. Falling back to runtime value heuristics.");
return new KarismaSceneVariableCatalog(
@@ -38,8 +39,17 @@ public sealed class KarismaSceneVariableCatalog
try
{
var scenes = ParseReport(reportPath);
logService.Info($"Karisma scene variable catalog loaded: scenes={scenes.Count} source='{reportPath}'.");
var mergedScenes = new Dictionary<string, Dictionary<string, KarismaSceneVariableDefinition>>(StringComparer.OrdinalIgnoreCase);
foreach (var reportPath in reportPaths)
{
MergeScenes(mergedScenes, ParseReport(reportPath));
}
var scenes = mergedScenes.ToDictionary(
pair => pair.Key,
pair => (IReadOnlyDictionary<string, KarismaSceneVariableDefinition>)pair.Value,
StringComparer.OrdinalIgnoreCase);
logService.Info($"Karisma scene variable catalog loaded: scenes={scenes.Count} sources={reportPaths.Length}.");
return new KarismaSceneVariableCatalog(scenes);
}
catch (Exception ex)
@@ -60,22 +70,45 @@ public sealed class KarismaSceneVariableCatalog
}
var relativePath = NormalizeRelativePath(Path.GetRelativePath(t3CutPath, scenePath));
return _scenes.TryGetValue(relativePath, out var variables)
? variables
: EmptySceneVariables;
if (_scenes.TryGetValue(relativePath, out var variables))
{
return variables;
}
var fileName = Path.GetFileName(relativePath);
if (!string.IsNullOrWhiteSpace(fileName))
{
var fileNameMatches = _scenes
.Where(pair => string.Equals(Path.GetFileName(pair.Key), fileName, StringComparison.OrdinalIgnoreCase))
.Take(2)
.ToArray();
if (fileNameMatches.Length == 1)
{
return fileNameMatches[0].Value;
}
}
return EmptySceneVariables;
}
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>> ParseReport(string reportPath)
{
var scenes = new Dictionary<string, Dictionary<string, KarismaSceneVariableDefinition>>(StringComparer.OrdinalIgnoreCase);
string? currentScene = null;
string? reportRootRelativePath = null;
foreach (var rawLine in File.ReadLines(reportPath, Encoding.UTF8))
{
var line = rawLine.Trim();
if (TryParseReportRoot(line, out var reportRoot))
{
reportRootRelativePath = NormalizeReportRootRelativePath(reportRoot);
continue;
}
if (TryParseSceneHeader(line, out var sceneRelativePath))
{
currentScene = NormalizeRelativePath(sceneRelativePath);
currentScene = NormalizeSceneKey(reportRootRelativePath, sceneRelativePath);
if (!scenes.ContainsKey(currentScene))
{
scenes[currentScene] = new Dictionary<string, KarismaSceneVariableDefinition>(StringComparer.OrdinalIgnoreCase);
@@ -124,6 +157,25 @@ public sealed class KarismaSceneVariableCatalog
StringComparer.OrdinalIgnoreCase);
}
private static void MergeScenes(
IDictionary<string, Dictionary<string, KarismaSceneVariableDefinition>> target,
IReadOnlyDictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>> source)
{
foreach (var (scenePath, variables) in source)
{
if (!target.TryGetValue(scenePath, out var mergedVariables))
{
mergedVariables = new Dictionary<string, KarismaSceneVariableDefinition>(StringComparer.OrdinalIgnoreCase);
target[scenePath] = mergedVariables;
}
foreach (var (variableName, definition) in variables)
{
mergedVariables[variableName] = definition;
}
}
}
private static bool TryParseSceneHeader(string line, out string sceneRelativePath)
{
sceneRelativePath = string.Empty;
@@ -136,6 +188,19 @@ public sealed class KarismaSceneVariableCatalog
return !string.IsNullOrWhiteSpace(sceneRelativePath);
}
private static bool TryParseReportRoot(string line, out string reportRoot)
{
reportRoot = string.Empty;
const string prefix = "- Root: `";
if (!line.StartsWith(prefix, StringComparison.Ordinal) || !line.EndsWith('`'))
{
return false;
}
reportRoot = line.Substring(prefix.Length, line.Length - prefix.Length - 1);
return !string.IsNullOrWhiteSpace(reportRoot);
}
private static List<string> SplitMarkdownRow(string line)
{
var cells = line.Split('|');
@@ -158,6 +223,11 @@ public sealed class KarismaSceneVariableCatalog
return KarismaSceneVariableKind.Counter;
}
if (IsLikelyCounterVariableName(variableName))
{
return KarismaSceneVariableKind.Counter;
}
if (variableName.StartsWith("\uC720\uD655\uB2F9", StringComparison.OrdinalIgnoreCase))
{
return KarismaSceneVariableKind.VideoResource;
@@ -179,8 +249,18 @@ public sealed class KarismaSceneVariableCatalog
return KarismaSceneVariableKind.Text;
}
private static string? FindDiscoveryReportPath()
private static bool IsLikelyCounterVariableName(string variableName)
{
return variableName.StartsWith("득표율", StringComparison.Ordinal) ||
variableName.StartsWith("투표율", StringComparison.Ordinal) ||
variableName.StartsWith("전국투표율", StringComparison.Ordinal);
}
private static IEnumerable<string> FindDiscoveryReportPaths()
{
var seenPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var reportPaths = new List<string>();
foreach (var startPath in EnumerateSearchRoots())
{
var current = startPath;
@@ -189,23 +269,25 @@ public sealed class KarismaSceneVariableCatalog
foreach (var reportName in PreferredReportNames)
{
var candidate = Path.Combine(current, reportName);
if (File.Exists(candidate))
if (File.Exists(candidate) && seenPaths.Add(candidate))
{
return candidate;
reportPaths.Add(candidate);
}
}
var wildcardCandidate = TryFindLatestDiscoveryReport(current);
if (!string.IsNullOrWhiteSpace(wildcardCandidate))
foreach (var wildcardCandidate in TryFindDiscoveryReports(current))
{
return wildcardCandidate;
if (seenPaths.Add(wildcardCandidate))
{
reportPaths.Add(wildcardCandidate);
}
}
current = Path.GetDirectoryName(current);
}
}
return null;
return reportPaths;
}
private static IEnumerable<string> EnumerateSearchRoots()
@@ -222,7 +304,7 @@ public sealed class KarismaSceneVariableCatalog
return roots;
}
private static string? TryFindLatestDiscoveryReport(string directoryPath)
private static IEnumerable<string> TryFindDiscoveryReports(string directoryPath)
{
try
{
@@ -230,12 +312,51 @@ public sealed class KarismaSceneVariableCatalog
.Where(path => !Path.GetFileName(path).Contains("SAMPLE", StringComparison.OrdinalIgnoreCase))
.OrderByDescending(path => File.GetLastWriteTimeUtc(path))
.ThenBy(path => path, StringComparer.OrdinalIgnoreCase)
.FirstOrDefault();
.ToArray();
}
catch
{
return [];
}
}
private static string NormalizeSceneKey(string? reportRootRelativePath, string sceneRelativePath)
{
var normalizedScenePath = NormalizeRelativePath(sceneRelativePath);
if (string.IsNullOrWhiteSpace(reportRootRelativePath) ||
string.IsNullOrWhiteSpace(normalizedScenePath) ||
normalizedScenePath.Contains('\\'))
{
return normalizedScenePath;
}
return NormalizeRelativePath(Path.Combine(reportRootRelativePath, normalizedScenePath));
}
private static string? NormalizeReportRootRelativePath(string reportRootPath)
{
if (string.IsNullOrWhiteSpace(reportRootPath))
{
return null;
}
var normalized = reportRootPath.Replace('/', '\\').Trim().TrimEnd('\\');
if (normalized.EndsWith("\\T3_Cut", StringComparison.OrdinalIgnoreCase))
{
return string.Empty;
}
const string marker = "\\T3_Cut\\";
var markerIndex = normalized.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
if (markerIndex >= 0)
{
return NormalizeRelativePath(normalized[(markerIndex + marker.Length)..]);
}
var leafFolder = Path.GetFileName(normalized);
return string.IsNullOrWhiteSpace(leafFolder)
? null
: NormalizeRelativePath(leafFolder);
}
private static string NormalizeRelativePath(string relativePath)