diff --git a/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs b/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs index 8caec9d5d..4013d5c93 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs @@ -129,33 +129,11 @@ public interface IOrderedBehavior int Order { get; } } -public interface IPersistentBehavior// where T : IBehavior -{ - PersistentBehaviorData Store(); - void Restore(PersistentBehaviorData data); -} - public interface IPopulationAssignment { int AssignedPopulation { get; set; } } -[MessagePackObject, - JsonObject(MemberSerialization.OptIn), - Union(0, typeof(MarkerPersistentBehaviorData))] -public abstract class PersistentBehaviorData -{ -} - -// No IBehavior implements IPersistentBehavior yet, so this abstract type has no production writer. This one -// concrete leaf exists so the wire format (EntitySerializer's PersistedBehaviors pairs array) can be exercised by -// tests ahead of a real persistent behavior landing. -[MessagePackObject] -public class MarkerPersistentBehaviorData : PersistentBehaviorData -{ - [Key(0)] public int Value; -} - [Inspectable, Union(0, typeof(GuidedWeaponData)), Union(1, typeof(LauncherData)), diff --git a/Assets/Scripts/ServerShared/EntitySerializer.cs b/Assets/Scripts/ServerShared/EntitySerializer.cs index 082f350f7..fc375dd8d 100644 --- a/Assets/Scripts/ServerShared/EntitySerializer.cs +++ b/Assets/Scripts/ServerShared/EntitySerializer.cs @@ -29,18 +29,6 @@ public static class EntitySerializer pack.Settings = entity.Settings; - // Filter item behavior collections by those with any persistent behaviors - // For each item create a (position, data) pair holding every one of its behaviors' persistent data. - // A dictionary keyed by int2 has no hash-resistant comparer under CultCache's untrusted-data security, so - // this is an array of pairs, matching Equipment/CargoBays/DockingBays. - pack.PersistedBehaviors = entity.Equipment - .Where(item => item.Behaviors.Any(b=>b is IPersistentBehavior)) - .Select(item => (item.Position, item.Behaviors - .Where(b=>b is IPersistentBehavior) - .Cast() - .Select(b => b.Store()).ToArray())) - .ToArray(); - pack.Hull = entity.Hull; pack.Name = entity.Name; pack.Faction = entity.Faction == null ? default : entity.ItemManager.ItemData.RefOf(entity.Faction); @@ -122,18 +110,6 @@ public static class EntitySerializer foreach (var (position, item) in pack.DockingBayContents[bayIndex]) entity.DockingBays[bayIndex].TryStore(item, position); - // Iterate only over the behaviors of items which contain persistent data - // Build a local lookup from the pairs array, then filter each item's behaviors for the persistent ones, - // cast them and combine with that item's persisted data array. - var persistedBehaviorsByPosition = pack.PersistedBehaviors.ToDictionary(x => x.position, x => x.data); - foreach (var persistentBehaviorData in entity.Equipment - .Where(item => persistedBehaviorsByPosition.ContainsKey(item.Position)) - .SelectMany(item => item.Behaviors - .Where(b=> b is IPersistentBehavior) - .Cast() - .Zip(persistedBehaviorsByPosition[item.Position], (behavior, data) => new{behavior, data}))) - persistentBehaviorData.behavior.Restore(persistentBehaviorData.data); - entity.Temperature = pack.Temperature; entity.Armor = pack.Armor; @@ -201,7 +177,7 @@ public abstract class EntityPack [Key(2)] public (int2 position, EquippableItem item)[] Equipment; [Key(3)] public (int2 position, EquippableItem item)[] CargoBays; [Key(4)] public (int2 position, EquippableItem item)[] DockingBays; - [Key(5)] public (int2 position, PersistentBehaviorData[] data)[] PersistedBehaviors; + // Key(5) retired 2026-09-17; a deleted field used it. Do not reuse. [Key(6)] public float[,] Temperature; [Key(7)] public float[,] Armor; [Key(8)] public bool2[,] Conductivity; diff --git a/tests/Aetheria.Shared.Tests/LoadoutTests.cs b/tests/Aetheria.Shared.Tests/LoadoutTests.cs index e68e834c4..7a6c4e925 100644 --- a/tests/Aetheria.Shared.Tests/LoadoutTests.cs +++ b/tests/Aetheria.Shared.Tests/LoadoutTests.cs @@ -357,7 +357,7 @@ public sealed class LoadoutTests : IDisposable Equipment = Array.Empty<(int2, EquippableItem)>(), CargoBays = Array.Empty<(int2, EquippableItem)>(), DockingBays = Array.Empty<(int2, EquippableItem)>(), CargoContents = Array.Empty<(int2, ItemInstance)[]>(), DockingBayContents = Array.Empty<(int2, ItemInstance)[]>(), Children = Array.Empty(), - PersistedBehaviors = Array.Empty<(int2, PersistentBehaviorData[])>(), Temperature = new float[0, 0], + Temperature = new float[0, 0], Armor = new float[0, 0], Conductivity = new bool2[0, 0], DockingBayAssignments = Array.Empty(), Settings = new EntitySettings(), WeaponGroups = Array.Empty() }; @@ -371,7 +371,7 @@ public sealed class LoadoutTests : IDisposable DockingBayContents = new[] { new[] { (default(int2), (ItemInstance) dockingBayContent) } }, Children = new EntityPack[] { childPack }, DockingBayAssignments = new[] { 0 }, - PersistedBehaviors = Array.Empty<(int2, PersistentBehaviorData[])>(), Temperature = new float[0, 0], + Temperature = new float[0, 0], Armor = new float[0, 0], Conductivity = new bool2[0, 0], Settings = new EntitySettings(), WeaponGroups = Array.Empty() }; diff --git a/tests/Aetheria.Shared.Tests/RunSaveTests.cs b/tests/Aetheria.Shared.Tests/RunSaveTests.cs index 3002c0395..86ca9a77e 100644 --- a/tests/Aetheria.Shared.Tests/RunSaveTests.cs +++ b/tests/Aetheria.Shared.Tests/RunSaveTests.cs @@ -212,46 +212,6 @@ public sealed class RunSaveTests : IDisposable Assert.Throws(() => ledger.Reachable(new[] { 9 })); } - // F1: PersistedBehaviors is now a pairs array (Dictionary has no hash-resistant comparer under - // CultCache's untrusted-data security, so a store holding any entity could never be reopened). Two positions, - // each with one persisted behavior, must round-trip through a commit and reopen. - [Fact] - public void ReopenedPackKeepsPersistedBehaviors() - { - var posA = new int2(0, 0); - var posB = new int2(1, 0); - var pack = BarePack(hull: null, persistedBehaviors: new (int2, PersistentBehaviorData[])[] - { - (posA, new PersistentBehaviorData[] { new MarkerPersistentBehaviorData { Value = 11 } }), - (posB, new PersistentBehaviorData[] { new MarkerPersistentBehaviorData { Value = 22 } }) - }); - - using (var cache = Open()) - { - var zones = new[] - { - new SavedZone - { - Name = "Zone 0", AdjacentZones = Array.Empty(), Factions = Array.Empty(), Owner = -1, - Contents = new ZonePack { Entities = new List { pack } } - } - }; - RunSave.Commit(cache, Game(cache), zones, new ProvenanceLedger()); - } - - using (var cache = Open()) - { - var run = cache.GetGlobal(); - var zone = cache.Get(run.Zones[0]); - var reopened = zone.Contents.Entities[0].PersistedBehaviors.OrderBy(p => p.position.x).ToArray(); - Assert.Equal(2, reopened.Length); - Assert.Equal(posA, reopened[0].position); - Assert.Equal(11, Assert.IsType(reopened[0].data.Single()).Value); - Assert.Equal(posB, reopened[1].position); - Assert.Equal(22, Assert.IsType(reopened[1].data.Single()).Value); - } - } - // F1: the reopen defect was found through a hand-built pack; this pins the same defect class at the layer it // actually failed, a real ship generated by LoadoutGenerator against a catalog fixture, committed into a // SavedZone and reopened (Soul's probe in docs/headless-playground-cut.md ยง2.6). @@ -327,8 +287,7 @@ public sealed class RunSaveTests : IDisposable // A minimal, valid EntityPack with every collection field non-null, for tests that build packs directly rather // than through EntitySerializer.Pack. private static ShipPack BarePack(EquippableItem hull, EntityPack[] children = null, - (int2, ItemInstance)[][] dockingBayContents = null, - (int2, PersistentBehaviorData[])[] persistedBehaviors = null) => new ShipPack + (int2, ItemInstance)[][] dockingBayContents = null) => new ShipPack { Name = "ship", Hull = hull, @@ -338,7 +297,6 @@ public sealed class RunSaveTests : IDisposable CargoContents = Array.Empty<(int2, ItemInstance)[]>(), DockingBayContents = dockingBayContents ?? Array.Empty<(int2, ItemInstance)[]>(), Children = children ?? Array.Empty(), - PersistedBehaviors = persistedBehaviors ?? Array.Empty<(int2, PersistentBehaviorData[])>(), Temperature = new float[0, 0], Armor = new float[0, 0], Conductivity = new bool2[0, 0], diff --git a/tests/mutation_tests.py b/tests/mutation_tests.py index 7c812b756..70dcc370c 100644 --- a/tests/mutation_tests.py +++ b/tests/mutation_tests.py @@ -139,15 +139,6 @@ MUTATIONS: list[Mutation] = [ test="LoadoutTests.StatsReadTheLot", expect="red", ), - # --- F1 (docs/headless-playground-cut.md Cut 0): PersistedBehaviors is a pairs array --- - # Cut 0 names two mutations here: "pack drops data" and "unpack ignores the lookup". Neither is reachable - # today. No IBehavior implements IPersistentBehavior (Behaviors.cs), so EntitySerializer.Pack's - # `.Where(item => item.Behaviors.Any(b=>b is IPersistentBehavior))` always filters every item out, and - # Restore's matching lookup is consulted by an always-empty SelectMany. A real ship built through Pack can - # never carry non-empty PersistedBehaviors, so mutating either body is unobservable through any live path; - # RunSaveTests.ReopenedPackKeepsPersistedBehaviors instead hand-builds the pairs array directly (bypassing - # Pack/Restore) to at least prove the wire format round-trips. Both mutations are not yet reached, pending a - # real IPersistentBehavior implementer, which is out of Cut 0's scope. # --- F2 (item-provenance-cut.md): the GC root walk must cover every EntityPack root --- Mutation( rule="Items yields the Equipment root, not just the hull",