feat(render): implement Campaign AR and terrain fidelity

This commit is contained in:
Erik 2026-08-22 13:13:29 +02:00
parent 99cf26e00c
commit 7a5f96ede5
368 changed files with 50611 additions and 950 deletions

View file

@ -16,9 +16,16 @@ namespace AcDream.App.Settings;
internal interface IRuntimeDisplayWindowTarget
{
void Apply(DisplaySettings display);
RuntimeDisplayApplyResult Apply(DisplaySettings display);
}
/// <summary>
/// Native display state observed after a requested settings apply. The
/// controller owns persistence and uses this result to keep the checkbox and
/// settings file aligned with the GLFW post-condition (#392).
/// </summary>
internal readonly record struct RuntimeDisplayApplyResult(bool Fullscreen);
internal interface IRuntimeQualityApplicationTarget
{
void SetAlphaToCoverage(bool enabled);
@ -133,7 +140,7 @@ internal sealed class SilkRuntimeDisplayWindowTarget : IRuntimeDisplayWindowTarg
/// gate crash). Every failure path logs and leaves the window in a
/// usable state instead of throwing.
/// </summary>
public void Apply(DisplaySettings display)
public RuntimeDisplayApplyResult Apply(DisplaySettings display)
{
ArgumentNullException.ThrowIfNull(display);
bool haveResolution =
@ -147,7 +154,7 @@ internal sealed class SilkRuntimeDisplayWindowTarget : IRuntimeDisplayWindowTarg
// "any refused/failed switch logs a line".
Console.WriteLine(
$"display: fullscreen refused — unparseable resolution '{display.Resolution}'");
return;
return CurrentResult();
}
// Mechanism/blast M2: idempotence BEFORE any native work — every
// Display-backed Config row applies per change (sliders per drag
@ -155,17 +162,17 @@ internal sealed class SilkRuntimeDisplayWindowTarget : IRuntimeDisplayWindowTarg
// display-mode change per mouse sample.
if (_modeSwitcher.CurrentFullscreenMode is (int curW, int curH)
&& curW == width && curH == height)
return;
return CurrentResult();
if (!_isOfferedMode.Invoke($"{width}x{height}"))
{
Console.WriteLine(
$"display: fullscreen {width}x{height} refused — not an offered mode");
return;
return CurrentResult();
}
if (!_modeSwitcher.TryEnterFullscreen(width, height, out string? error))
Console.WriteLine(
$"display: fullscreen {width}x{height} failed ({error}) — window state unchanged (#392 tracks the persisted-flag divergence)");
return;
$"display: fullscreen {width}x{height} failed ({error}) — window state unchanged");
return CurrentResult();
}
// Windowed target: leave fullscreen first if needed (the native exit
@ -180,7 +187,7 @@ internal sealed class SilkRuntimeDisplayWindowTarget : IRuntimeDisplayWindowTarg
if (!_modeSwitcher.TryLeaveFullscreen(width, height, out string? error))
Console.WriteLine(
$"display: leaving fullscreen failed ({error})");
return;
return CurrentResult();
}
if (haveResolution && (_window.Size.X != width || _window.Size.Y != height))
@ -196,8 +203,12 @@ internal sealed class SilkRuntimeDisplayWindowTarget : IRuntimeDisplayWindowTarg
$"(window was {_window.Size.X}x{_window.Size.Y})");
_window.Size = new Vector2D<int>(width, height);
}
return CurrentResult();
}
private RuntimeDisplayApplyResult CurrentResult() =>
new(_modeSwitcher.IsFullscreen);
internal static bool TryParseResolution(
string spec,
out int width,
@ -240,13 +251,14 @@ internal sealed class RuntimeSettingsStartupTargets : IRuntimeSettingsStartupTar
_audio = audio;
}
public void ApplyDisplay(DisplaySettings display)
public RuntimeDisplayApplyResult ApplyDisplay(DisplaySettings display)
{
ArgumentNullException.ThrowIfNull(display);
_pacing.RefreshActiveMonitor();
_pacing.ApplyPreference(display.VSync);
_displayWindow.Apply(display);
RuntimeDisplayApplyResult result = _displayWindow.Apply(display);
ApplyFieldOfView(_cameras, display.FieldOfView);
return result;
}
public void ApplyAudio(AudioSettings audio) => ApplyAudio(_audio, audio);
@ -470,9 +482,9 @@ internal sealed class RuntimeSettingsTargets : IRuntimeSettingsTargets
_cameras = cameras;
}
public void ApplyDisplayWindowState(DisplaySettings display)
public RuntimeDisplayApplyResult ApplyDisplayWindowState(DisplaySettings display)
{
_displayWindow.Apply(display);
RuntimeDisplayApplyResult result = _displayWindow.Apply(display);
// #389 blast MUST-FIX 2 (see the ctor's cameras doc): the Field of
// View applies live on Save, from the update-phase save handler —
// deliberately NOT from the render-phase preview seam
@ -480,6 +492,7 @@ internal sealed class RuntimeSettingsTargets : IRuntimeSettingsTargets
// is the review's WATCH-3 cull-vs-raster landmine.
if (_cameras is not null)
RuntimeSettingsStartupTargets.ApplyFieldOfView(_cameras, display.FieldOfView);
return result;
}
/// <summary>Campaign OP slice OP6: reuses the SAME static helper the