Persist item enabled state in typed snapshots

This commit is contained in:
Meta Vens 2026-06-14 17:13:16 +01:00
commit f785f8e350
10 changed files with 58 additions and 18 deletions

Binary file not shown.

View file

@ -185,7 +185,8 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
{
ItemKey = AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId).ToString(),
Quality = 0.95,
Durability = 0.8
Durability = 0.8,
Enabled = false
}
}
],
@ -209,7 +210,8 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
ItemKey = AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId).ToString(),
Quality = 0.9,
Durability = 0.8,
Quantity = 1
Quantity = 1,
Enabled = false
}
],
CargoContents =
@ -609,6 +611,7 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
if (loadout?.RootEntity.Hull.ItemKey != AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId).ToString() ||
loadout.RootEntity.Equipment.Length != 1 ||
loadout.RootEntity.Equipment[0].Item.Enabled ||
loadout.RootEntity.WeaponGroups.Length != 1)
{
throw new InvalidOperationException("Loadout template did not survive flush/reopen.");
@ -646,6 +649,7 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
entitySnapshot.Equipment[0].Quality != 0.9 ||
entitySnapshot.Equipment[0].Durability != 0.8 ||
entitySnapshot.Equipment[0].Quantity != 1 ||
entitySnapshot.Equipment[0].Enabled ||
entitySnapshot.CargoContents.Length != 1 ||
entitySnapshot.CargoContents[0].Items[0].Item.Quantity != 7 ||
entitySnapshot.DockingBayAssignments.Length != 1 ||

View file

@ -184,7 +184,8 @@ try
{
ItemDefinitionLegacyId = "smoke:weapon",
Quality = 0.9,
Durability = 0.8
Durability = 0.8,
Enabled = false
}
}
},
@ -343,7 +344,8 @@ try
{
ItemDefinitionLegacyId = "smoke:weapon",
Quality = 0.9,
Durability = 0.8
Durability = 0.8,
Enabled = false
}
}
},
@ -667,6 +669,7 @@ try
loadout.RootEntity.Hull.ItemKey == "aetheria.item_definition:legacy:smoke:hull" &&
loadout.RootEntity.Equipment.Length == 1 &&
loadout.RootEntity.Equipment[0].Position.X == 1 &&
!loadout.RootEntity.Equipment[0].Item.Enabled &&
loadout.RootEntity.CargoContents.Length == 1 &&
loadout.RootEntity.CargoContents[0].Items[0].Item.Quantity == 5 &&
run?.ZoneKeys.Length == 1 &&
@ -702,6 +705,7 @@ try
entity.Equipment[0].Quality == 0.9 &&
entity.Equipment[0].Durability == 0.8 &&
entity.Equipment[0].Quantity == 1 &&
!entity.Equipment[0].Enabled &&
entity.Velocity.X == 4.0 &&
entity.Velocity.Y == -2.0 &&
entity.TargetEntityKey == "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" &&
@ -816,6 +820,7 @@ try
packageLoadouts.Count != 1 ||
packageLoadouts[0].RootEntity.Hull.ItemKey != "aetheria.item_definition:legacy:smoke:hull" ||
packageLoadouts[0].RootEntity.Equipment[0].X != 1 ||
packageLoadouts[0].RootEntity.Equipment[0].Item.Enabled ||
packageLoadouts[0].RootEntity.CargoContents[0].Items[0].Item.Quantity != 5 ||
packageRuns.Count != 1 ||
packageRuns[0].RunId != "smoke-run" ||
@ -841,6 +846,7 @@ try
packageEntities[0].Equipment[0].Quality != 0.9 ||
packageEntities[0].Equipment[0].Durability != 0.8 ||
packageEntities[0].Equipment[0].Quantity != 1 ||
packageEntities[0].Equipment[0].Enabled ||
packageEntities[0].VelocityX != 4.0 ||
packageEntities[0].VelocityY != -2.0 ||
packageEntities[0].TargetEntityKey != "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" ||

View file

