Merge eureka/aetheria-release-cultlib-2-0-adopt: Aetheria on CultLib 2.0.1 and CultMath 0.5.0; LockWeaponData is a BehaviorData arm; 1.0.x saves open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
bd628de3bf
12 changed files with 143 additions and 25 deletions
|
|
@ -198,6 +198,7 @@ public interface IPowerConsumer
|
|||
Union(49, typeof(LaunchSuppressorData)),
|
||||
Union(50, typeof(ListenerData)),
|
||||
Union(51, typeof(ScannerData)),
|
||||
Union(52, typeof(LockWeaponData)),
|
||||
JsonObject(MemberSerialization.OptIn)]
|
||||
public abstract class BehaviorData
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,12 +11,7 @@ using Newtonsoft.Json;
|
|||
using CultMath;
|
||||
using static CultMath.math;
|
||||
|
||||
[Inspectable,
|
||||
Union(0, typeof(InstantWeaponData)),
|
||||
Union(1, typeof(LauncherData)),
|
||||
Union(2, typeof(ConstantWeaponData)),
|
||||
Union(3, typeof(ChargedWeaponData)),
|
||||
RuntimeInspectable]
|
||||
[Inspectable, RuntimeInspectable]
|
||||
public abstract class WeaponData : BehaviorData
|
||||
{
|
||||
[Inspectable, JsonProperty("damageType"), Key(1), RuntimeInspectable]
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ public class NoiseSettings
|
|||
|
||||
[Union(0, typeof(PowerBrush)),
|
||||
Union(1, typeof(SimplexBrush)),
|
||||
Union(2, typeof(AnimatedSimplexBrush)),
|
||||
Union(3, typeof(RadialWaveBrush)),
|
||||
Serializable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
|
||||
public abstract class Brush
|
||||
{
|
||||
|
|
|
|||
|
|
@ -136,10 +136,14 @@ public static class ShipAuthoringStore
|
|||
{
|
||||
RefuseLegacyEmbeddedHull(path);
|
||||
try { return LoadRecords(path); }
|
||||
catch (MessagePackSerializationException error)
|
||||
catch (Exception error) when (error is InvalidDataException or MessagePackSerializationException)
|
||||
{
|
||||
throw new InvalidOperationException($"{path}: a record does not decode as its schema: {error.Message}", error);
|
||||
}
|
||||
catch (NotSupportedException error)
|
||||
{
|
||||
throw new InvalidOperationException($"{path}: not a readable ship file: {error.Message}", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Key 1 of ShipAuthoring held the whole hull before S1. The type no longer has that member, so a deserialized record
|
||||
|
|
@ -152,7 +156,7 @@ public static class ShipAuthoringStore
|
|||
if (bytes.Length == 0) return;
|
||||
CultPersistedStoreSnapshot snapshot;
|
||||
try { snapshot = CultDocumentMessagePackSerialization.DeserializeSnapshot(bytes); }
|
||||
catch (MessagePackSerializationException error)
|
||||
catch (Exception error) when (error is MessagePackSerializationException or NotSupportedException)
|
||||
{
|
||||
throw new InvalidOperationException($"{path}: not a readable ship file: {error.Message}", error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
<PropertyGroup>
|
||||
<CultLibRoot Condition="'$(CultLibRoot)' == '' and '$(CULTLIB_ROOT)' != ''">$(CULTLIB_ROOT)</CultLibRoot>
|
||||
<CultLibRoot Condition="'$(CultLibRoot)' == ''">$(MSBuildThisFileDirectory)..\CultLib</CultLibRoot>
|
||||
<!-- CultCache: caching-unity-v1.4.0 / cultlib-unity-v1.0.60 in Packages/manifest.json. -->
|
||||
<CultLibRevision>45c2f4006f5f7dab357426cabd3197e8d0a17f66</CultLibRevision>
|
||||
<!-- CultCache: caching-unity-v2.0.0 / cultlib-unity-v2.0.1 in Packages/manifest.json. -->
|
||||
<CultLibRevision>b8fc5eb07720a187a0421424ac2cb3aa2966ca1d</CultLibRevision>
|
||||
<CultMathRoot Condition="'$(CultMathRoot)' == '' and '$(CULTMATH_ROOT)' != ''">$(CULTMATH_ROOT)</CultMathRoot>
|
||||
<CultMathRoot Condition="'$(CultMathRoot)' == ''">$(CultLibRoot)</CultMathRoot>
|
||||
<!-- CultMath: cultmath-unity-v0.4.1 in Packages/manifest.json. -->
|
||||
<CultMathRevision>9c1b06cd703bfa44352050e819b0d0659cf5978a</CultMathRevision>
|
||||
<!-- CultMath: cultmath-unity-v0.5.0 in Packages/manifest.json. -->
|
||||
<CultMathRevision>7a8933b085468e1fc6f9d3bce4a85e7d101aaca3</CultMathRevision>
|
||||
<GitExecutable Condition="'$(GitExecutable)' == '' and Exists('C:\Program Files\Git\cmd\git.exe')">C:\Program Files\Git\cmd\git.exe</GitExecutable>
|
||||
<GitExecutable Condition="'$(GitExecutable)' == ''">git</GitExecutable>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@
|
|||
"com.unity.modules.vr": "1.0.0",
|
||||
"com.unity.modules.wind": "1.0.0",
|
||||
"com.unity.modules.xr": "1.0.0",
|
||||
"org.gamecult.caching.unity": "https://github.com/GameCult/CultLib.git?path=/src/GameCult.Unity/Assets/Caching#caching-unity-v1.4.0",
|
||||
"org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.60",
|
||||
"org.gamecult.cultmath": "https://github.com/GameCult/CultLib.git?path=/packages/cultmath/unity/org.gamecult.cultmath#cultmath-unity-v0.4.1"
|
||||
"org.gamecult.caching.unity": "https://github.com/GameCult/CultLib.git?path=/src/GameCult.Unity/Assets/Caching#caching-unity-v2.0.0",
|
||||
"org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v2.0.1",
|
||||
"org.gamecult.cultmath": "https://github.com/GameCult/CultLib.git?path=/packages/cultmath/unity/org.gamecult.cultmath#cultmath-unity-v0.5.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,27 +256,29 @@
|
|||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"org.gamecult.caching.unity": {
|
||||
"version": "https://github.com/GameCult/CultLib.git?path=/src/GameCult.Unity/Assets/Caching#caching-unity-v1.4.0",
|
||||
"version": "https://github.com/GameCult/CultLib.git?path=/src/GameCult.Unity/Assets/Caching#caching-unity-v2.0.0",
|
||||
"depth": 0,
|
||||
"source": "git",
|
||||
"dependencies": {
|
||||
"org.gamecult.cultlib": "1.0.60"
|
||||
"org.gamecult.cultlib": "2.0.0"
|
||||
},
|
||||
"hash": "45c2f4006f5f7dab357426cabd3197e8d0a17f66"
|
||||
"hash": "42dce3611a81adc5c5fac54cadbaa5637d6022d3"
|
||||
},
|
||||
"org.gamecult.cultlib": {
|
||||
"version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.60",
|
||||
"version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v2.0.1",
|
||||
"depth": 0,
|
||||
"source": "git",
|
||||
"dependencies": {},
|
||||
"hash": "45c2f4006f5f7dab357426cabd3197e8d0a17f66"
|
||||
"dependencies": {
|
||||
"org.gamecult.cultmath": "0.5.0"
|
||||
},
|
||||
"hash": "b8fc5eb07720a187a0421424ac2cb3aa2966ca1d"
|
||||
},
|
||||
"org.gamecult.cultmath": {
|
||||
"version": "https://github.com/GameCult/CultLib.git?path=/packages/cultmath/unity/org.gamecult.cultmath#cultmath-unity-v0.4.1",
|
||||
"version": "https://github.com/GameCult/CultLib.git?path=/packages/cultmath/unity/org.gamecult.cultmath#cultmath-unity-v0.5.0",
|
||||
"depth": 0,
|
||||
"source": "git",
|
||||
"dependencies": {},
|
||||
"hash": "9c1b06cd703bfa44352050e819b0d0659cf5978a"
|
||||
"hash": "7a8933b085468e1fc6f9d3bce4a85e7d101aaca3"
|
||||
},
|
||||
"com.unity.modules.accessibility": {
|
||||
"version": "1.0.0",
|
||||
|
|
|
|||
38
tests/Aetheria.Shared.Tests/CultLibAdoptionTests.cs
Normal file
38
tests/Aetheria.Shared.Tests/CultLibAdoptionTests.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using GameCult.Caching;
|
||||
using GameCult.Caching.MessagePack;
|
||||
using MessagePack;
|
||||
using Xunit;
|
||||
|
||||
// CultLib 2.0 refuses a union subtype no arm names (it used to write nil). These keep that refusal a build-time failure.
|
||||
public sealed class CultLibAdoptionTests
|
||||
{
|
||||
[Fact]
|
||||
public void EveryConcreteSubtypeOfAUnionBaseIsAnArmOfIt()
|
||||
{
|
||||
var types = typeof(BehaviorData).Assembly.GetTypes();
|
||||
var missing = new System.Collections.Generic.List<string>();
|
||||
foreach (var root in types)
|
||||
{
|
||||
var arms = root.GetCustomAttributes<UnionAttribute>(false).Select(u => u.SubType).ToHashSet();
|
||||
if (arms.Count == 0) continue;
|
||||
foreach (var subtype in types.Where(t => t != root && !t.IsAbstract && !t.IsInterface && root.IsAssignableFrom(t)))
|
||||
if (!arms.Contains(subtype)) missing.Add($"{subtype.Name} is not an arm of {root.Name}");
|
||||
}
|
||||
Assert.Empty(missing);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoShippedGearHoldsANullBehavior()
|
||||
{
|
||||
using var cache = new CultCache(TestCatalog.Registry());
|
||||
cache.AddBackingStore(new SingleFileMessagePackBackingStore(TestCatalog.Repo, true), AetheriaStores.CatalogTypes);
|
||||
var gear = cache.GetAll<GearData>().ToArray();
|
||||
Assert.NotEmpty(gear);
|
||||
var bad = gear.Where(g => g.Behaviors != null && g.Behaviors.Any(b => b == null)).Select(g => g.Name).ToArray();
|
||||
Assert.Empty(bad);
|
||||
}
|
||||
}
|
||||
33
tests/Aetheria.Shared.Tests/RunSaveCompatibilityTests.cs
Normal file
33
tests/Aetheria.Shared.Tests/RunSaveCompatibilityTests.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using GameCult.Caching;
|
||||
using GameCult.Caching.MessagePack;
|
||||
using Xunit;
|
||||
|
||||
// tests/vectors/cultlib-1.0.60/run.cc is a run.cc written by Aetheria on CultLib 1.0.60 (64 zones, factions, one entity).
|
||||
// A player's save outlives the runtime that wrote it, so the current runtime must open it as compatible drift.
|
||||
public sealed class RunSaveCompatibilityTests : IDisposable
|
||||
{
|
||||
private readonly string _root = Path.Combine(Path.GetTempPath(), "aetheria-runcompat-" + Guid.NewGuid().ToString("N"));
|
||||
|
||||
public RunSaveCompatibilityTests() => Directory.CreateDirectory(_root);
|
||||
|
||||
public void Dispose() => Directory.Delete(_root, true);
|
||||
|
||||
[Fact]
|
||||
public void ARunWrittenByCultLib1060OpensWithEveryZoneAndFactionResolving()
|
||||
{
|
||||
var run = Path.Combine(_root, "run.cc");
|
||||
File.Copy(Path.Combine(RunStartTests.FindRepoRoot(), "tests", "vectors", "cultlib-1.0.60", "run.cc"), run);
|
||||
using var cache = new CultCache(TestCatalog.Registry());
|
||||
cache.AddBackingStore(new SingleFileMessagePackBackingStore(TestCatalog.Repo, true), AetheriaStores.CatalogTypes);
|
||||
cache.AddBackingStore(new SingleFileMessagePackBackingStore(run), AetheriaStores.RunTypes);
|
||||
var saved = cache.GetGlobal<SavedGame>();
|
||||
Assert.NotNull(saved);
|
||||
Assert.NotEmpty(saved.Zones);
|
||||
Assert.NotEmpty(saved.Factions);
|
||||
Assert.All(saved.Zones, zone => Assert.NotNull(cache.Get(zone)));
|
||||
Assert.All(saved.Factions, faction => Assert.NotNull(cache.Get(faction)));
|
||||
}
|
||||
}
|
||||
|
|
@ -173,7 +173,47 @@ public sealed class ShipAuthoringTests
|
|||
RewritePayload(path, "aetheria.hulldata", slots => ((object[])((object[])slots[5])[0])[2] = new object[] { 5, false, true, false });
|
||||
var error = Assert.Throws<InvalidOperationException>(() => ShipAuthoringStore.Load(path));
|
||||
Assert.Contains(path, error.Message);
|
||||
Assert.IsAssignableFrom<MessagePackSerializationException>(error.InnerException);
|
||||
// The pull names the record key and schema; the MessagePack failure is its cause.
|
||||
var pull = Assert.IsType<InvalidDataException>(error.InnerException);
|
||||
Assert.Contains("Record '" + ShipModCatalog.HullKey("mod.skiff") + "'", pull.Message);
|
||||
Assert.Contains("schema", pull.Message);
|
||||
Assert.IsAssignableFrom<MessagePackSerializationException>(pull.InnerException);
|
||||
}
|
||||
|
||||
// A store header with a nil format stamp is refused by the runtime with NotSupportedException; Load names the file.
|
||||
[Fact]
|
||||
public void LoadNamesTheFileWhoseFormatStampIsNil()
|
||||
{
|
||||
using var directory = new TempDirectory();
|
||||
var path = Path.Combine(directory.Path, "nilstamp.cc");
|
||||
File.WriteAllBytes(path, CultDocumentMessagePackSerialization.SerializeSnapshot(new CultPersistedStoreSnapshot
|
||||
{
|
||||
FormatVersion = null,
|
||||
SchemaCatalog = Array.Empty<CultSchemaCatalogEntry>(),
|
||||
Records = Array.Empty<CultPersistedRecord>(),
|
||||
}));
|
||||
var error = Assert.Throws<InvalidOperationException>(() => ShipAuthoringStore.Load(path));
|
||||
Assert.Contains(path, error.Message);
|
||||
Assert.IsType<NotSupportedException>(error.InnerException);
|
||||
}
|
||||
|
||||
// A store format this runtime does not know is refused on read with NotSupportedException from the attach; Load names the file.
|
||||
[Fact]
|
||||
public async Task LoadNamesTheFileWhoseStoreFormatIsNewer()
|
||||
{
|
||||
using var directory = new TempDirectory();
|
||||
var path = Path.Combine(directory.Path, "newer.cc");
|
||||
using (var cache = ShipAuthoringStore.Open(path, writable: true))
|
||||
{
|
||||
ShipAuthoringStore.Write(cache, Fixture().Hull, Fixture().Visual);
|
||||
await cache.FlushAsync();
|
||||
}
|
||||
var snapshot = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(path));
|
||||
snapshot.FormatVersion = "cultcache.store.v9";
|
||||
File.WriteAllBytes(path, CultDocumentMessagePackSerialization.SerializeSnapshot(snapshot));
|
||||
var error = Assert.Throws<InvalidOperationException>(() => ShipAuthoringStore.Load(path));
|
||||
Assert.Contains(path, error.Message);
|
||||
Assert.IsType<NotSupportedException>(error.InnerException);
|
||||
}
|
||||
|
||||
// Decodes one record's payload of the named schema to its slot array, lets the edit change it, and stores it again.
|
||||
|
|
|
|||
BIN
tests/vectors/cultlib-1.0.60/run.cc
Normal file
BIN
tests/vectors/cultlib-1.0.60/run.cc
Normal file
Binary file not shown.
|
|
@ -119,7 +119,10 @@ class ShipFile(NamedTuple):
|
|||
def read(path: str, cultlib_packages: str) -> ShipFile:
|
||||
cultcache, msgpack = _libraries(cultlib_packages)
|
||||
store = cultcache.SingleFileMessagePackBackingStore(path)
|
||||
envelopes = store.pull_all()
|
||||
try:
|
||||
envelopes = store.pull_all()
|
||||
except OSError as error:
|
||||
raise ValueError(f"The ship file cannot be read: {error.strerror}") from None
|
||||
ships = [envelope for envelope in envelopes if envelope.type == SCHEMA]
|
||||
hulls = [envelope for envelope in envelopes if envelope.type == HULL_SCHEMA]
|
||||
# Before S1 the hull lived inside the ship record. Saving such a file would preserve the stale hull under a ship that
|
||||
|
|
|
|||
Loading…
Reference in a new issue