diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25bda40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +bin/ +obj/ +artifacts/ +*.user +*.suo +.vs/ +.idea/ diff --git a/Aquarium.Engine.slnx b/Aquarium.Engine.slnx new file mode 100644 index 0000000..2eb83ff --- /dev/null +++ b/Aquarium.Engine.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/README.md b/README.md index 40176cb..d1cfa08 100644 --- a/README.md +++ b/README.md @@ -30,3 +30,20 @@ Aquarium-first rather than framework-first. Create the smallest C# executable that opens a window, owns the camera/Grid invariant, and renders one sun plus orbiting planets with diegetic labels. Everything else earns its way back in. + +## Run + +```powershell +dotnet build Aquarium.Engine.slnx +dotnet run --project src\Aquarium.Engine\Aquarium.Engine.csproj +``` + +Headless smoke: + +```powershell +dotnet run --project src\Aquarium.Engine\Aquarium.Engine.csproj -- --headless +``` + +The first cut uses Stride as a NuGet-hosted shell through +`Stride.CommunityToolkit.Windows`. Stride is scaffolding; the Aquarium owns the +camera/Grid invariant and the renderer direction. See `docs/stride-boundary.md`. diff --git a/docs/stride-boundary.md b/docs/stride-boundary.md new file mode 100644 index 0000000..4110b5f --- /dev/null +++ b/docs/stride-boundary.md @@ -0,0 +1,38 @@ +# Stride Boundary + +Stride enters Aquarium as a package dependency, not as vendored engine source. + +## What Stride Provides + +- Window/device/input scaffolding. +- Game loop hosting. +- Asset and graphics infrastructure where it helps. +- A place to insert Aquarium-owned renderer passes. + +## What Aquarium Owns + +- Camera/Grid invariants. +- World model and simulation state. +- CultNet and CultCache boundaries. +- Renderer architecture and field ownership. +- Diegetic UI grammar. +- Debug command surface. + +## First Invariant + +The camera target is a point on global XY. The Grid center is that same point. +Grid radius is derived from camera zoom distance, not from camera pitch/yaw or +screen projection. Orbiting the camera must not resize the Grid. + +Current code expresses this through `OrbitCameraRig` and `GridFrame`. + +## Current Warnings + +`Stride.CommunityToolkit.Windows` brings Stride's asset/compiler toolchain into +the restore graph. The first restore/build currently reports transitive NuGet +advisories for `Microsoft.Build.Tasks.Core`, `NuGet.Packaging`, and +`NuGet.Protocol`, plus Stride's default missing `GameSettings` warning. These +are dependency/tooling warnings, not Aquarium code warnings. + +Do not hide them globally yet. Track whether newer Stride/Toolkit packages clear +the advisories before adding warning suppressions. diff --git a/global.json b/global.json new file mode 100644 index 0000000..99b1511 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "10.0.202", + "rollForward": "latestFeature" + } +} diff --git a/src/Aquarium.Engine/Aquarium.Engine.csproj b/src/Aquarium.Engine/Aquarium.Engine.csproj new file mode 100644 index 0000000..9c2e394 --- /dev/null +++ b/src/Aquarium.Engine/Aquarium.Engine.csproj @@ -0,0 +1,14 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + diff --git a/src/Aquarium.Engine/AquariumHost.cs b/src/Aquarium.Engine/AquariumHost.cs new file mode 100644 index 0000000..4e33cab --- /dev/null +++ b/src/Aquarium.Engine/AquariumHost.cs @@ -0,0 +1,26 @@ +using Stride.CommunityToolkit.Engine; +using Stride.Engine; +using Stride.Games; + +namespace Aquarium.Engine; + +public static class AquariumHost +{ + public static int Run(string[] args) + { + using var game = new Game(); + var runtime = new AquariumRuntime(new AquariumRuntimeOptions(ParseHeadless(args))); + + game.Run( + context: null, + start: runtime.Start, + update: runtime.Update); + + return 0; + } + + private static bool ParseHeadless(IEnumerable args) + { + return args.Any(arg => string.Equals(arg, "--headless", StringComparison.OrdinalIgnoreCase)); + } +} diff --git a/src/Aquarium.Engine/AquariumRuntime.cs b/src/Aquarium.Engine/AquariumRuntime.cs new file mode 100644 index 0000000..2e3254b --- /dev/null +++ b/src/Aquarium.Engine/AquariumRuntime.cs @@ -0,0 +1,49 @@ +using Stride.Core.Mathematics; +using Stride.Engine; +using Stride.Games; + +namespace Aquarium.Engine; + +public sealed class AquariumRuntime +{ + private readonly AquariumRuntimeOptions options; + private readonly OrbitCameraRig cameraRig; + + private GridFrame gridFrame; + + public AquariumRuntime(AquariumRuntimeOptions options) + { + this.options = options; + cameraRig = new OrbitCameraRig( + target: Vector2.Zero, + yawRadians: MathUtil.DegreesToRadians(35.0f), + pitchRadians: MathUtil.DegreesToRadians(42.0f), + distance: 24.0f); + gridFrame = GridFrame.FromCamera(cameraRig); + } + + public void Start(Scene rootScene) + { + _ = rootScene; + Console.WriteLine("Aquarium Engine booted."); + Console.WriteLine($"Grid center: {gridFrame.Center}, radius: {gridFrame.Radius:0.00}"); + Console.WriteLine("Stride is present as the host shell; Aquarium owns the invariants."); + } + + public void Update(Scene rootScene, GameTime gameTime) + { + _ = rootScene; + _ = gameTime; + + gridFrame = GridFrame.FromCamera(cameraRig); + + if (options.Headless) + { + Console.WriteLine("Headless smoke reached first update."); + Environment.ExitCode = 0; + Environment.Exit(0); + } + } +} + +public readonly record struct AquariumRuntimeOptions(bool Headless); diff --git a/src/Aquarium.Engine/Program.cs b/src/Aquarium.Engine/Program.cs new file mode 100644 index 0000000..d88c1aa --- /dev/null +++ b/src/Aquarium.Engine/Program.cs @@ -0,0 +1,3 @@ +using Aquarium.Engine; + +return AquariumHost.Run(args); diff --git a/src/Aquarium.Engine/World/GridFrame.cs b/src/Aquarium.Engine/World/GridFrame.cs new file mode 100644 index 0000000..d976ef6 --- /dev/null +++ b/src/Aquarium.Engine/World/GridFrame.cs @@ -0,0 +1,11 @@ +using Stride.Core.Mathematics; + +namespace Aquarium.Engine; + +public readonly record struct GridFrame(Vector2 Center, float Radius) +{ + public static GridFrame FromCamera(OrbitCameraRig camera) + { + return new GridFrame(camera.Target, camera.Distance); + } +} diff --git a/src/Aquarium.Engine/World/OrbitCameraRig.cs b/src/Aquarium.Engine/World/OrbitCameraRig.cs new file mode 100644 index 0000000..fa3934f --- /dev/null +++ b/src/Aquarium.Engine/World/OrbitCameraRig.cs @@ -0,0 +1,53 @@ +using Stride.Core.Mathematics; + +namespace Aquarium.Engine; + +public sealed class OrbitCameraRig +{ + public OrbitCameraRig(Vector2 target, float yawRadians, float pitchRadians, float distance) + { + Target = target; + YawRadians = yawRadians; + PitchRadians = pitchRadians; + Distance = MathF.Max(1.0f, distance); + } + + public Vector2 Target { get; private set; } + + public float YawRadians { get; private set; } + + public float PitchRadians { get; private set; } + + public float Distance { get; private set; } + + public Vector3 Position + { + get + { + var horizontal = Distance * MathF.Cos(PitchRadians); + var x = Target.X + horizontal * MathF.Cos(YawRadians); + var y = Target.Y + horizontal * MathF.Sin(YawRadians); + var z = Distance * MathF.Sin(PitchRadians); + return new Vector3(x, y, z); + } + } + + public void Orbit(float yawDeltaRadians, float pitchDeltaRadians) + { + YawRadians += yawDeltaRadians; + PitchRadians = MathUtil.Clamp( + PitchRadians + pitchDeltaRadians, + MathUtil.DegreesToRadians(5.0f), + MathUtil.DegreesToRadians(85.0f)); + } + + public void Zoom(float wheelSteps) + { + Distance = MathF.Max(1.0f, Distance * MathF.Pow(0.92f, wheelSteps)); + } + + public void Pan(Vector2 gridDelta) + { + Target += gridDelta; + } +}