@ -168,7 +168,8 @@ public static class AetheriaRuntimeCommitLogApplier
ItemKey = ReferenceKey("aetheria.item_definition", item.ItemDefinitionLegacyId ?? ""),
Quality = item.Quality,
Durability = item.Durability,
Quantity = item.Quantity
Quantity = item.Quantity,
Enabled = item.Enabled
};
}
@ -205,7 +206,8 @@ public static class AetheriaRuntimeCommitLogApplier
ItemKey = item.ItemKey,
Quality = item.Quality,
Durability = item.Durability,
Quantity = item.Quantity
Quantity = item.Quantity,
Enabled = item.Enabled
};
})
.ToArray();

View file

@ -168,6 +168,9 @@ public sealed class AetheriaLoadoutItem
[Key(3)]
public int Quantity { get; set; } = 1;
[Key(4)]
public bool Enabled { get; set; } = true;
}
[MessagePackObject]
@ -568,6 +571,9 @@ public sealed class AetheriaEntityItemSlot
[Key(4)]
public int Quantity { get; set; } = 1;
[Key(5)]
public bool Enabled { get; set; } = true;
}
[MessagePackObject]

View file

@ -260,8 +260,12 @@ public class ActionGameManager : MonoBehaviour
return ItemManager.CreateSimpleCommodityInstance(typedItem, Math.Max(1, item.Quantity));
var instance = ItemManager.CreateCraftedInstance(typedItem, (float)item.Quality);
if (instance is EquippableItem equippable && item.Durability > 0)
equippable.Durability = (float)item.Durability;
if (instance is EquippableItem equippable)
{
equippable.Enabled.Value = item.Enabled;
if (item.Durability > 0)
equippable.Durability = (float)item.Durability;
}
return instance;
}
@ -524,7 +528,8 @@ public class ActionGameManager : MonoBehaviour
ItemDefinitionLegacyId = item.ItemId == Guid.Empty ? "" : item.ItemId.ToString("D"),
Quality = item is CraftedItemInstance crafted ? crafted.Quality : 1.0,
Durability = item is EquippableItem equippable ? equippable.Durability : 1.0,
Quantity = item is SimpleCommodity commodity ? commodity.Quantity : 1
Quantity = item is SimpleCommodity commodity ? commodity.Quantity : 1,
Enabled = item is not EquippableItem enabledItem || enabledItem.Enabled.Value
};
}

View file

@ -636,18 +636,20 @@ namespace GameCult.Aetheria.State.Unity
public sealed class AetheriaRuntimeLoadoutItemSnapshot
{
public AetheriaRuntimeLoadoutItemSnapshot(string itemKey, double quality, double durability, int quantity)
public AetheriaRuntimeLoadoutItemSnapshot(string itemKey, double quality, double durability, int quantity, bool enabled)
{
ItemKey = itemKey;
Quality = quality;
Durability = durability;
Quantity = quantity;
Enabled = enabled;
}
public string ItemKey { get; }
public double Quality { get; }
public double Durability { get; }
public int Quantity { get; }
public bool Enabled { get; }
}
public sealed class AetheriaRuntimeLoadoutItemSlotSnapshot
@ -973,7 +975,7 @@ namespace GameCult.Aetheria.State.Unity
public sealed class AetheriaRuntimeEntityItemSlotSnapshot
{
public AetheriaRuntimeEntityItemSlotSnapshot(int x, int y, string itemKey, double quality, double durability, int quantity)
public AetheriaRuntimeEntityItemSlotSnapshot(int x, int y, string itemKey, double quality, double durability, int quantity, bool enabled)
{
X = x;
Y = y;
@ -981,6 +983,7 @@ namespace GameCult.Aetheria.State.Unity
Quality = quality;
Durability = durability;
Quantity = quantity;
Enabled = enabled;
}
public int X { get; }
@ -989,6 +992,7 @@ namespace GameCult.Aetheria.State.Unity
public double Quality { get; }
public double Durability { get; }
public int Quantity { get; }
public bool Enabled { get; }
}
public sealed class AetheriaRuntimeEntityStatGridSnapshot

