diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..e90715f
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,16 @@
+
+
+
+
+ net9.0
+ 13.0
+ enable
+ enable
+
+
+ false
+
+
+
diff --git a/LinguaEngine.sln b/LinguaEngine.sln
new file mode 100644
index 0000000..05fbe3f
--- /dev/null
+++ b/LinguaEngine.sln
@@ -0,0 +1,121 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Kernel", "src\Engine.Kernel\Engine.Kernel.csproj", "{40297197-30BF-4180-88C9-916E66859E0F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Host", "src\Engine.Host\Engine.Host.csproj", "{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{07D57EEB-2F50-60C4-C011-FE4FA775C9A8}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sandbox.echo", "sandbox.echo", "{5277871D-FD82-4E0F-9994-47CB654DDE62}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandbox.Echo.Contracts", "plugins\sandbox.echo\Sandbox.Echo.Contracts\Sandbox.Echo.Contracts.csproj", "{6CDA3EB0-CC08-48E6-B946-183D31840661}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandbox.Echo", "plugins\sandbox.echo\Sandbox.Echo\Sandbox.Echo.csproj", "{B0F967C4-B801-4A53-829E-689C2728553A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Kernel.Tests", "tests\Engine.Kernel.Tests\Engine.Kernel.Tests.csproj", "{D7C61E35-8805-4C28-B3D7-084D8FBC6820}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.ConformanceHarness", "tests\Engine.ConformanceHarness\Engine.ConformanceHarness.csproj", "{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {40297197-30BF-4180-88C9-916E66859E0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Debug|x64.Build.0 = Debug|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Debug|x86.Build.0 = Debug|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Release|x64.ActiveCfg = Release|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Release|x64.Build.0 = Release|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Release|x86.ActiveCfg = Release|Any CPU
+ {40297197-30BF-4180-88C9-916E66859E0F}.Release|x86.Build.0 = Release|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|x64.Build.0 = Debug|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|x86.Build.0 = Debug|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|x64.ActiveCfg = Release|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|x64.Build.0 = Release|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|x86.ActiveCfg = Release|Any CPU
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|x86.Build.0 = Release|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|x64.Build.0 = Debug|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|x86.Build.0 = Debug|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|x64.ActiveCfg = Release|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|x64.Build.0 = Release|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|x86.ActiveCfg = Release|Any CPU
+ {6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|x86.Build.0 = Release|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Debug|x64.Build.0 = Debug|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Debug|x86.Build.0 = Debug|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Release|x64.ActiveCfg = Release|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Release|x64.Build.0 = Release|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Release|x86.ActiveCfg = Release|Any CPU
+ {B0F967C4-B801-4A53-829E-689C2728553A}.Release|x86.Build.0 = Release|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|x64.Build.0 = Debug|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|x86.Build.0 = Debug|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|x64.ActiveCfg = Release|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|x64.Build.0 = Release|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|x86.Build.0 = Release|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|x64.Build.0 = Debug|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|x86.Build.0 = Debug|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|x64.ActiveCfg = Release|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|x64.Build.0 = Release|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|x86.ActiveCfg = Release|Any CPU
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {40297197-30BF-4180-88C9-916E66859E0F} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
+ {A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
+ {5277871D-FD82-4E0F-9994-47CB654DDE62} = {07D57EEB-2F50-60C4-C011-FE4FA775C9A8}
+ {6CDA3EB0-CC08-48E6-B946-183D31840661} = {5277871D-FD82-4E0F-9994-47CB654DDE62}
+ {B0F967C4-B801-4A53-829E-689C2728553A} = {5277871D-FD82-4E0F-9994-47CB654DDE62}
+ {D7C61E35-8805-4C28-B3D7-084D8FBC6820} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ EndGlobalSection
+EndGlobal
diff --git a/plugins/sandbox.echo/Sandbox.Echo.Contracts/Ping.cs b/plugins/sandbox.echo/Sandbox.Echo.Contracts/Ping.cs
new file mode 100644
index 0000000..c301a16
--- /dev/null
+++ b/plugins/sandbox.echo/Sandbox.Echo.Contracts/Ping.cs
@@ -0,0 +1,13 @@
+using Engine.Kernel.World;
+
+namespace Sandbox.Echo.Contracts;
+
+///
+/// Minimal component for exercising the reload loop described in
+/// docs/kernel-contract.md §7 — not a real engine feature, just something
+/// for a system to touch before any real plugin exists.
+///
+public sealed class Ping : Component
+{
+ public int Count;
+}
diff --git a/plugins/sandbox.echo/Sandbox.Echo.Contracts/Sandbox.Echo.Contracts.csproj b/plugins/sandbox.echo/Sandbox.Echo.Contracts/Sandbox.Echo.Contracts.csproj
new file mode 100644
index 0000000..e4acf50
--- /dev/null
+++ b/plugins/sandbox.echo/Sandbox.Echo.Contracts/Sandbox.Echo.Contracts.csproj
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/plugins/sandbox.echo/Sandbox.Echo/EchoPlugin.cs b/plugins/sandbox.echo/Sandbox.Echo/EchoPlugin.cs
new file mode 100644
index 0000000..e8da0fb
--- /dev/null
+++ b/plugins/sandbox.echo/Sandbox.Echo/EchoPlugin.cs
@@ -0,0 +1,33 @@
+using Engine.Kernel.Plugins;
+using Engine.Kernel.Scheduling;
+using Engine.Kernel.World;
+using Sandbox.Echo.Contracts;
+
+namespace Sandbox.Echo;
+
+///
+/// M0's test fixture. Exists only to exercise the reload loop end to end —
+/// edit, rebuild, ALC reload, headless run, dump — before any real
+/// subsystem exists to test it against. See M0 in docs/kernel-contract.md §8.
+///
+public sealed class EchoPlugin : IPlugin
+{
+ public void Configure(IPluginContext ctx)
+ {
+ ctx.Schedule.Add(Stage.Update, Tick)
+ .Writes();
+
+ ctx.Log.Info("sandbox.echo configured");
+ }
+
+ public void Shutdown(IPluginContext ctx)
+ {
+ ctx.Schedule.RemoveAllFrom("sandbox.echo");
+ }
+
+ static void Tick(IWorld world)
+ {
+ // TODO(M0): bump every Ping.Count by one. Placeholder until the
+ // Scheduler actually exists to invoke this.
+ }
+}
diff --git a/plugins/sandbox.echo/Sandbox.Echo/Sandbox.Echo.csproj b/plugins/sandbox.echo/Sandbox.Echo/Sandbox.Echo.csproj
new file mode 100644
index 0000000..01983e7
--- /dev/null
+++ b/plugins/sandbox.echo/Sandbox.Echo/Sandbox.Echo.csproj
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/plugins/sandbox.echo/plugin.json b/plugins/sandbox.echo/plugin.json
new file mode 100644
index 0000000..f686ea8
--- /dev/null
+++ b/plugins/sandbox.echo/plugin.json
@@ -0,0 +1,8 @@
+{
+ "id": "sandbox.echo",
+ "version": "0.0.1",
+ "contracts": "Sandbox.Echo.Contracts.dll",
+ "assembly": "Sandbox.Echo.dll",
+ "dependsOn": {},
+ "reloadable": true
+}
diff --git a/samples/EmptyProject/project.json b/samples/EmptyProject/project.json
new file mode 100644
index 0000000..1b79ced
--- /dev/null
+++ b/samples/EmptyProject/project.json
@@ -0,0 +1,7 @@
+{
+ "engineVersion": "^0.1",
+ "plugins": [
+ { "id": "sandbox.echo" }
+ ],
+ "pluginPaths": []
+}
diff --git a/src/Engine.Host/Engine.Host.csproj b/src/Engine.Host/Engine.Host.csproj
new file mode 100644
index 0000000..7b13f52
--- /dev/null
+++ b/src/Engine.Host/Engine.Host.csproj
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+ Exe
+
+
+
diff --git a/src/Engine.Host/Program.cs b/src/Engine.Host/Program.cs
new file mode 100644
index 0000000..36b5ed8
--- /dev/null
+++ b/src/Engine.Host/Program.cs
@@ -0,0 +1,11 @@
+// The runtime host: resolves a project's plugins and runs the engine loop.
+// Its shape is sketched in docs/kernel-contract.md §7 —
+//
+// engine run --headless --frames 60 --scene ... --dump ... --assert ...
+// engine diag why-pinned
+//
+// Neither the argument parsing nor the loop it drives exists yet; both
+// depend on PluginHost and Scheduler, which are M0's actual work. This is
+// a placeholder so the solution builds and runs end to end.
+
+Console.WriteLine("Lingua Engine host — not yet implemented. See docs/kernel-contract.md, M0.");
diff --git a/src/Engine.Kernel/Diagnostics/ILogger.cs b/src/Engine.Kernel/Diagnostics/ILogger.cs
new file mode 100644
index 0000000..3ddac55
--- /dev/null
+++ b/src/Engine.Kernel/Diagnostics/ILogger.cs
@@ -0,0 +1,8 @@
+namespace Engine.Kernel.Diagnostics;
+
+public interface ILogger
+{
+ void Info(string message);
+ void Warn(string message);
+ void Error(string message);
+}
diff --git a/src/Engine.Kernel/Engine.Kernel.csproj b/src/Engine.Kernel/Engine.Kernel.csproj
new file mode 100644
index 0000000..c632161
--- /dev/null
+++ b/src/Engine.Kernel/Engine.Kernel.csproj
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Engine.Kernel/Events/IEventBus.cs b/src/Engine.Kernel/Events/IEventBus.cs
new file mode 100644
index 0000000..a37acab
--- /dev/null
+++ b/src/Engine.Kernel/Events/IEventBus.cs
@@ -0,0 +1,11 @@
+namespace Engine.Kernel.Events;
+
+///
+/// Open question (see the footer of docs/kernel-contract.md): whether this
+/// is needed at launch at all, or whether event-components in World cover
+/// its role. Left as a bare marker so IPluginContext compiles — no API
+/// decided yet.
+///
+public interface IEventBus
+{
+}
diff --git a/src/Engine.Kernel/Plugins/IPlugin.cs b/src/Engine.Kernel/Plugins/IPlugin.cs
new file mode 100644
index 0000000..f86b101
--- /dev/null
+++ b/src/Engine.Kernel/Plugins/IPlugin.cs
@@ -0,0 +1,17 @@
+namespace Engine.Kernel.Plugins;
+
+///
+/// A plugin holds no game state — none. State lives in World; the plugin
+/// is code that operates on it. See docs/kernel-contract.md §3.
+///
+public interface IPlugin
+{
+ /// Registration: services, systems, component types.
+ void Configure(IPluginContext ctx);
+
+ ///
+ /// Full undo of Configure. Whether this method is honest determines
+ /// whether the ALC unloads at all — see §4.
+ ///
+ void Shutdown(IPluginContext ctx);
+}
diff --git a/src/Engine.Kernel/Plugins/IPluginContext.cs b/src/Engine.Kernel/Plugins/IPluginContext.cs
new file mode 100644
index 0000000..a2a7730
--- /dev/null
+++ b/src/Engine.Kernel/Plugins/IPluginContext.cs
@@ -0,0 +1,16 @@
+using Engine.Kernel.Diagnostics;
+using Engine.Kernel.Events;
+using Engine.Kernel.Scheduling;
+using Engine.Kernel.Services;
+using Engine.Kernel.World;
+
+namespace Engine.Kernel.Plugins;
+
+public interface IPluginContext
+{
+ IWorld World { get; } // data
+ IServiceRegistry Services { get; } // Provide / Require
+ ISchedule Schedule { get; } // systems and ordering
+ IEventBus Events { get; }
+ ILogger Log { get; }
+}
diff --git a/src/Engine.Kernel/Plugins/PluginManifest.cs b/src/Engine.Kernel/Plugins/PluginManifest.cs
new file mode 100644
index 0000000..7d2993a
--- /dev/null
+++ b/src/Engine.Kernel/Plugins/PluginManifest.cs
@@ -0,0 +1,22 @@
+namespace Engine.Kernel.Plugins;
+
+///
+/// Deserialized shape of a plugin's plugin.json. Read *before*
+/// anything loads, so the Plugin Host can build the dependency graph ahead
+/// of any ALC loading — see docs/kernel-contract.md §3.
+///
+public sealed class PluginManifest
+{
+ public required string Id { get; init; }
+ public required string Version { get; init; }
+
+ /// Assembly loaded into the Default ALC. Never unloads. See §4.
+ public required string Contracts { get; init; }
+
+ /// Assembly loaded into a collectible ALC. Reloadable. See §4.
+ public required string Assembly { get; init; }
+
+ public Dictionary DependsOn { get; init; } = new();
+
+ public bool Reloadable { get; init; } = true;
+}
diff --git a/src/Engine.Kernel/Plugins/ProjectManifest.cs b/src/Engine.Kernel/Plugins/ProjectManifest.cs
new file mode 100644
index 0000000..9d91937
--- /dev/null
+++ b/src/Engine.Kernel/Plugins/ProjectManifest.cs
@@ -0,0 +1,25 @@
+namespace Engine.Kernel.Plugins;
+
+///
+/// Deserialized shape of a project's project.json — which plugins a
+/// specific game loads, at which versions, and where to find its own. See
+/// "Per-project configuration" in docs/kernel-contract.md §2.
+///
+public sealed class ProjectManifest
+{
+ public required string EngineVersion { get; init; }
+
+ public List Plugins { get; init; } = new();
+
+ /// Search paths for plugins local to this project, not shipped
+ /// with the engine.
+ public List PluginPaths { get; init; } = new();
+}
+
+public sealed class PluginReference
+{
+ public required string Id { get; init; }
+
+ /// Null means "whatever the engine ships by default."
+ public string? Version { get; init; }
+}
diff --git a/src/Engine.Kernel/Scheduling/ISchedule.cs b/src/Engine.Kernel/Scheduling/ISchedule.cs
new file mode 100644
index 0000000..a28ff58
--- /dev/null
+++ b/src/Engine.Kernel/Scheduling/ISchedule.cs
@@ -0,0 +1,13 @@
+namespace Engine.Kernel.Scheduling;
+
+///
+/// Frame stages and system ordering. See docs/kernel-contract.md §2.
+///
+public interface ISchedule
+{
+ ISystemBuilder Add(Stage stage, Delegate system);
+
+ /// Called from a plugin's Shutdown() — must remove everything
+ /// Configure() added, or the ALC it lives in will never unload. See §4.
+ void RemoveAllFrom(string pluginId);
+}
diff --git a/src/Engine.Kernel/Scheduling/ISystemBuilder.cs b/src/Engine.Kernel/Scheduling/ISystemBuilder.cs
new file mode 100644
index 0000000..5c61590
--- /dev/null
+++ b/src/Engine.Kernel/Scheduling/ISystemBuilder.cs
@@ -0,0 +1,16 @@
+using Engine.Kernel.World;
+
+namespace Engine.Kernel.Scheduling;
+
+///
+/// Fluent builder returned by . Declared
+/// Reads/Writes are what lets the scheduler run systems with disjoint
+/// access in parallel and, in debug builds, enforce that a system only
+/// touches what it declared. See docs/kernel-contract.md §2 and §7.
+///
+public interface ISystemBuilder
+{
+ ISystemBuilder After(string systemId);
+ ISystemBuilder Reads() where T : Component;
+ ISystemBuilder Writes() where T : Component;
+}
diff --git a/src/Engine.Kernel/Scheduling/Stage.cs b/src/Engine.Kernel/Scheduling/Stage.cs
new file mode 100644
index 0000000..0f54625
--- /dev/null
+++ b/src/Engine.Kernel/Scheduling/Stage.cs
@@ -0,0 +1,12 @@
+namespace Engine.Kernel.Scheduling;
+
+///
+/// Open question (see the footer of docs/kernel-contract.md): whether the
+/// set of stages is fixed or plugin-extensible. This is only enough to
+/// make the §3 example compile — not a decision.
+///
+public enum Stage
+{
+ Update,
+ Render,
+}
diff --git a/src/Engine.Kernel/Services/IServiceRegistry.cs b/src/Engine.Kernel/Services/IServiceRegistry.cs
new file mode 100644
index 0000000..85b8984
--- /dev/null
+++ b/src/Engine.Kernel/Services/IServiceRegistry.cs
@@ -0,0 +1,13 @@
+namespace Engine.Kernel.Services;
+
+///
+/// The control-plane channel between plugins: commands and resources, not
+/// per-entity data. See the "two channels" table in docs/kernel-contract.md
+/// §2 — and the rule right below it about what never belongs here.
+///
+public interface IServiceRegistry
+{
+ void Provide(T instance) where T : class;
+ T Require() where T : class;
+ void Revoke() where T : class;
+}
diff --git a/src/Engine.Kernel/World/Component.cs b/src/Engine.Kernel/World/Component.cs
new file mode 100644
index 0000000..70e7d68
--- /dev/null
+++ b/src/Engine.Kernel/World/Component.cs
@@ -0,0 +1,11 @@
+namespace Engine.Kernel.World;
+
+///
+/// Base class for all component data. Plain fields, no methods, no
+/// lifecycle hooks — behavior lives in systems, never on the component
+/// itself. See docs/kernel-contract.md §1 and §7.
+///
+public abstract class Component
+{
+ // Intentionally empty. A subclass adds fields only.
+}
diff --git a/src/Engine.Kernel/World/GameObject.cs b/src/Engine.Kernel/World/GameObject.cs
new file mode 100644
index 0000000..a19ac8e
--- /dev/null
+++ b/src/Engine.Kernel/World/GameObject.cs
@@ -0,0 +1,28 @@
+namespace Engine.Kernel.World;
+
+///
+/// The entity type: identity, hierarchy, and a list of components. See
+/// docs/kernel-contract.md §2.
+///
+public sealed class GameObject
+{
+ public required string Name { get; set; }
+
+ public Transform Transform;
+
+ public GameObject? Parent { get; internal set; }
+
+ // TODO(M0): backing storage for children, tags, and the component list
+ // + type index that makes World.Query() O(matches), not O(all).
+
+ public IReadOnlyList Children => throw new NotImplementedException();
+
+ public T? GetComponent() where T : Component
+ => throw new NotImplementedException();
+
+ public T AddComponent() where T : Component, new()
+ => throw new NotImplementedException();
+
+ public void RemoveComponent() where T : Component
+ => throw new NotImplementedException();
+}
diff --git a/src/Engine.Kernel/World/IWorld.cs b/src/Engine.Kernel/World/IWorld.cs
new file mode 100644
index 0000000..08b17c5
--- /dev/null
+++ b/src/Engine.Kernel/World/IWorld.cs
@@ -0,0 +1,19 @@
+namespace Engine.Kernel.World;
+
+///
+/// Kernel-owned storage for every in a scene.
+/// Shape sketched from its usage throughout docs/kernel-contract.md — not a
+/// final API; M0's job is to actually implement this.
+///
+public interface IWorld
+{
+ GameObject CreateGameObject(string name);
+
+ void Destroy(GameObject go);
+
+ /// Type-indexed lookup — O(matches), not O(all). See §2.
+ IEnumerable Query() where T : Component;
+
+ // TODO(§5): Snapshot()/Restore() for Play mode — a deep clone of the
+ // GameObject graph, taken on EnterPlay and discarded on ExitPlay.
+}
diff --git a/src/Engine.Kernel/World/Transform.cs b/src/Engine.Kernel/World/Transform.cs
new file mode 100644
index 0000000..af8af1b
--- /dev/null
+++ b/src/Engine.Kernel/World/Transform.cs
@@ -0,0 +1,18 @@
+using System.Numerics;
+
+namespace Engine.Kernel.World;
+
+///
+/// Embedded directly on rather than modeled as a
+/// subclass, because nearly every system touches it
+/// every frame — see the kernel scope table in docs/kernel-contract.md §2.
+///
+public struct Transform
+{
+ public Vector3 LocalPosition;
+ public Quaternion LocalRotation;
+ public Vector3 LocalScale;
+
+ // TODO(M0): derive from the GameObject hierarchy once World can walk it.
+ public readonly Matrix4x4 WorldMatrix => throw new NotImplementedException();
+}
diff --git a/tests/Engine.ConformanceHarness/AlcUnloadTests.cs b/tests/Engine.ConformanceHarness/AlcUnloadTests.cs
new file mode 100644
index 0000000..33d1fca
--- /dev/null
+++ b/tests/Engine.ConformanceHarness/AlcUnloadTests.cs
@@ -0,0 +1,24 @@
+namespace Engine.ConformanceHarness;
+
+///
+/// The test docs/kernel-contract.md §4 calls the one thing that keeps this
+/// architecture from slowly degrading: load and unload a plugin 200 times,
+/// and after every cycle verify the ALC actually collected. Runs against
+/// Sandbox.Echo — see the ReferenceOutputAssembly="false" note in this
+/// project's .csproj for why that reference doesn't link its types in.
+///
+public class AlcUnloadTests
+{
+ // TODO(M0): once PluginHost exists —
+ // for (int i = 0; i < 200; i++) {
+ // var handle = host.Load("sandbox.echo");
+ // var weakAlc = host.Unload(handle);
+ // GC.Collect(); GC.WaitForPendingFinalizers();
+ // Assert.False(weakAlc.IsAlive, $"ALC survived cycle {i}");
+ // }
+ // and assert working-set memory hasn't grown beyond noise.
+ [Fact(Skip = "PluginHost has no implementation yet — see M0 in docs/kernel-contract.md §8.")]
+ public void Plugin_Survives_200_Load_Unload_Cycles()
+ {
+ }
+}
diff --git a/tests/Engine.ConformanceHarness/Engine.ConformanceHarness.csproj b/tests/Engine.ConformanceHarness/Engine.ConformanceHarness.csproj
new file mode 100644
index 0000000..938b1e7
--- /dev/null
+++ b/tests/Engine.ConformanceHarness/Engine.ConformanceHarness.csproj
@@ -0,0 +1,29 @@
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Engine.Kernel.Tests/Engine.Kernel.Tests.csproj b/tests/Engine.Kernel.Tests/Engine.Kernel.Tests.csproj
new file mode 100644
index 0000000..b3078b1
--- /dev/null
+++ b/tests/Engine.Kernel.Tests/Engine.Kernel.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Engine.Kernel.Tests/WorldTests.cs b/tests/Engine.Kernel.Tests/WorldTests.cs
new file mode 100644
index 0000000..f452e4f
--- /dev/null
+++ b/tests/Engine.Kernel.Tests/WorldTests.cs
@@ -0,0 +1,14 @@
+namespace Engine.Kernel.Tests;
+
+public class WorldTests
+{
+ // TODO(M0): once IWorld has a concrete implementation —
+ // - GameObject hierarchy (parent/children) behaves correctly
+ // - Query() returns exactly the GameObjects holding a T, and is
+ // O(matches) — see the World row in docs/kernel-contract.md §2
+ // - AddComponent/RemoveComponent keep the type index consistent
+ [Fact(Skip = "World has no implementation yet — see M0 in docs/kernel-contract.md §8.")]
+ public void Placeholder()
+ {
+ }
+}