Extract reset, selection, entered-world, and route construction behind LiveSessionHost while preserving the sole LiveSessionController authority. Retain partial route and subscription cleanup for retry, and replace the embedded ACE-only shortcut with the exact named-retail unsigned skill formula. Co-authored-by: Codex <codex@openai.com>
281 lines
12 KiB
C#
281 lines
12 KiB
C#
namespace AcDream.App.Tests.Rendering;
|
|
|
|
/// <summary>
|
|
/// Temporary source-shape freeze for the Slice 8 cutover. Each later checkpoint
|
|
/// replaces the corresponding assertion with functional tests on the extracted
|
|
/// owner; this file prevents a mechanical move from silently reordering host
|
|
/// behavior before that owner exists.
|
|
/// </summary>
|
|
public sealed class GameWindowSlice8BoundaryTests
|
|
{
|
|
[Fact]
|
|
public void Run_PreservesNativeAttributeAndCallbackOrder()
|
|
{
|
|
string body = MethodBody("public void Run()", "private void OnLoad()");
|
|
|
|
AssertAppearsInOrder(
|
|
body,
|
|
"startupStore.LoadDisplay()",
|
|
"QualitySettings.WithEnvOverrides(startupBase)",
|
|
"_displayFramePacing.InitializeStartup(startupDisplay.VSync)",
|
|
"VSync = startupPacing.UseVSync",
|
|
"Samples = startupQuality.MsaaSamples",
|
|
"Window.Create(options)",
|
|
"_displayFramePacing.BindSurface(",
|
|
"_windowCallbacks = SilkWindowCallbackBinding.Create(",
|
|
"new WindowCallbackTargets(",
|
|
"OnLoad,",
|
|
"OnUpdate,",
|
|
"OnRender,",
|
|
"OnClosing,",
|
|
"OnFocusChanged,",
|
|
"OnFramebufferResize),",
|
|
"_displayFramePacing,",
|
|
"_hostQuiescence);",
|
|
"_windowCallbacks.Attach();",
|
|
"_window.Run();");
|
|
|
|
Assert.Equal(1, CountOccurrences(body, "SilkWindowCallbackBinding.Create("));
|
|
Assert.Equal(1, CountOccurrences(body, "_windowCallbacks.Attach();"));
|
|
Assert.DoesNotContain("_window.Load +=", body, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("_window.Update +=", body, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("_window.Render +=", body, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("_window.Closing +=", body, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void SessionStart_FollowsFrameGraphPublication()
|
|
{
|
|
string body = MethodBody(
|
|
"private void OnLoad()",
|
|
"private AcDream.App.Net.LiveSessionHost");
|
|
|
|
AssertAppearsInOrder(
|
|
body,
|
|
"_gl = GL.GetApi(_window!);",
|
|
"_input = _window!.CreateInput();",
|
|
"_kbSource = new AcDream.App.Input.SilkKeyboardSource(firstKb);",
|
|
"_mouseSource = new AcDream.App.Input.SilkMouseSource(",
|
|
"_inputDispatcher = new AcDream.UI.Abstractions.Input.InputDispatcher(",
|
|
"_movementInput.Bind(_inputDispatcher);",
|
|
"_cameraInput.Bind(_inputDispatcher);",
|
|
"_inputDispatcher.Fired += OnInputAction;",
|
|
"mouse.MouseMove +=",
|
|
"_cameraController = new CameraController(orbit, fly);",
|
|
"_dats = RuntimeDatCollectionFactory.OpenReadOnly(_datDir);",
|
|
"LoadAndApplyPersistedSettings();",
|
|
"_uiHost = new AcDream.App.UI.UiHost(",
|
|
"_uiHost.WireMouse(m)",
|
|
"_uiHost.WireKeyboard(kb)",
|
|
"_retailUiRuntime = AcDream.App.UI.RetailUiRuntime.Mount(",
|
|
"_liveEntities = new AcDream.App.World.LiveEntityRuntime(",
|
|
"_renderFrameOrchestrator =",
|
|
"_updateFrameOrchestrator = new AcDream.App.Update.UpdateFrameOrchestrator(",
|
|
"_liveSessionHost = CreateLiveSessionHost();",
|
|
"_liveSessionHost.Start(_options)");
|
|
Assert.Equal(1, CountOccurrences(body, "_liveSessionHost.Start(_options)"));
|
|
int start = body.IndexOf("_liveSessionHost.Start(_options)", StringComparison.Ordinal);
|
|
string postStart = body[start..];
|
|
Assert.Contains("switch (liveStart.Status)", postStart, StringComparison.Ordinal);
|
|
Assert.Empty(System.Text.RegularExpressions.Regex.Matches(
|
|
postStart,
|
|
@"(?m)^\s+_[A-Za-z]\w*\s*(?:\?\?=|=)"));
|
|
Assert.DoesNotContain("new ", postStart, StringComparison.Ordinal);
|
|
Assert.DoesNotContain(".Mount(", postStart, StringComparison.Ordinal);
|
|
Assert.DoesNotContain(".Bind(", postStart, StringComparison.Ordinal);
|
|
Assert.DoesNotContain(".Attach(", postStart, StringComparison.Ordinal);
|
|
Assert.DoesNotContain(".Compose(", postStart, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("PrepareResources(", postStart, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void InputAction_PreservesRetailAcceptedPriority()
|
|
{
|
|
string body = MethodBody(
|
|
"private void OnInputAction(",
|
|
"private void ToggleLiveCombatMode()");
|
|
|
|
AssertAppearsInOrder(
|
|
body,
|
|
"HandlePointerAction(action, activation)",
|
|
"action == AcDream.UI.Abstractions.Input.InputAction.ScrollUp",
|
|
"HandleScrollAction(action);",
|
|
"HandleCombatAction(action, activation)",
|
|
"activation != AcDream.UI.Abstractions.Input.ActivationType.Press",
|
|
"_retailUiRuntime?.HandleInputAction(action)",
|
|
"_selectionInteractions?.HandleInputAction(action)",
|
|
"HandlePressedMovementAction(action)",
|
|
"switch (action)");
|
|
}
|
|
|
|
[Fact]
|
|
public void FramebufferResize_PreservesViewportCameraAndDevtoolsOrder()
|
|
{
|
|
string body = MethodBody(
|
|
"private void OnFramebufferResize(",
|
|
"private void ApplyDisplayWindowState(");
|
|
|
|
AssertAppearsInOrder(
|
|
body,
|
|
"if (newSize.X <= 0 || newSize.Y <= 0) return;",
|
|
"_gl?.Viewport(",
|
|
"_viewportAspect.Update(",
|
|
"_cameraController?.SetAspect(",
|
|
"_devToolsFramePresenter?.ResetLayout(");
|
|
Assert.DoesNotContain("_uiHost", body, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void UpdateRenderFocusAndCloseRemainNarrowHostEdges()
|
|
{
|
|
string source = GameWindowSource();
|
|
string update = Slice(
|
|
source,
|
|
"private void OnUpdate(double dt)",
|
|
"private void OnCameraModeChanged(");
|
|
string render = Slice(
|
|
source,
|
|
"private void OnRender(double deltaSeconds)",
|
|
"private AcDream.App.Diagnostics.WorldLifecycleResourceSnapshot");
|
|
string focus = Slice(
|
|
source,
|
|
"private void OnFocusChanged(bool focused)",
|
|
"public void Dispose()");
|
|
string close = Slice(
|
|
source,
|
|
"private void OnClosing()",
|
|
"private ResourceShutdownTransaction CreateShutdownTransaction()");
|
|
|
|
Assert.Equal(1, CountOccurrences(update, "_updateFrameOrchestrator.Tick("));
|
|
Assert.Equal(1, CountOccurrences(render, "_renderFrameOrchestrator!.Render("));
|
|
AssertAppearsInOrder(
|
|
render,
|
|
"Vector2D<int> size = _window!.Size;",
|
|
"_renderFrameOrchestrator!.Render(",
|
|
"new AcDream.App.Rendering.RenderFrameInput(");
|
|
Assert.DoesNotContain("FramebufferSize", render, StringComparison.Ordinal);
|
|
AssertAppearsInOrder(focus, "if (!focused)", "_gameplayInputFrame?.EndMouseLook();");
|
|
AssertAppearsInOrder(
|
|
close,
|
|
"_hostQuiescence.StopAccepting();",
|
|
"CompleteShutdown();",
|
|
"_shutdown.CompleteOrThrow();");
|
|
}
|
|
|
|
[Fact]
|
|
public void Shutdown_PreservesDependencyStagesAndNativeWindowLast()
|
|
{
|
|
string source = GameWindowSource();
|
|
string manifest = Slice(
|
|
source,
|
|
"private ResourceShutdownTransaction CreateShutdownTransaction()",
|
|
"private void OnFocusChanged(bool focused)");
|
|
string dispose = Slice(
|
|
source,
|
|
"public void Dispose()",
|
|
"private sealed class NullAnimLoader");
|
|
|
|
string[] stages =
|
|
[
|
|
"new ResourceShutdownStage(\"session lifetime\"",
|
|
"new ResourceShutdownStage(\"frame borrowers\"",
|
|
"new ResourceShutdownStage(\"session dependents\"",
|
|
"new ResourceShutdownStage(\"live entities\"",
|
|
"new ResourceShutdownStage(\"live entity dependents\"",
|
|
"new ResourceShutdownStage(\"submitted GPU work\"",
|
|
"new ResourceShutdownStage(\"render frontends\"",
|
|
"new ResourceShutdownStage(\"shared texture owners\"",
|
|
"new ResourceShutdownStage(\"mesh adapter\"",
|
|
"new ResourceShutdownStage(\"remaining render owners\"",
|
|
"new ResourceShutdownStage(\"frame flight owner\"",
|
|
"new ResourceShutdownStage(\"content mappings\"",
|
|
"new ResourceShutdownStage(\"input\"",
|
|
"new ResourceShutdownStage(\"OpenGL context\"",
|
|
];
|
|
AssertAppearsInOrder(manifest, stages);
|
|
Assert.Equal(stages.Length, CountOccurrences(manifest, "new ResourceShutdownStage("));
|
|
foreach (string stage in stages)
|
|
Assert.Equal(1, CountOccurrences(manifest, stage));
|
|
AssertAppearsInOrder(
|
|
manifest,
|
|
"binding.Dispose();",
|
|
"if (!binding.IsDisposalComplete)",
|
|
"_windowCallbacks = null;");
|
|
AssertAppearsInOrder(
|
|
dispose,
|
|
"CompleteShutdown();",
|
|
"_window?.Dispose();",
|
|
"_window = null;");
|
|
Assert.Equal(1, CountOccurrences(dispose, "CompleteShutdown();"));
|
|
Assert.Equal(1, CountOccurrences(dispose, "_window?.Dispose();"));
|
|
Assert.Equal(1, CountOccurrences(dispose, "_window = null;"));
|
|
int nativeReleased = dispose.IndexOf("_window = null;", StringComparison.Ordinal)
|
|
+ "_window = null;".Length;
|
|
string afterNativeRelease = dispose[nativeReleased..];
|
|
int fallbackDocumentation = afterNativeRelease.IndexOf(
|
|
"/// <summary>",
|
|
StringComparison.Ordinal);
|
|
if (fallbackDocumentation >= 0)
|
|
afterNativeRelease = afterNativeRelease[..fallbackDocumentation];
|
|
Assert.All(
|
|
afterNativeRelease,
|
|
character => Assert.True(char.IsWhiteSpace(character) || character == '}'));
|
|
}
|
|
|
|
private static string MethodBody(string start, string end) =>
|
|
Slice(GameWindowSource(), start, end);
|
|
|
|
private static string Slice(string source, string start, string end)
|
|
{
|
|
int first = source.IndexOf(start, StringComparison.Ordinal);
|
|
int last = source.IndexOf(end, first + 1, StringComparison.Ordinal);
|
|
Assert.True(first >= 0, $"Missing source boundary: {start}");
|
|
Assert.True(last > first, $"Missing source boundary: {end}");
|
|
return source[first..last];
|
|
}
|
|
|
|
private static int CountOccurrences(string source, string value)
|
|
{
|
|
int count = 0;
|
|
int cursor = 0;
|
|
while ((cursor = source.IndexOf(value, cursor, StringComparison.Ordinal)) >= 0)
|
|
{
|
|
count++;
|
|
cursor += value.Length;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
private static void AssertAppearsInOrder(string source, params string[] fragments)
|
|
{
|
|
int cursor = -1;
|
|
foreach (string fragment in fragments)
|
|
{
|
|
int next = source.IndexOf(fragment, cursor + 1, StringComparison.Ordinal);
|
|
Assert.True(next >= 0, $"Missing expected source fragment: {fragment}");
|
|
Assert.True(next > cursor, $"Out-of-order source fragment: {fragment}");
|
|
cursor = next;
|
|
}
|
|
}
|
|
|
|
private static string GameWindowSource() => File.ReadAllText(Path.Combine(
|
|
FindRepoRoot(),
|
|
"src",
|
|
"AcDream.App",
|
|
"Rendering",
|
|
"GameWindow.cs"));
|
|
|
|
private static string FindRepoRoot()
|
|
{
|
|
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
|
while (directory is not null)
|
|
{
|
|
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
|
return directory.FullName;
|
|
directory = directory.Parent;
|
|
}
|
|
|
|
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
|
|
}
|
|
}
|