View file

@ -769,14 +769,16 @@ namespace GameCult.Aetheria.State.Unity
var quality = ReadFieldDouble(ref reader, slotFields, 2, 1);
var durability = ReadFieldDouble(ref reader, slotFields, 3, 1);
var quantity = ReadFieldInt32(ref reader, slotFields, 4);
SkipRemaining(ref reader, slotFields, 5);
var enabled = ReadFieldBool(ref reader, slotFields, 5, true);
SkipRemaining(ref reader, slotFields, 6);
slots[slot] = new AetheriaRuntimeEntityItemSlotSnapshot(
position.X,
position.Y,
itemKey,
quality,
durability,
quantity <= 0 ? 1 : quantity);
quantity <= 0 ? 1 : quantity,
enabled);
}
return slots;
@ -1058,6 +1060,11 @@ namespace GameCult.Aetheria.State.Unity
return index < fields && reader.ReadBoolean();
}
private static bool ReadFieldBool(ref MessagePackReader reader, int fields, int index, bool fallback)
{
return index >= fields ? fallback : reader.ReadBoolean();
}
private static long ReadFieldInt64(ref MessagePackReader reader, int fields, int index)
{
return index >= fields ? 0 : reader.ReadInt64();
@ -1220,13 +1227,14 @@ namespace GameCult.Aetheria.State.Unity
var quality = ReadFieldDouble(ref reader, itemFields, 1, 1);
var durability = ReadFieldDouble(ref reader, itemFields, 2, 1);
var quantity = ReadFieldInt32(ref reader, itemFields, 3);
SkipRemaining(ref reader, itemFields, 4);
return new AetheriaRuntimeLoadoutItemSnapshot(itemKey, quality, durability, quantity);
var enabled = ReadFieldBool(ref reader, itemFields, 4, true);
SkipRemaining(ref reader, itemFields, 5);
return new AetheriaRuntimeLoadoutItemSnapshot(itemKey, quality, durability, quantity, enabled);
}
private static AetheriaRuntimeLoadoutItemSnapshot EmptyLoadoutItem()
{
return new AetheriaRuntimeLoadoutItemSnapshot("", 1, 1, 1);
return new AetheriaRuntimeLoadoutItemSnapshot("", 1, 1, 1, true);
}
private static IReadOnlyList<AetheriaRuntimeLoadoutItemSlotSnapshot> ReadFieldLoadoutItemSlots(ref MessagePackReader reader, int fields, int index)

View file

@ -184,6 +184,9 @@ namespace GameCult.Aetheria.State.Unity
[Key(3)]
public int Quantity { get; set; } = 1;
[Key(4)]
public bool Enabled { get; set; } = true;
}
[MessagePackObject]

View file

@ -158,8 +158,8 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
contact rows for gathered information, hostility, and visible classification,
and active consumable item/timer rows.
Entity equipment, cargo-bay, and docking-bay slot rows preserve item-instance
facts needed for restore: item key, quality, durability, and quantity. Those
facts are no longer discarded when pending checkpoint commands become
facts needed for restore: item key, quality, durability, quantity, and enabled
state. Those facts are no longer discarded when pending checkpoint commands become
canonical `AetheriaEntitySnapshot` documents or package readback DTOs.
Cargo bay contents, docking bay contents, and docking bay child assignments
also persist as typed snapshot rows, so inventories and docked-child
@ -850,6 +850,8 @@ First Aetheria surfaces to publish:
- Done: preserve entity snapshot item-instance facts for equipment,
cargo-bay, and docking-bay slot rows: item key, quality, durability, and
quantity now survive canonical state and Unity package readback.
- Done: preserve item enabled state on typed loadout items and entity item
slots, so disabled equipment is no longer only runtime blueprint state.
- Done: preserve cargo bay contents, docking bay contents, and docking bay
child assignments in typed entity snapshots.
- Done: preserve aggregate entity visibility and visibility-source count in