From 8b0b50382a56a86becf6b350821f83f56f2ac281 Mon Sep 17 00:00:00 2001 From: soul Date: Thu, 22 Oct 2026 20:23:17 +0200 Subject: [PATCH 01/11] WeaponData: drop the dead [Union] list; BehaviorData's union owns behavior arms Co-Authored-By: Claude Sonnet 5.5 --- Assets/Scripts/ServerShared/Behaviors/Weapon.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Assets/Scripts/ServerShared/Behaviors/Weapon.cs b/Assets/Scripts/ServerShared/Behaviors/Weapon.cs index 12166d46c..cb42d6bd4 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Weapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Weapon.cs @@ -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] From 1dce24a226277d8570a3a40eb17d282fd04c746d Mon Sep 17 00:00:00 2001 From: soul Date: Thu, 22 Oct 2026 20:23:23 +0200 Subject: [PATCH 02/11] Pin CultLib 2.0.0 (42dce361) and CultMath 0.5.0 (7a8933b0): manifest tags and build revisions Co-Authored-By: Claude Sonnet 5.5 --- Directory.Build.props | 8 ++++---- Packages/manifest.json | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 190e89780..e17a8e5d5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,12 +2,12 @@ $(CULTLIB_ROOT) $(MSBuildThisFileDirectory)..\CultLib - - 45c2f4006f5f7dab357426cabd3197e8d0a17f66 + + 42dce3611a81adc5c5fac54cadbaa5637d6022d3 $(CULTMATH_ROOT) $(CultLibRoot) - - 9c1b06cd703bfa44352050e819b0d0659cf5978a + + 7a8933b085468e1fc6f9d3bce4a85e7d101aaca3 C:\Program Files\Git\cmd\git.exe git diff --git a/Packages/manifest.json b/Packages/manifest.json index 2dd2a3bb3..d4dafcbf2 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -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.0", + "org.gamecult.cultmath": "https://github.com/GameCult/CultLib.git?path=/packages/cultmath/unity/org.gamecult.cultmath#cultmath-unity-v0.5.0" } } From bd0293d6836e43f4a26a7d9c6021b0901a401ead Mon Sep 17 00:00:00 2001 From: soul Date: Thu, 22 Oct 2026 20:23:27 +0200 Subject: [PATCH 03/11] BehaviorData: LockWeaponData is arm 52; union-closure and null-gear tests Co-Authored-By: Claude Sonnet 5.5 --- .../ServerShared/Behaviors/Behaviors.cs | 1 + .../CultLibAdoptionTests.cs | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 tests/Aetheria.Shared.Tests/CultLibAdoptionTests.cs diff --git a/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs b/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs index 7150e47a1..6b899ee35 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs @@ -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 { diff --git a/tests/Aetheria.Shared.Tests/CultLibAdoptionTests.cs b/tests/Aetheria.Shared.Tests/CultLibAdoptionTests.cs new file mode 100644 index 000000000..bfe379d36 --- /dev/null +++ b/tests/Aetheria.Shared.Tests/CultLibAdoptionTests.cs @@ -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(); + foreach (var root in types) + { + var arms = root.GetCustomAttributes(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().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); + } +} From fb8f2ea2fb7211563b3e491afb69ba32199782e5 Mon Sep 17 00:00:00 2001 From: soul Date: Thu, 22 Oct 2026 20:23:32 +0200 Subject: [PATCH 04/11] ShipAuthoring: Load follows the pull's InvalidDataException, RefuseLegacyEmbeddedHull the nil-stamp NotSupportedException Co-Authored-By: Claude Sonnet 5.5 --- Assets/Scripts/ServerShared/ShipAuthoring.cs | 4 ++-- .../ShipAuthoringTests.cs | 23 ++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Assets/Scripts/ServerShared/ShipAuthoring.cs b/Assets/Scripts/ServerShared/ShipAuthoring.cs index eafbacd2d..2f6db9e5c 100644 --- a/Assets/Scripts/ServerShared/ShipAuthoring.cs +++ b/Assets/Scripts/ServerShared/ShipAuthoring.cs @@ -136,7 +136,7 @@ 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); } @@ -152,7 +152,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); } diff --git a/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs b/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs index eced4c6f7..35a346fe0 100644 --- a/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs +++ b/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs @@ -173,7 +173,28 @@ 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(() => ShipAuthoringStore.Load(path)); Assert.Contains(path, error.Message); - Assert.IsAssignableFrom(error.InnerException); + // The pull names the record key and schema; the MessagePack failure is its cause. + var pull = Assert.IsType(error.InnerException); + Assert.Contains("aetheria.hulldata", pull.Message); + Assert.Contains(ShipModCatalog.HullKey("mod.skiff").ToString(), pull.Message); + Assert.IsAssignableFrom(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(), + Records = Array.Empty(), + })); + var error = Assert.Throws(() => ShipAuthoringStore.Load(path)); + Assert.Contains(path, error.Message); + Assert.IsType(error.InnerException); } // Decodes one record's payload of the named schema to its slot array, lets the edit change it, and stores it again. From fbf88e85c514daaeba06b1b8f18abe704810b3ef Mon Sep 17 00:00:00 2001 From: soul Date: Thu, 22 Oct 2026 20:23:37 +0200 Subject: [PATCH 05/11] ship_cc.read owns its unreadable-path refusal, naming no path Co-Authored-By: Claude Sonnet 5.5 --- tools/blender/aetheria_ships/ship_cc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/blender/aetheria_ships/ship_cc.py b/tools/blender/aetheria_ships/ship_cc.py index dd3ca78be..b7dca307c 100644 --- a/tools/blender/aetheria_ships/ship_cc.py +++ b/tools/blender/aetheria_ships/ship_cc.py @@ -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 From e518a0b3d161a05029f86d6973d1186d6f9afead Mon Sep 17 00:00:00 2001 From: soul Date: Thu, 22 Oct 2026 20:37:40 +0200 Subject: [PATCH 06/11] packages-lock: regenerated by Unity at the 2.0.0 / 0.5.0 tags Co-Authored-By: Claude Sonnet 5.5 --- Packages/packages-lock.json | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 5d26bd587..7345ecc47 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -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.0", "depth": 0, "source": "git", - "dependencies": {}, - "hash": "45c2f4006f5f7dab357426cabd3197e8d0a17f66" + "dependencies": { + "org.gamecult.cultmath": "0.5.0" + }, + "hash": "42dce3611a81adc5c5fac54cadbaa5637d6022d3" }, "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", From f448957f3ad1d1de2c9a96477acacb53047683da Mon Sep 17 00:00:00 2001 From: soul Date: Thu, 22 Oct 2026 20:39:29 +0200 Subject: [PATCH 07/11] Brush: name AnimatedSimplexBrush and RadialWaveBrush as arms; pull-failure test asserts the record key Co-Authored-By: Claude Sonnet 5.5 --- Assets/Scripts/ServerShared/Environment.cs | 2 ++ tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Assets/Scripts/ServerShared/Environment.cs b/Assets/Scripts/ServerShared/Environment.cs index 737033e4e..9d4994fa3 100644 --- a/Assets/Scripts/ServerShared/Environment.cs +++ b/Assets/Scripts/ServerShared/Environment.cs @@ -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 { diff --git a/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs b/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs index 35a346fe0..cb4d12d5f 100644 --- a/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs +++ b/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs @@ -175,8 +175,8 @@ public sealed class ShipAuthoringTests Assert.Contains(path, error.Message); // The pull names the record key and schema; the MessagePack failure is its cause. var pull = Assert.IsType(error.InnerException); - Assert.Contains("aetheria.hulldata", pull.Message); - Assert.Contains(ShipModCatalog.HullKey("mod.skiff").ToString(), pull.Message); + Assert.Contains("Record '" + ShipModCatalog.HullKey("mod.skiff") + "'", pull.Message); + Assert.Contains("schema", pull.Message); Assert.IsAssignableFrom(pull.InnerException); } From da5ba653cb78cb0ba2029ba83b924db7b455bf60 Mon Sep 17 00:00:00 2001 From: soul Date: Thu, 22 Oct 2026 23:34:08 +0200 Subject: [PATCH 08/11] Tests (red at 42dce361): a CultLib 1.0.60 run.cc opens; Load names a ship file of a newer store format tests/vectors/cultlib-1.0.60/run.cc is the run store Soul wrote with the base build (cl2-old-run-save-refused). Both new tests fail at the 2.0.0 pin; the first needs 2.0.1, the second needs ShipAuthoringStore.Load. Co-Authored-By: Claude Sonnet 5.5 --- .../RunSaveCompatibilityTests.cs | 33 ++++++++++++++++++ .../ShipAuthoringTests.cs | 19 ++++++++++ tests/vectors/cultlib-1.0.60/run.cc | Bin 0 -> 53949 bytes 3 files changed, 52 insertions(+) create mode 100644 tests/Aetheria.Shared.Tests/RunSaveCompatibilityTests.cs create mode 100644 tests/vectors/cultlib-1.0.60/run.cc diff --git a/tests/Aetheria.Shared.Tests/RunSaveCompatibilityTests.cs b/tests/Aetheria.Shared.Tests/RunSaveCompatibilityTests.cs new file mode 100644 index 000000000..8b8c98217 --- /dev/null +++ b/tests/Aetheria.Shared.Tests/RunSaveCompatibilityTests.cs @@ -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(); + 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))); + } +} diff --git a/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs b/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs index cb4d12d5f..850e83dfd 100644 --- a/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs +++ b/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs @@ -197,6 +197,25 @@ public sealed class ShipAuthoringTests Assert.IsType(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(() => ShipAuthoringStore.Load(path)); + Assert.Contains(path, error.Message); + Assert.IsType(error.InnerException); + } + // Decodes one record's payload of the named schema to its slot array, lets the edit change it, and stores it again. private static void RewritePayload(string path, string schemaName, Action edit) { diff --git a/tests/vectors/cultlib-1.0.60/run.cc b/tests/vectors/cultlib-1.0.60/run.cc new file mode 100644 index 0000000000000000000000000000000000000000..fc2a4e76e119257e70ccafe82b3916de79159aef GIT binary patch literal 53949 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 Date: Thu, 22 Oct 2026 23:34:24 +0200 Subject: [PATCH 09/11] Pins: cultlib-unity-v2.0.1 at b8fc5eb0 (1.0.x saves with ref lists open as compatible drift) packages-lock.json follows from the Unity resolve. caching-unity stays at v2.0.0; cultmath at 7a8933b0. Co-Authored-By: Claude Sonnet 5.5 --- Directory.Build.props | 4 ++-- Packages/manifest.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index e17a8e5d5..05d5e4d22 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,8 +2,8 @@ $(CULTLIB_ROOT) $(MSBuildThisFileDirectory)..\CultLib - - 42dce3611a81adc5c5fac54cadbaa5637d6022d3 + + b8fc5eb07720a187a0421424ac2cb3aa2966ca1d $(CULTMATH_ROOT) $(CultLibRoot) diff --git a/Packages/manifest.json b/Packages/manifest.json index d4dafcbf2..7693bd94f 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -53,7 +53,7 @@ "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-v2.0.0", - "org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-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" } } From 7886f0a231890ab3f69932feec1e8d3a97e17926 Mon Sep 17 00:00:00 2001 From: soul Date: Thu, 22 Oct 2026 23:34:26 +0200 Subject: [PATCH 10/11] ShipAuthoringStore.Load names the file when its store format is newer than this runtime reads Co-Authored-By: Claude Sonnet 5.5 --- Assets/Scripts/ServerShared/ShipAuthoring.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Scripts/ServerShared/ShipAuthoring.cs b/Assets/Scripts/ServerShared/ShipAuthoring.cs index 2f6db9e5c..3962fa468 100644 --- a/Assets/Scripts/ServerShared/ShipAuthoring.cs +++ b/Assets/Scripts/ServerShared/ShipAuthoring.cs @@ -140,6 +140,10 @@ public static class ShipAuthoringStore { 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 From d9bc46535566fa3b702ae8ca7d21287d7a4209ec Mon Sep 17 00:00:00 2001 From: soul Date: Fri, 23 Oct 2026 01:26:14 +0200 Subject: [PATCH 11/11] Packages: lock regenerated by Unity at cultlib-unity-v2.0.1 (b8fc5eb0) Co-Authored-By: Claude Opus 5.5 --- Packages/packages-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 7345ecc47..919cf94d1 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -265,13 +265,13 @@ "hash": "42dce3611a81adc5c5fac54cadbaa5637d6022d3" }, "org.gamecult.cultlib": { - "version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v2.0.0", + "version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v2.0.1", "depth": 0, "source": "git", "dependencies": { "org.gamecult.cultmath": "0.5.0" }, - "hash": "42dce3611a81adc5c5fac54cadbaa5637d6022d3" + "hash": "b8fc5eb07720a187a0421424ac2cb3aa2966ca1d" }, "org.gamecult.cultmath": { "version": "https://github.com/GameCult/CultLib.git?path=/packages/cultmath/unity/org.gamecult.cultmath#cultmath-unity-v0.5.0",