feat: complete legacy parity and modernize operator UI

This commit is contained in:
2026-07-22 12:34:46 +09:00
parent fc4007d676
commit 939c252d23
149 changed files with 26515 additions and 1736 deletions

View File

@@ -22,11 +22,11 @@ public sealed class LegacyOperatorSettingsTests
}
[Fact]
public void SceneValidation_RequiresOnlyTheBaselineSceneAndReturnsAnAbbreviation()
public void SceneValidation_RequiresEveryActiveSceneAndBuiltInAssetAndAllowsMissingExternalVideo()
{
using var directory = new TemporaryDirectory();
var sceneDirectory = directory.CreateDirectory("Cuts");
File.WriteAllText(Path.Combine(sceneDirectory, "5001.t2s"), "baseline");
WriteValidSceneFiles(sceneDirectory);
var result = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
@@ -34,7 +34,7 @@ public sealed class LegacyOperatorSettingsTests
Assert.True(result.IsValid);
Assert.Equal(Path.GetFullPath(sceneDirectory), result.Folder!.CanonicalPath);
Assert.Equal("baseline", File.ReadAllText(
Assert.Equal("fixture-scene", File.ReadAllText(
Path.Combine(result.Folder.CanonicalPath, "5001.t2s")));
Assert.Contains("Cuts", result.Folder.AbbreviatedPath);
Assert.InRange(
@@ -42,9 +42,125 @@ public sealed class LegacyOperatorSettingsTests
1,
LegacyOperatorFolderValidator.DefaultDisplayCharacters);
Assert.False(result.Folder.DatabaseIniDetected);
Assert.Equal(
LegacyOperatorFolderValidator.OptionalExternalAssetRelativePaths.Count,
result.Folder.MissingOptionalExternalAssetCount);
Assert.Null(result.WarningMessage);
}
[Fact]
public void SceneValidation_BaselineOnlyIsRejectedInsteadOfBreakingOtherScenesAfterRestart()
{
using var directory = new TemporaryDirectory();
var sceneDirectory = directory.CreateDirectory("partial-Cuts");
File.WriteAllText(Path.Combine(sceneDirectory, "5001.t2s"), "baseline");
var result = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
sceneDirectory);
Assert.False(result.IsValid);
Assert.Equal(
LegacyOperatorFolderValidationFailure.RequiredFileMissing,
result.Failure);
Assert.Null(result.Folder);
AssertPathFree(result.WarningMessage, sceneDirectory);
}
[Fact]
public void SceneValidation_MissingOneActiveSceneOrBuiltInAssetIsRejected()
{
using var directory = new TemporaryDirectory();
var missingSceneDirectory = directory.CreateDirectory("missing-scene");
WriteValidSceneFiles(missingSceneDirectory);
File.Delete(Path.Combine(missingSceneDirectory, "5079.t2s"));
var missingScene = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
missingSceneDirectory);
var missingAssetDirectory = directory.CreateDirectory("missing-asset");
WriteValidSceneFiles(missingAssetDirectory);
File.Delete(Path.Combine(missingAssetDirectory, "Images", "KRX.png"));
var missingAsset = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
missingAssetDirectory);
Assert.False(missingScene.IsValid);
Assert.Equal(LegacyOperatorFolderValidationFailure.RequiredFileMissing,
missingScene.Failure);
Assert.False(missingAsset.IsValid);
Assert.Equal(LegacyOperatorFolderValidationFailure.RequiredFileMissing,
missingAsset.Failure);
}
[Fact]
public void SceneValidation_RejectsEmptyRequiredOrPresentOptionalFiles()
{
using var directory = new TemporaryDirectory();
var emptyRequiredDirectory = directory.CreateDirectory("empty-required");
WriteValidSceneFiles(emptyRequiredDirectory);
File.WriteAllBytes(Path.Combine(emptyRequiredDirectory, "5076.t2s"), []);
var emptyRequired = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
emptyRequiredDirectory);
var emptyOptionalDirectory = directory.CreateDirectory("empty-optional");
WriteValidSceneFiles(emptyOptionalDirectory);
var optionalPath = Path.Combine(emptyOptionalDirectory, "Video", "큐브배경.vrv");
Directory.CreateDirectory(Path.GetDirectoryName(optionalPath)!);
File.WriteAllBytes(optionalPath, []);
var emptyOptional = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
emptyOptionalDirectory);
Assert.False(emptyRequired.IsValid);
Assert.Equal(LegacyOperatorFolderValidationFailure.RequiredFileUnsafe,
emptyRequired.Failure);
Assert.False(emptyOptional.IsValid);
Assert.Equal(LegacyOperatorFolderValidationFailure.RequiredFileUnsafe,
emptyOptional.Failure);
}
[Fact]
public void SceneValidationContract_MatchesTheReachableCutRequirementManifest()
{
var manifestPath = Path.Combine(
FindRepositoryRoot(),
"scripts",
"LegacyCutRequirements.json");
using var document = JsonDocument.Parse(File.ReadAllBytes(manifestPath));
var root = document.RootElement;
var manifestScenes = root.GetProperty("activeAliases")
.EnumerateArray()
.Select(value => string.Concat(value.GetString(), ".t2s"))
.ToArray();
var manifestAssets = root.GetProperty("assets")
.EnumerateArray()
.Select(value => new
{
Kind = value.GetProperty("kind").GetString(),
Path = value.GetProperty("path").GetString()!
})
.ToArray();
Assert.Equal(
manifestScenes.OrderBy(value => value, StringComparer.Ordinal),
LegacyOperatorFolderValidator.RequiredSceneFileNames
.OrderBy(value => value, StringComparer.Ordinal));
Assert.Equal(
manifestAssets.Where(value => value.Kind != "Video")
.Select(value => value.Path)
.OrderBy(value => value, StringComparer.Ordinal),
LegacyOperatorFolderValidator.RequiredBuiltInAssetRelativePaths
.OrderBy(value => value, StringComparer.Ordinal));
Assert.Equal(
manifestAssets.Where(value => value.Kind == "Video")
.Select(value => value.Path)
.OrderBy(value => value, StringComparer.Ordinal),
LegacyOperatorFolderValidator.OptionalExternalAssetRelativePaths
.OrderBy(value => value, StringComparer.Ordinal));
}
[Fact]
public void SceneValidation_MissingBaselineFailsWithoutLeakingThePath()
{
@@ -229,7 +345,7 @@ public sealed class LegacyOperatorSettingsTests
{
using var directory = new TemporaryDirectory();
var sceneDirectory = directory.CreateDirectory("Cuts");
File.WriteAllText(Path.Combine(sceneDirectory, "5001.t2s"), "baseline");
WriteValidSceneFiles(sceneDirectory);
var resourceDirectory = directory.CreateDirectory("Res");
WriteValidResourceFiles(resourceDirectory);
var backgroundDirectory = directory.CreateDirectory("Background");
@@ -395,6 +511,22 @@ public sealed class LegacyOperatorSettingsTests
WriteComparisonFile(resourceDirectory);
}
private static void WriteValidSceneFiles(string sceneDirectory)
{
foreach (var fileName in LegacyOperatorFolderValidator.RequiredSceneFileNames)
{
File.WriteAllText(Path.Combine(sceneDirectory, fileName), "fixture-scene");
}
foreach (var relativePath in
LegacyOperatorFolderValidator.RequiredBuiltInAssetRelativePaths)
{
var path = Path.Combine(sceneDirectory, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
File.WriteAllText(path, "fixture-asset");
}
}
private static void WriteFixedFile(
string resourceDirectory,
string fileName,
@@ -463,6 +595,21 @@ public sealed class LegacyOperatorSettingsTests
DecoderFallback.ExceptionFallback);
}
private static string FindRepositoryRoot()
{
for (var current = new DirectoryInfo(AppContext.BaseDirectory);
current is not null;
current = current.Parent)
{
if (File.Exists(Path.Combine(current.FullName, "MBN_STOCK_WEBVIEW.sln")))
{
return current.FullName;
}
}
throw new DirectoryNotFoundException("Repository root was not found.");
}
private sealed class TemporaryDirectory : IDisposable
{
public TemporaryDirectory()