fix #376+#388: real fullscreen mode switching, state-aware display apply
Slice 5+6 of the display block, one coherent unit (they share the state machine the goal's dual review covers). GlfwDisplayModeSwitcher (#376) ports retail's fullscreen semantics - Device::ForceDisplayResolution @gmClient::Init 0x004047af is a REAL video-mode change - through native glfwSetWindowMonitor on the same IWindow.Native.Glfw handle path #348's cursor cache proved. Primary monitor (retail's primary display device); refresh = the monitor's highest for the picked WxH; the windowed placement is remembered for the exit path; every failure is a no-throw (bool, reason) result. SilkRuntimeDisplayWindowTarget.Apply (#388) becomes the state-aware machine: fullscreen target = validated native mode switch (mode must be in #391's DisplayModeCatalog - an offered mode is supported by construction, making the "Graphics mode not supported" crash class unreachable from the dropdown); windowed target while fullscreen = the native exit (which sets the client size itself); plain windowed pick = the proven #387 size write. A raw Size write NEVER happens against a fullscreen window - on GLFW that is a video-mode request, and an unsupported one was the exact unhandled-GlfwException that killed the user's 2026-08-13 session. The old Silk borderless WindowState path is deleted from the apply. New IWindowedSizeSurface narrows the window dependency so the machine is unit-testable (FakePacingSurface idiom). Live-verified on this machine (goal-sanctioned automated run): display: fullscreen mode switch 1920x1080@300 -> framebuffer resize event 1920x1080 -> vulkan: swapchain recreated 1920x1080 ok=True -> graceful close, desktop mode restored. Tests: 5 state-machine facts (validated switch/never-size-write, unoffered refusal, failed-switch usability, native exit, plain windowed write). App suite 4,972/3 skips. Gate script sections D4-D6 written (black-screen-risk steps flagged). Dual Opus review of the pair follows as its own round. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2153bee247
commit
e56aa5115c
5 changed files with 451 additions and 27 deletions
|
|
@ -154,6 +154,139 @@ public sealed class RuntimeSettingsControllerTests
|
|||
Assert.Equal(expectedFov, cameras.Fly.FovY, precision: 5);
|
||||
}
|
||||
|
||||
// ── #376/#388: the state-aware display apply ────────────────────────
|
||||
|
||||
private sealed class FakeSizeSurface : IWindowedSizeSurface
|
||||
{
|
||||
public Silk.NET.Maths.Vector2D<int> Size { get; set; } = new(1280, 720);
|
||||
public int Writes { get; private set; }
|
||||
Silk.NET.Maths.Vector2D<int> IWindowedSizeSurface.Size
|
||||
{
|
||||
get => Size;
|
||||
set { Size = value; Writes++; }
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeModeSwitcher : IDisplayModeSwitcher
|
||||
{
|
||||
public bool IsFullscreen { get; set; }
|
||||
public bool EnterSucceeds { get; set; } = true;
|
||||
public List<string> Calls { get; } = [];
|
||||
|
||||
public bool TryEnterFullscreen(int width, int height, out string? error)
|
||||
{
|
||||
Calls.Add($"enter:{width}x{height}");
|
||||
error = EnterSucceeds ? null : "injected failure";
|
||||
if (EnterSucceeds) IsFullscreen = true;
|
||||
return EnterSucceeds;
|
||||
}
|
||||
|
||||
public bool TryLeaveFullscreen(int width, int height, out string? error)
|
||||
{
|
||||
Calls.Add($"leave:{width}x{height}");
|
||||
error = null;
|
||||
IsFullscreen = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayApply_FullscreenPick_IsAValidatedModeSwitch_NeverASizeWrite()
|
||||
{
|
||||
// #388: a raw Size write on a fullscreen GLFW window is a video-mode
|
||||
// request — the crash class from the 2026-08-13 gate session.
|
||||
var surface = new FakeSizeSurface();
|
||||
var switcher = new FakeModeSwitcher();
|
||||
var target = new SilkRuntimeDisplayWindowTarget(
|
||||
surface, switcher, spec => spec == "1920x1080");
|
||||
|
||||
target.Apply(DisplaySettings.Default with
|
||||
{
|
||||
Fullscreen = true,
|
||||
Resolution = "1920x1080",
|
||||
});
|
||||
|
||||
Assert.Equal(["enter:1920x1080"], switcher.Calls);
|
||||
Assert.Equal(0, surface.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayApply_UnofferedFullscreenMode_IsRefused_NotAttempted()
|
||||
{
|
||||
// #376: validation against the offered catalog makes "Graphics mode
|
||||
// not supported" unreachable from the dropdown.
|
||||
var surface = new FakeSizeSurface();
|
||||
var switcher = new FakeModeSwitcher();
|
||||
var target = new SilkRuntimeDisplayWindowTarget(
|
||||
surface, switcher, _ => false);
|
||||
|
||||
target.Apply(DisplaySettings.Default with
|
||||
{
|
||||
Fullscreen = true,
|
||||
Resolution = "1234x777",
|
||||
});
|
||||
|
||||
Assert.Empty(switcher.Calls);
|
||||
Assert.Equal(0, surface.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayApply_FailedModeSwitch_LeavesTheWindowUsable()
|
||||
{
|
||||
// #388: a failed switch is logged and the client stays windowed —
|
||||
// never a throw out of a settings apply.
|
||||
var surface = new FakeSizeSurface();
|
||||
var switcher = new FakeModeSwitcher { EnterSucceeds = false };
|
||||
var target = new SilkRuntimeDisplayWindowTarget(
|
||||
surface, switcher, _ => true);
|
||||
|
||||
target.Apply(DisplaySettings.Default with
|
||||
{
|
||||
Fullscreen = true,
|
||||
Resolution = "1920x1080",
|
||||
});
|
||||
|
||||
Assert.False(switcher.IsFullscreen);
|
||||
Assert.Equal(0, surface.Writes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayApply_WindowedWhileFullscreen_LeavesViaTheSwitcher()
|
||||
{
|
||||
var surface = new FakeSizeSurface();
|
||||
var switcher = new FakeModeSwitcher { IsFullscreen = true };
|
||||
var target = new SilkRuntimeDisplayWindowTarget(
|
||||
surface, switcher, _ => true);
|
||||
|
||||
target.Apply(DisplaySettings.Default with
|
||||
{
|
||||
Fullscreen = false,
|
||||
Resolution = "1600x900",
|
||||
});
|
||||
|
||||
Assert.Equal(["leave:1600x900"], switcher.Calls);
|
||||
Assert.Equal(0, surface.Writes); // the native exit sets the size itself
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayApply_PlainWindowedPick_IsTheProvenSizeWrite()
|
||||
{
|
||||
var surface = new FakeSizeSurface();
|
||||
var switcher = new FakeModeSwitcher();
|
||||
var target = new SilkRuntimeDisplayWindowTarget(
|
||||
surface, switcher, _ => true);
|
||||
|
||||
target.Apply(DisplaySettings.Default with
|
||||
{
|
||||
Fullscreen = false,
|
||||
Resolution = "1600x900",
|
||||
});
|
||||
|
||||
Assert.Empty(switcher.Calls);
|
||||
Assert.Equal(1, surface.Writes);
|
||||
Assert.Equal(new Silk.NET.Maths.Vector2D<int>(1600, 900), surface.Size);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeTarget_ApplyDisplayWindowState_AppliesFieldOfViewLive()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue