diff --git a/.gitignore b/.gitignore index e82f6272d..50e57f2ed 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,12 @@ ExportedObj/ # Per-user editor state /[Uu]ser[Ss]ettings/ +# .NET build output outside Unity +/Aetheria.Shared/bin/ +/Aetheria.Shared/obj/ +/tools/**/bin/ +/tools/**/obj/ + # Unity3D generated meta files *.pidb.meta *.pdb.meta diff --git a/Aetheria.Shared/Aetheria.Shared.csproj b/Aetheria.Shared/Aetheria.Shared.csproj new file mode 100644 index 000000000..b546290bd --- /dev/null +++ b/Aetheria.Shared/Aetheria.Shared.csproj @@ -0,0 +1,35 @@ + + + + + netstandard2.1 + 9.0 + disable + disable + true + false + + $(DefineConstants);UniRxLibrary + + $(NoWarn);CS0108;CS0114;CS0162;CS0168;CS0169;CS0219;CS0414;CS0618;CS0649;CS8321;CS8981;SYSLIB0021 + $(MSBuildThisFileDirectory)..\Assets\ + + $(MSBuildThisFileDirectory)..\Library\PackageCache\com.unity.mathematics@*\Unity.Mathematics\ + + + + + + + + + + + + + + + + diff --git a/docs/three-gates-scope.md b/docs/three-gates-scope.md index e9e5452ed..a99c86073 100644 --- a/docs/three-gates-scope.md +++ b/docs/three-gates-scope.md @@ -59,9 +59,12 @@ AI share it, so AI accuracy is no longer superhuman by construction. pickup (`ShieldManager`), `TractorBeam`, `Mine`. Not blockers. **Invariant: game simulation runs independently of Unity.** `ServerShared` -enforces this at compile time (`Aetheria.Shared.Unity.asmdef` sets -`noEngineReferences`), but Unity-owned hit resolution breaks it behaviorally: -the simulation compiles without Unity yet cannot resolve combat without it. +cannot reference UnityEngine (`Aetheria.Shared.Unity.asmdef` sets +`noEngineReferences`), and `Aetheria.Shared/Aetheria.Shared.csproj` builds it +from source with the plain .NET SDK, so a clean headless build is the check. +Tools (`tools/AetherDb`) and future test harnesses reference that project. +Unity-owned hit resolution still breaks the invariant behaviorally: the +simulation builds without Unity yet cannot resolve combat without it. That was a velocity compromise. Moving hit authority into fire control restores the invariant, and lets the planner and a balance harness simulate combat. @@ -95,3 +98,9 @@ Economy simulation, hauling, mining yield, crafting/blueprints, reputation changes, story placement, multiplayer and `Economy.Server`, a headless server deployment, and the CultMesh/daemon rebuild (parked in `F:\Projects\AetheriaEve`). + +Post-ship direction for items: generic item classes (docking bays, cargo +bays) become faction-neutral blueprints that branded manufacturing runs +reference, so no faction's absence from a galaxy can remove a required item +class. Until then, database variety has to guarantee it; `LoadoutGenerator` +logs whenever a required item falls back to an unavailable manufacturer. diff --git a/tools/AetherDb/AetherDb.cs b/tools/AetherDb/AetherDb.cs new file mode 100644 index 000000000..c31fcf6ee --- /dev/null +++ b/tools/AetherDb/AetherDb.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; + +// Opens GameData/AetherDB.msgpack through the game's own CultCache and data types, for inspection and editing scripts. +// Close the editor's Database Tools before saving from a script, or its next Save will overwrite the script's changes. +public sealed class AetherDb +{ + public string Root { get; } + public CultCache Cache { get; } + private readonly SingleFileMessagePackBackingStore _store; + + private AetherDb(string root) + { + Root = root; + RegisterResolver.Register(); + var gameData = Path.Combine(root, "GameData"); + _store = new SingleFileMessagePackBackingStore(Path.Combine(gameData, "AetherDB.msgpack")); + Cache = new CultCache(); + Cache.AddBackingStore(_store); + Cache.AddBackingStore(new MultiFileMessagePackBackingStore(gameData), typeof(NameFile)); + Cache.PullAllBackingStores(); + } + + // With no root given, walks up from the working directory to the repository + public static AetherDb Open(string root = null) => new AetherDb(root ?? FindRoot()); + + // Writes every entry back to AetherDB.msgpack, as the editor's Save button does + public void Save() => _store.PushAll(); + + private static string FindRoot() + { + for (var dir = new DirectoryInfo(Environment.CurrentDirectory); dir != null; dir = dir.Parent) + if (File.Exists(Path.Combine(dir.FullName, "GameData", "AetherDB.msgpack"))) + return dir.FullName; + throw new DirectoryNotFoundException("Run from inside the Aetheria repository."); + } +} diff --git a/tools/AetherDb/Directory.Build.props b/tools/AetherDb/Directory.Build.props new file mode 100644 index 000000000..1a7b942e9 --- /dev/null +++ b/tools/AetherDb/Directory.Build.props @@ -0,0 +1,14 @@ + + + + disable + disable + + false + + + + + + diff --git a/tools/AetherDb/scripts/station-fit.cs b/tools/AetherDb/scripts/station-fit.cs new file mode 100644 index 000000000..fd1323daf --- /dev/null +++ b/tools/AetherDb/scripts/station-fit.cs @@ -0,0 +1,22 @@ +// Checks that some docking bay fits every station hull, which LoadoutGenerator.GenerateStationLoadout requires. +// Exits with the number of station hulls that cannot take any docking bay. +using System; +using System.Linq; + +var db = AetherDb.Open(); +var bays = db.Cache.GetAll().ToArray(); +var failures = 0; +foreach (var hull in db.Cache.GetAll().Where(h => h.HullType == HullType.Station)) +{ + // Hardpoint gear is equipped before the docking bay, so the worst case leaves only the non-hardpoint interior free + var worstCase = new Shape(hull.Shape.Width, hull.Shape.Height); + foreach (var v in hull.InteriorCells.Coordinates) worstCase[v] = true; + foreach (var hardpoint in hull.Hardpoints) + foreach (var v in hardpoint.Shape.Coordinates) + worstCase[hardpoint.Position + v] = false; + + var fitting = bays.Where(b => b.Shape.FitsWithin(worstCase, out _, out _)).Select(b => b.Name).ToArray(); + if (fitting.Length == 0) failures++; + Console.WriteLine($"{hull.Name}: {(fitting.Length == 0 ? "NO docking bay fits" : string.Join(", ", fitting))}"); +} +return failures;