From 9ea8c05f225f7509a6cdce2d5474557df92ff029 Mon Sep 17 00:00:00 2001 From: Highbyte Date: Sun, 12 Jul 2026 23:38:56 +0200 Subject: [PATCH] Fix Avalonia startup display sizing and system switch state --- .../App.axaml.cs | 34 ++++++++---- .../ViewModels/MainViewModel.cs | 15 +++--- .../C64SystemConfigurerCore.cs | 9 ++++ .../GenericComputerSystemConfigurerCore.cs | 16 ++++++ .../Vic20SystemConfigurerCore.cs | 11 ++++ .../Highbyte.DotNet6502.Systems/HostApp.cs | 53 +++++++++++++------ .../ISystemConfigurer.cs | 1 + .../Highbyte.DotNet6502.Systems/ScreenInfo.cs | 35 ++++++++++++ .../Highbyte.DotNet6502.Systems/SystemList.cs | 8 +++ .../HostAppTests.cs | 48 +++++++++++++++++ .../SystemConfigurerTests.cs | 11 +++- .../SystemRunnerTests.cs | 4 +- 12 files changed, 209 insertions(+), 36 deletions(-) create mode 100644 src/libraries/Highbyte.DotNet6502.Systems/ScreenInfo.cs diff --git a/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Core/App.axaml.cs b/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Core/App.axaml.cs index 8564e767c..544eb30fb 100644 --- a/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Core/App.axaml.cs +++ b/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Core/App.axaml.cs @@ -66,10 +66,10 @@ public partial class App : Application public bool IsHostAppReady => _hostApp != null; /// - /// Optional async runner invoked from - /// when an automated startup has been requested (e.g. via URL query parameters in the Browser host). - /// Running automation from there guarantees the Avalonia view tree has been loaded and rendered - /// at least once, so subsequent InvalidateVisual calls actually trigger paint. + /// Optional async runner invoked from + /// when an automated startup has been requested (e.g. via URL query parameters in the Browser host). + /// Running automation from there guarantees the Avalonia view tree has been loaded and rendered + /// at least once, so subsequent InvalidateVisual calls actually trigger paint. /// public Func? AutomatedStartupRunner => _automatedStartupRunner; @@ -119,7 +119,7 @@ public partial class App : Application /// Optional callback to remove a script by file name (browser: from localStorage). /// Optional callback to seed bundled example scripts into the host's script storage. /// - /// Optional automated-startup delegate invoked from + /// Optional automated-startup delegate invoked from /// after the view tree has been loaded. When non-null, the UI's default system selection is /// suppressed and the runner is invoked instead — used by the Browser host for URL-driven /// automation, by Desktop for CLI-driven automation, and as a no-op (_ => Task.CompletedTask) @@ -456,11 +456,25 @@ private void InitializeHostApp() _scriptingConfig); // Wire Lua scripting engine (NoScriptingEngine used when null, e.g. in WASM) - _hostApp.SetScriptingEngine(_scriptingEngine ?? new NoScriptingEngine()); - - // Signal waiters (e.g. automated startup on a background thread) that HostApp is ready. - // TrySetResult guarantees all writes above are visible to awaiters before they resume. - s_hostAppReady.TrySetResult(_hostApp); + _hostApp.SetScriptingEngine(_scriptingEngine ?? new NoScriptingEngine()); + + // Normal startup should have a selected system before the first view layout. Otherwise + // bindings that size the emulator display evaluate with no selected system and briefly + // fall back to 320x200 before MainView.Loaded selects the default system. Automated + // startup still runs after the view is loaded because it may need a rendered tree. + if (_automatedStartupRunner == null) + { + _logger.LogInformation( + "Selecting default system '{DefaultEmulator}' before Avalonia view creation", + _emulatorConfig.DefaultEmulator); +#pragma warning disable VSTHRD002 + _hostApp.SelectSystem(_emulatorConfig.DefaultEmulator).GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 + } + + // Signal waiters (e.g. automated startup on a background thread) that HostApp is ready. + // TrySetResult guarantees all writes above are visible to awaiters before they resume. + s_hostAppReady.TrySetResult(_hostApp); } catch (Exception ex) { diff --git a/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Core/ViewModels/MainViewModel.cs b/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Core/ViewModels/MainViewModel.cs index 91270e51a..76310f7f2 100644 --- a/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Core/ViewModels/MainViewModel.cs +++ b/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Core/ViewModels/MainViewModel.cs @@ -872,11 +872,9 @@ public MainViewModel( .Select(config => GetAudioToolTip(config)) .ToProperty(this, x => x.AudioTooltip); - // A host-config update can rebuild the selected system's temporary instance and thereby - // change its screen dimensions (e.g. ROMs downloaded during automated startup make the C64 - // buildable, so CurrentSystemScreenInfo goes from the default fallback to the real size). - // The system/variant identity does not change in that case, so refresh the emulator display - // size here too — otherwise the display container keeps the default 320x200 size. + // A host-config update can rebuild the selected system's temporary instance or change the + // config-derived pre-build screen dimensions. The system/variant identity does not change in + // that case, so refresh the emulator display size here too. _hostApp .WhenAnyValue(x => x.CurrentHostSystemConfig) .Subscribe(_ => @@ -1245,8 +1243,11 @@ private async Task SetDefaultSystemSelectionAsync() return; } - _logger.LogInformation($"Setting default system '{_emulatorConfig.DefaultEmulator}' during MainViewModel initialization"); - await HostApp.SelectSystem(_emulatorConfig.DefaultEmulator); + if (HostApp.SelectedSystemName != _emulatorConfig.DefaultEmulator) + { + _logger.LogInformation($"Setting default system '{_emulatorConfig.DefaultEmulator}' during MainViewModel initialization"); + await HostApp.SelectSystem(_emulatorConfig.DefaultEmulator); + } } /// diff --git a/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/C64SystemConfigurerCore.cs b/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/C64SystemConfigurerCore.cs index ef487ea84..435d0b159 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/C64SystemConfigurerCore.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems.Commodore64/C64SystemConfigurerCore.cs @@ -3,6 +3,7 @@ using Highbyte.DotNet6502.Systems.Commodore64.Cartridge.SwiftLink; using Highbyte.DotNet6502.Systems.Commodore64.Transport; using Highbyte.DotNet6502.Systems.Commodore64.Models; +using Highbyte.DotNet6502.Systems.Commodore64.Video; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -65,6 +66,14 @@ public virtual IEnumerable GetUserContentDirectories() public Task> GetConfigurationVariants(ISystemConfig systemConfig) => Task.FromResult(C64ModelInventory.C64Models.Keys.ToList()); + public IScreen? GetScreenInfo(string configurationVariant, ISystemConfig systemConfig) + { + if (!C64ModelInventory.C64Models.TryGetValue(configurationVariant, out var c64Model)) + return null; + + return new Vic2Screen(c64Model.Vic2Models.First(), c64Model.CPUFrequencyHz); + } + /// /// Creates a fresh host config via the supplied factory and binds it from the matching /// section. Hosts that store config elsewhere (e.g. browser diff --git a/src/libraries/Highbyte.DotNet6502.Systems.Generic/GenericComputerSystemConfigurerCore.cs b/src/libraries/Highbyte.DotNet6502.Systems.Generic/GenericComputerSystemConfigurerCore.cs index e59a07ad1..af16dd197 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems.Generic/GenericComputerSystemConfigurerCore.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems.Generic/GenericComputerSystemConfigurerCore.cs @@ -55,6 +55,22 @@ protected GenericComputerSystemConfigurerCore( public virtual Task> GetConfigurationVariants(ISystemConfig systemConfig) => Task.FromResult(((GenericComputerSystemConfig)systemConfig).ExamplePrograms.Keys.ToList()); + public IScreen? GetScreenInfo(string configurationVariant, ISystemConfig systemConfig) + { + var genericSystemConfig = (GenericComputerSystemConfig)systemConfig; + var genericComputerConfig = GenericComputerExampleConfigs.GetExampleConfig(configurationVariant, genericSystemConfig); + var screen = genericComputerConfig.Memory.Screen; + const int characterWidth = 8; + const int characterHeight = 8; + + return new ScreenInfo( + screen.Cols * characterWidth, + screen.Rows * characterHeight, + (screen.Cols + (2 * screen.BorderCols)) * characterWidth, + (screen.Rows + (2 * screen.BorderRows)) * characterHeight, + genericComputerConfig.ScreenRefreshFrequencyHz); + } + /// /// Creates a fresh host config via the supplied factory and binds it from the matching /// section. Hosts that store config elsewhere (e.g. browser diff --git a/src/libraries/Highbyte.DotNet6502.Systems.Vic20/Vic20SystemConfigurerCore.cs b/src/libraries/Highbyte.DotNet6502.Systems.Vic20/Vic20SystemConfigurerCore.cs index 8e1228e80..e5fe200d1 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems.Vic20/Vic20SystemConfigurerCore.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems.Vic20/Vic20SystemConfigurerCore.cs @@ -45,6 +45,17 @@ public virtual IEnumerable GetUserContentDirectories() public virtual Task> GetConfigurationVariants(ISystemConfig systemConfig) => Task.FromResult(new List { VariantNtsc, VariantPal }); + public IScreen? GetScreenInfo(string configurationVariant, ISystemConfig systemConfig) + { + var vic20Config = BuildVic20ConfigForVariant(configurationVariant); + return new ScreenInfo( + Vic20Config.DrawableAreaWidth, + Vic20Config.DrawableAreaHeight, + vic20Config.MaxVisibleWidth, + vic20Config.MaxVisibleHeight, + vic20Config.ScreenRefreshFrequencyHz); + } + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Host config binding is limited to known application config models that are rooted by the host application.")] public virtual Task GetNewHostSystemConfig() { diff --git a/src/libraries/Highbyte.DotNet6502.Systems/HostApp.cs b/src/libraries/Highbyte.DotNet6502.Systems/HostApp.cs index df7d20989..5393e6af1 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/HostApp.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/HostApp.cs @@ -73,15 +73,19 @@ public List AllSelectedSystemConfigurationVariants } set { - _allSelectedSystemConfigurationVariants = value; - OnAfterAllSystemConfigurationVariantsChanged(); + SetAllSelectedSystemConfigurationVariants(value); } } private SystemRunner? _systemRunner = null; public SystemRunner? CurrentSystemRunner => _systemRunner; public ISystem? CurrentRunningSystem => _systemRunner?.System; - public IScreen? CurrentSystemScreenInfo => _systemRunner != null ? _systemRunner?.System.Screen : _selectedSystemTemporary?.Screen; + public IScreen? CurrentSystemScreenInfo => _systemRunner != null + ? _systemRunner.System.Screen + : _selectedSystemTemporary?.Screen + ?? (_currentHostSystemConfig != null + ? _systemList.GetScreenInfo(_selectedSystemName, _selectedSystemConfigurationVariant, _currentHostSystemConfig) + : null); public EmulatorState EmulatorState { get @@ -437,19 +441,16 @@ public async Task SelectSystem(string systemName) _selectedSystemName = systemName; CurrentHostSystemConfig = await _systemList.GetHostSystemConfig(_selectedSystemName); - OnAfterSelectedSystemChanged(); - - SelectedSystemChanged?.Invoke(this, EventArgs.Empty); - _scriptingEngine.InvokeEvent("on_system_selected", SelectedSystemName); - // If system changed, make sure any state regarding the system variant is also in sync if (systemChanged) { - AllSelectedSystemConfigurationVariants = await _systemList.GetSystemConfigurationVariants(systemName, CurrentHostSystemConfig); + SetAllSelectedSystemConfigurationVariants( + await _systemList.GetSystemConfigurationVariants(systemName, CurrentHostSystemConfig), + notify: false); if (_allSelectedSystemConfigurationVariants.Count > 0) { - var selectedSystemConfigurationVariant = _allSelectedSystemConfigurationVariants.First(); - await SelectSystemConfigurationVariant(selectedSystemConfigurationVariant); + _selectedSystemConfigurationVariant = _allSelectedSystemConfigurationVariants.First(); + await UpdateSelectedSystemTemporary(); } else { @@ -466,10 +467,26 @@ public async Task SelectSystem(string systemName) _selectedSystemTemporary = null; } } + + OnAfterAllSystemConfigurationVariantsChanged(); + OnAfterSelectedSystemVariantChanged(); + OnAfterSelectedSystemChanged(); + + SelectedSystemVariantChanged?.Invoke(this, EventArgs.Empty); + SelectedSystemChanged?.Invoke(this, EventArgs.Empty); + _scriptingEngine.InvokeEvent("on_variant_selected", SelectedSystemConfigurationVariant); + _scriptingEngine.InvokeEvent("on_system_selected", SelectedSystemName); } public virtual void OnAfterAllSystemConfigurationVariantsChanged() { } + private void SetAllSelectedSystemConfigurationVariants(List variants, bool notify = true) + { + _allSelectedSystemConfigurationVariants = variants; + if (notify) + OnAfterAllSystemConfigurationVariantsChanged(); + } + public async Task SelectSystemConfigurationVariant(string configurationVariant) { if (EmulatorState != EmulatorState.Uninitialized) @@ -479,7 +496,16 @@ public async Task SelectSystemConfigurationVariant(string configurationVariant) throw new DotNet6502Exception($"System configuration variant not found: {configurationVariant}"); _selectedSystemConfigurationVariant = configurationVariant; + await UpdateSelectedSystemTemporary(); + + OnAfterSelectedSystemVariantChanged(); + SelectedSystemVariantChanged?.Invoke(this, EventArgs.Empty); + _scriptingEngine.InvokeEvent("on_variant_selected", SelectedSystemConfigurationVariant); + } + + private async Task UpdateSelectedSystemTemporary() + { // Pre-create a temporary variable to contain the system if it is valid. // This is useful if the system has not been started yet, but client requests the system object. if (CurrentHostSystemConfig.IsValid(out _)) @@ -490,11 +516,6 @@ public async Task SelectSystemConfigurationVariant(string configurationVariant) { _selectedSystemTemporary = null; } - - OnAfterSelectedSystemVariantChanged(); - - SelectedSystemVariantChanged?.Invoke(this, EventArgs.Empty); - _scriptingEngine.InvokeEvent("on_variant_selected", SelectedSystemConfigurationVariant); } public virtual void OnAfterSelectedSystemChanged() { } diff --git a/src/libraries/Highbyte.DotNet6502.Systems/ISystemConfigurer.cs b/src/libraries/Highbyte.DotNet6502.Systems/ISystemConfigurer.cs index a11c8a7a0..4a37b3d2d 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/ISystemConfigurer.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/ISystemConfigurer.cs @@ -4,6 +4,7 @@ public interface ISystemConfigurer { public string SystemName { get; } public Task> GetConfigurationVariants(ISystemConfig systemConfig); + public IScreen? GetScreenInfo(string configurationVariant, ISystemConfig systemConfig) => null; public Task BuildSystem(string configurationVariant, ISystemConfig systemConfig); public Task GetNewHostSystemConfig(); public Task PersistHostSystemConfig(IHostSystemConfig hostSystemConfig); diff --git a/src/libraries/Highbyte.DotNet6502.Systems/ScreenInfo.cs b/src/libraries/Highbyte.DotNet6502.Systems/ScreenInfo.cs new file mode 100644 index 000000000..4a4c5fed4 --- /dev/null +++ b/src/libraries/Highbyte.DotNet6502.Systems/ScreenInfo.cs @@ -0,0 +1,35 @@ +namespace Highbyte.DotNet6502.Systems; + +/// +/// Immutable screen geometry for cases where a host knows the selected system/variant but does +/// not yet have a built emulator instance. This lets UI code size the emulator display from +/// configuration-derived dimensions without forcing CPU, memory, ROM, or render setup. +/// +public sealed class ScreenInfo : IScreen +{ + /// + /// Creates a screen descriptor using drawable and visible pixel dimensions. + /// + public ScreenInfo( + int drawableAreaWidth, + int drawableAreaHeight, + int visibleWidth, + int visibleHeight, + float refreshFrequencyHz) + { + DrawableAreaWidth = drawableAreaWidth; + DrawableAreaHeight = drawableAreaHeight; + VisibleWidth = visibleWidth; + VisibleHeight = visibleHeight; + RefreshFrequencyHz = refreshFrequencyHz; + } + + public int DrawableAreaWidth { get; } + public int DrawableAreaHeight { get; } + public int VisibleWidth { get; } + public int VisibleHeight { get; } + public bool HasBorder => VisibleWidth > DrawableAreaWidth || VisibleHeight > DrawableAreaHeight; + public int VisibleLeftRightBorderWidth => (VisibleWidth - DrawableAreaWidth) / 2; + public int VisibleTopBottomBorderHeight => (VisibleHeight - DrawableAreaHeight) / 2; + public float RefreshFrequencyHz { get; } +} diff --git a/src/libraries/Highbyte.DotNet6502.Systems/SystemList.cs b/src/libraries/Highbyte.DotNet6502.Systems/SystemList.cs index 55a7de65e..527e05697 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/SystemList.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/SystemList.cs @@ -89,6 +89,14 @@ public async Task BuildSystem(string systemName, string configurationVa return system; } + public IScreen? GetScreenInfo(string systemName, string configurationVariant, IHostSystemConfig hostSystemConfig) + { + if (!Systems.Contains(systemName)) + throw new DotNet6502Exception($"System does not exist: {systemName}"); + + return _systemConfigurers[systemName].GetScreenInfo(configurationVariant, hostSystemConfig.SystemConfig); + } + public async Task BuildSystemRunner( string systemName, string configurationVariant) diff --git a/tests/Highbyte.DotNet6502.Systems.Tests/HostAppTests.cs b/tests/Highbyte.DotNet6502.Systems.Tests/HostAppTests.cs index 87a4efae2..7d123ac29 100644 --- a/tests/Highbyte.DotNet6502.Systems.Tests/HostAppTests.cs +++ b/tests/Highbyte.DotNet6502.Systems.Tests/HostAppTests.cs @@ -137,6 +137,43 @@ public async Task UpdateHostSystemConfig_WhenConfigBecomesValid_RebuildsTemporar Assert.NotNull(systemAfterFixup); } + [Fact] + public async Task CurrentSystemScreenInfo_WhenSelectedSystemCannotBeBuilt_UsesConfigurerScreenInfo() + { + // Arrange: select a system, then make config invalid so no temporary system can be built. + var testApp = BuildTestHostApp(); + await testApp.SelectSystem(TestSystem.SystemName); + testApp.UpdateHostSystemConfig(new TestHostSystemConfig { TestIsValid = false }); + + // Act + var screenInfo = testApp.CurrentSystemScreenInfo; + + // Assert + Assert.NotNull(screenInfo); + Assert.Equal(123, screenInfo.VisibleWidth); + Assert.Equal(67, screenInfo.VisibleHeight); + } + + [Fact] + public async Task SelectSystem_WhenPreviousSystemIsInvalid_UpdatesVariantBeforeSystemChangedNotification() + { + // Arrange: the first system is selected but cannot be built, so screen info must come from + // the selected system/variant rather than from a temporary system instance. + var testApp = BuildTestHostApp(); + await testApp.SelectSystem(TestSystem.SystemName); + testApp.UpdateHostSystemConfig(new TestHostSystemConfig { TestIsValid = false }); + testApp.ReadScreenInfoOnSelectedSystemChanged = true; + + // Act: switch to a system whose GetScreenInfo rejects stale variants. + await testApp.SelectSystem(TestSystem2.SystemName); + + // Assert: reading screen info during the selected-system notification saw the new variant. + Assert.Equal(TestSystem2Configurer.Variant, testApp.SelectedSystemConfigurationVariant); + Assert.NotNull(testApp.ScreenInfoReadDuringSelectedSystemChanged); + Assert.Equal(246, testApp.ScreenInfoReadDuringSelectedSystemChanged.VisibleWidth); + Assert.Equal(134, testApp.ScreenInfoReadDuringSelectedSystemChanged.VisibleHeight); + } + public class TestHostApp : HostApp { public TestHostApp( @@ -144,6 +181,17 @@ SystemList systemList ) : base("TestHost", systemList, new NullLoggerFactory()) { } + + public bool ReadScreenInfoOnSelectedSystemChanged { get; set; } + public IScreen? ScreenInfoReadDuringSelectedSystemChanged { get; private set; } + + public override void OnAfterSelectedSystemChanged() + { + if (ReadScreenInfoOnSelectedSystemChanged) + ScreenInfoReadDuringSelectedSystemChanged = CurrentSystemScreenInfo; + + base.OnAfterSelectedSystemChanged(); + } } private TestHostApp BuildTestHostApp(bool setContexts = true, bool initContexts = true) diff --git a/tests/Highbyte.DotNet6502.Systems.Tests/SystemConfigurerTests.cs b/tests/Highbyte.DotNet6502.Systems.Tests/SystemConfigurerTests.cs index b06a8b664..12cecb978 100644 --- a/tests/Highbyte.DotNet6502.Systems.Tests/SystemConfigurerTests.cs +++ b/tests/Highbyte.DotNet6502.Systems.Tests/SystemConfigurerTests.cs @@ -23,6 +23,9 @@ public class TestSystemConfigurer : ISystemConfigurer public string SystemName => TestSystem.SystemName; public Task> GetConfigurationVariants(ISystemConfig systemConfig) => Task.FromResult(new List { "DEFAULT" }); + public IScreen? GetScreenInfo(string configurationVariant, ISystemConfig systemConfig) + => new ScreenInfo(100, 50, 123, 67, 60); + public Task GetNewHostSystemConfig() { return Task.FromResult(new TestHostSystemConfig()); @@ -51,7 +54,13 @@ IHostSystemConfig hostSystemConfig public class TestSystem2Configurer : ISystemConfigurer { public string SystemName => TestSystem2.SystemName; - public Task> GetConfigurationVariants(ISystemConfig systemConfig) => Task.FromResult(new List { "DEFAULT" }); + public const string Variant = "TEST2"; + public Task> GetConfigurationVariants(ISystemConfig systemConfig) => Task.FromResult(new List { Variant }); + + public IScreen? GetScreenInfo(string configurationVariant, ISystemConfig systemConfig) + => configurationVariant == Variant + ? new ScreenInfo(200, 100, 246, 134, 60) + : throw new ArgumentException($"Unexpected variant '{configurationVariant}'.", nameof(configurationVariant)); public Task GetNewHostSystemConfig() { diff --git a/tests/Highbyte.DotNet6502.Systems.Tests/SystemRunnerTests.cs b/tests/Highbyte.DotNet6502.Systems.Tests/SystemRunnerTests.cs index 4d8891af3..8f9a732b7 100644 --- a/tests/Highbyte.DotNet6502.Systems.Tests/SystemRunnerTests.cs +++ b/tests/Highbyte.DotNet6502.Systems.Tests/SystemRunnerTests.cs @@ -77,7 +77,7 @@ public class TestSystem : ISystem public Memory Mem => throw new NotImplementedException(); - public IScreen Screen => throw new NotImplementedException(); + public IScreen Screen => new ScreenInfo(100, 50, 123, 67, 60); public bool InstrumentationEnabled { get; set; } = false; @@ -112,7 +112,7 @@ public class TestSystem2 : ISystem public Memory Mem => throw new NotImplementedException(); - public IScreen Screen => throw new NotImplementedException(); + public IScreen Screen => new ScreenInfo(200, 100, 246, 134, 60); public bool InstrumentationEnabled { get; set; } = false;