diff --git a/Promete.Test/ScenelessExampleTests.cs b/Promete.Test/ScenelessExampleTests.cs
new file mode 100644
index 0000000..c51ac96
--- /dev/null
+++ b/Promete.Test/ScenelessExampleTests.cs
@@ -0,0 +1,53 @@
+using Promete.Headless;
+using Promete.Input;
+using Promete.Windowing;
+using System.Reflection;
+
+namespace Promete.Test;
+
+///
+/// Issue で示されたコード例と同様のテスト。
+/// シーンを使わずに PrometeApp を実行できることを確認する。
+///
+public class ScenelessExampleTests
+{
+ [Fact]
+ public void Example_ScenelessHelloWorld_ShouldHaveRequiredMethods()
+ {
+ // Arrange - Issue で示されたコード例と同様のパターン
+ var app = PrometeApp.Create()
+ .Use()
+ .Use()
+ .BuildWithHeadless();
+
+ var keyboard = app.GetPlugin();
+ var console = app.GetPlugin();
+
+ // Window イベントにハンドラを登録できることを確認
+ app.Window.Start += () =>
+ {
+ console.Print("Hello, world!");
+ };
+
+ app.Window.Update += () =>
+ {
+ // Update logic would go here
+ };
+
+ // Run() メソッドが存在することを確認
+ var runMethods = typeof(PrometeApp).GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .Where(m => m.Name == "Run" && m.GetParameters().Length == 0 && !m.IsGenericMethod);
+ Assert.Single(runMethods);
+
+ var runMethodsWithOpts = typeof(PrometeApp).GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .Where(m => m.Name == "Run" &&
+ m.GetParameters().Length == 1 &&
+ m.GetParameters()[0].ParameterType == typeof(WindowOptions) &&
+ !m.IsGenericMethod);
+ Assert.Single(runMethodsWithOpts);
+
+ // プラグインが正しく取得できることを確認
+ Assert.NotNull(keyboard);
+ Assert.NotNull(console);
+ }
+}
diff --git a/Promete.Test/ScenelessRunTests.cs b/Promete.Test/ScenelessRunTests.cs
new file mode 100644
index 0000000..1e0adab
--- /dev/null
+++ b/Promete.Test/ScenelessRunTests.cs
@@ -0,0 +1,62 @@
+using FluentAssertions;
+using Promete.Headless;
+using System.Reflection;
+
+namespace Promete.Test;
+
+public class ScenelessRunTests
+{
+ ///
+ /// DefaultScene の型を取得するヘルパーメソッド
+ ///
+ private static Type GetDefaultSceneType()
+ {
+ return typeof(PrometeApp).GetNestedType("DefaultScene", BindingFlags.NonPublic)!;
+ }
+
+ [Fact]
+ public void Run_WithoutScene_ShouldHaveValidRoot()
+ {
+ // Arrange
+ var app = PrometeApp.Create()
+ .BuildWithHeadless();
+
+ // OnStartを手動で呼び出してDefaultSceneをロード
+ var onStartMethod = typeof(PrometeApp).GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Instance)!;
+ var genericOnStartMethod = onStartMethod.MakeGenericMethod(GetDefaultSceneType());
+ genericOnStartMethod.Invoke(app, null);
+
+ // Assert
+ app.Root.Should().NotBeNull("Root container should be initialized even without explicit scene");
+ }
+
+ [Fact]
+ public void GetPlugin_ShouldWork_WhenRunningWithoutScene()
+ {
+ // Arrange
+ var app = PrometeApp.Create()
+ .Use()
+ .BuildWithHeadless();
+
+ // Act
+ var console = app.GetPlugin();
+
+ // Assert
+ console.Should().NotBeNull("Plugins should be accessible even when running without explicit scene");
+ }
+
+ [Fact]
+ public void DefaultScene_ShouldBeRegistered()
+ {
+ // Arrange
+ var app = PrometeApp.Create()
+ .BuildWithHeadless();
+
+ // Act - DefaultSceneのロードを試みる
+ var loadSceneMethod = typeof(PrometeApp).GetMethod("LoadScene", BindingFlags.Public | BindingFlags.Instance, [typeof(Type)])!;
+
+ // Assert - 例外がスローされないことを確認
+ var act = () => loadSceneMethod.Invoke(app, [GetDefaultSceneType()]);
+ act.Should().NotThrow("DefaultScene should be registered and loadable");
+ }
+}
diff --git a/Promete/PrometeApp.cs b/Promete/PrometeApp.cs
index d6a45a9..8559a0b 100644
--- a/Promete/PrometeApp.cs
+++ b/Promete/PrometeApp.cs
@@ -136,6 +136,25 @@ public int Run(WindowOptions opts) where TScene : Scene
return _statusCode;
}
+ ///
+ /// Promete アプリケーションをシーンなしで実行します。
+ ///
+ /// 終了ステータスコード。
+ public int Run()
+ {
+ return Run(WindowOptions.Default);
+ }
+
+ ///
+ /// Promete アプリケーションをシーンなしで実行します。
+ ///
+ /// ウィンドウのオプション。
+ /// 終了ステータスコード。
+ public int Run(WindowOptions opts)
+ {
+ return Run(opts);
+ }
+
///
/// 指定したステータスコードで Promete アプリケーションを終了します。
///
@@ -379,6 +398,9 @@ private void ProcessNextFrameQueue()
private void RegisterAllScenes()
{
+ // DefaultScene を明示的に登録
+ _services.AddTransient();
+
var asm = Assembly.GetEntryAssembly() ?? throw new InvalidOperationException("There is no entry assembly.");
// Scene 派生クラスを全て取得する
var types = asm.GetTypes();
@@ -403,6 +425,14 @@ private Scene GetScene(Type scene)
///
public event Action? SceneWillChange;
+ ///
+ /// シーンを使用せずにアプリケーションを実行する際に使用されるデフォルトの空のシーン。
+ ///
+ [IgnoredScene]
+ private sealed class DefaultScene : Scene
+ {
+ }
+
///
/// Promete アプリケーションを構築するためのビルダークラスです。
///