feat(render): implement Campaign AR and terrain fidelity
This commit is contained in:
parent
99cf26e00c
commit
7a5f96ede5
368 changed files with 50611 additions and 950 deletions
|
|
@ -76,14 +76,14 @@ internal sealed record RuntimeSettingsSnapshot(
|
|||
|
||||
internal interface IRuntimeSettingsStartupTarget
|
||||
{
|
||||
void ApplyDisplay(DisplaySettings display);
|
||||
RuntimeDisplayApplyResult ApplyDisplay(DisplaySettings display);
|
||||
|
||||
void ApplyAudio(AudioSettings audio);
|
||||
}
|
||||
|
||||
internal interface IRuntimeSettingsTargets
|
||||
{
|
||||
void ApplyDisplayWindowState(DisplaySettings display);
|
||||
RuntimeDisplayApplyResult ApplyDisplayWindowState(DisplaySettings display);
|
||||
|
||||
/// <summary>
|
||||
/// Campaign OP slice OP6 (2026-08-11): pushes the CURRENT audio
|
||||
|
|
@ -195,6 +195,13 @@ internal sealed class RuntimeSettingsController :
|
|||
|
||||
public QualitySettings ResolvedQuality { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Successful committed display changes. Render-pack selection listens to
|
||||
/// this edge and defers the actual candidate swap to its next stable frame
|
||||
/// boundary; failed persistence never changes the live selection.
|
||||
/// </summary>
|
||||
public event Action<DisplaySettings>? DisplayChanged;
|
||||
|
||||
// OP9: the optional developer-tools draft-preview view model
|
||||
// (SettingsVM) was retired — it had zero production construction
|
||||
// sites (only tests ever called CreateViewModel). HasDraftPreview is
|
||||
|
|
@ -216,7 +223,16 @@ internal sealed class RuntimeSettingsController :
|
|||
|
||||
if (!_startupDisplayApplied)
|
||||
{
|
||||
target.ApplyDisplay(Startup.Display);
|
||||
RuntimeDisplayApplyResult result = target.ApplyDisplay(Startup.Display);
|
||||
DisplaySettings applied = ReconcileDisplayResult(Startup.Display, result);
|
||||
if (!ReferenceEquals(applied, Startup.Display))
|
||||
{
|
||||
_storage.SaveDisplay(applied);
|
||||
Display = applied;
|
||||
_log(
|
||||
$"settings: startup fullscreen request reconciled to "
|
||||
+ $"native state {applied.Fullscreen}");
|
||||
}
|
||||
_startupDisplayApplied = true;
|
||||
}
|
||||
if (!_startupAudioApplied)
|
||||
|
|
@ -420,16 +436,43 @@ internal sealed class RuntimeSettingsController :
|
|||
{
|
||||
_storage.SaveDisplay(display);
|
||||
_log($"settings: display saved to {_storage.Location}");
|
||||
_runtimeTargets?.ApplyDisplayWindowState(display);
|
||||
Display = display;
|
||||
ReapplyQualityPreset(display.Quality);
|
||||
RuntimeDisplayApplyResult result = _runtimeTargets is null
|
||||
? new RuntimeDisplayApplyResult(display.Fullscreen)
|
||||
: _runtimeTargets.ApplyDisplayWindowState(display);
|
||||
DisplaySettings applied = ReconcileDisplayResult(display, result);
|
||||
if (!ReferenceEquals(applied, display))
|
||||
{
|
||||
_storage.SaveDisplay(applied);
|
||||
_log(
|
||||
$"settings: fullscreen request reconciled to native state "
|
||||
+ $"{applied.Fullscreen}");
|
||||
}
|
||||
Display = applied;
|
||||
ReapplyQualityPreset(applied.Quality);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log($"settings: display save failed: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DisplayChanged?.Invoke(Display);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log($"settings: display observer failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static DisplaySettings ReconcileDisplayResult(
|
||||
DisplaySettings requested,
|
||||
RuntimeDisplayApplyResult result) =>
|
||||
requested.Fullscreen == result.Fullscreen
|
||||
? requested
|
||||
: requested with { Fullscreen = result.Fullscreen };
|
||||
|
||||
/// <summary>Widened from private to public at Campaign OP slice OP6,
|
||||
/// same reason as <see cref="SaveDisplay"/>. Also now pushes the saved
|
||||
/// snapshot into the live engine via
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue