Use managed typed inventory documents

This commit is contained in:
Meta Vens 2026-06-28 07:45:31 +01:00
commit 14a4680c5f
4 changed files with 97 additions and 36 deletions

View file

@ -90,6 +90,7 @@ RequireEntityDestroyedRequestAuthority(root);
RequireDroppedPickupCheckpointState(root);
RequireTradePurchaseRequestAuthority(root);
RequireInventoryProjectionSlotIdentity(root);
RequireInventoryValidationUsesManagedTypedDocuments(root);
RequireInventoryLoadoutSaveRequestAuthority(root);
RequireInventoryLoadoutRestoreRequestAuthority(root);
RequireDockedCurrentShipRequestAuthority(root);
@ -6816,6 +6817,78 @@ static void RequireInventoryProjectionSlotIdentity(string root)
}
}
static void RequireInventoryValidationUsesManagedTypedDocuments(string root)
{
var clientStatePath = Path.Combine(
root,
"Packages",
"org.gamecult.aetheria.state",
"Runtime",
"AetheriaClientState.cs");
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
if (!File.Exists(clientStatePath) ||
!File.Exists(inventoryMenuPath) ||
!File.Exists(inventoryPanelPath))
{
throw new InvalidOperationException("Cannot verify managed inventory document access; expected client state and inventory UI sources are missing.");
}
var clientState = File.ReadAllText(clientStatePath);
if (!clientState.Contains("public Task<AetheriaRuntimeInventoryDocument> LatestInventoryAsync(int entityIndex)", StringComparison.Ordinal) ||
!clientState.Contains("public AetheriaRuntimeInventoryDocument LatestInventory(int entityIndex)", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"AetheriaClientDetailState must expose managed typed latest access for indexed inventory documents.");
}
var sources = new Dictionary<string, string>
{
["InventoryMenu.cs"] = File.ReadAllText(inventoryMenuPath),
["InventoryPanel.cs"] = File.ReadAllText(inventoryPanelPath)
};
foreach (var (name, source) in sources)
{
var compact = CompactSource(source);
var requiredSymbols = new[]
{
"var state = ResolveClient().Aetheria();",
"state.Latest<AetheriaRuntimeCurrentEntityDocument>()",
"state.Latest<AetheriaRuntimeStationRefitDocument>()",
"state.Details.LatestInventory(entityIndex)"
};
var missingSymbols = requiredSymbols
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (missingSymbols.Length > 0)
{
throw new InvalidOperationException(
$"{name} no longer validates inventory slots through managed typed client documents: " +
string.Join(", ", missingSymbols));
}
var forbiddenCompactedSymbols = new[]
{
".Aetheria().Current.Entity.LatestAsync()",
".Aetheria().StationRefit.LatestAsync()",
".Details.Inventory(entityIndex).LatestAsync()"
}
.Select(CompactSource)
.ToArray();
var hits = forbiddenCompactedSymbols
.Where(symbol => compact.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (hits.Length > 0)
{
throw new InvalidOperationException(
$"{name} still walks CultMesh document handles manually instead of using managed typed client state.");
}
}
Console.WriteLine("Inventory validation: cargo/equipment checks read managed typed client documents instead of manual handle walks");
}
static void RequireVerseHostSettingsAuthority(string root)
{
var settingsPath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaVerseHostSettings.cs");
@ -14828,7 +14901,7 @@ static void RequireInventoryDoubleClickTransferRequestAuthority(string root)
"TryValidateTypedCargoSlot(",
"TryValidateTypedEquipmentSlot(",
".Details",
".Inventory(entityIndex)",
".LatestInventory(entityIndex)",
"origin.Cargo.TryGetValue(item, out var originPosition)",
"SourceIndex == cargoIndex",
"SourceIndex == equipmentIndex",
@ -15823,6 +15896,11 @@ static bool ContainsUnitySettingsMember(string source, string memberName)
source.Contains($"\nSettings.{memberName}", StringComparison.Ordinal);
}
static string CompactSource(string source)
{
return string.Concat((source ?? "").Where(character => !char.IsWhiteSpace(character)));
}
static string TryReadMethodName(string trimmedLine)
{
if (!StartsWithMethodAccessModifier(trimmedLine) ||

View file

@ -838,13 +838,8 @@ public class InventoryMenu : MonoBehaviour
try
{
var current = ResolveClient()
.Aetheria()
.Current
.Entity
.LatestAsync()
.GetAwaiter()
.GetResult();
var state = ResolveClient().Aetheria();
var current = state.Latest<AetheriaRuntimeCurrentEntityDocument>();
if (current != null && string.Equals(current.EntityKey, entityKey, StringComparison.Ordinal))
{
equipment = current.Equipment ?? Array.Empty<AetheriaRuntimeRtsInventoryItem>();
@ -852,12 +847,7 @@ public class InventoryMenu : MonoBehaviour
return true;
}
var refit = ResolveClient()
.Aetheria()
.StationRefit
.LatestAsync()
.GetAwaiter()
.GetResult();
var refit = state.Latest<AetheriaRuntimeStationRefitDocument>();
var entityIndex = -1;
if (refit != null)
{
@ -872,13 +862,7 @@ public class InventoryMenu : MonoBehaviour
if (entityIndex < 0)
return false;
var inventory = ResolveClient()
.Aetheria()
.Details
.Inventory(entityIndex)
.LatestAsync()
.GetAwaiter()
.GetResult();
var inventory = state.Details.LatestInventory(entityIndex);
if (inventory == null || !string.Equals(inventory.EntityKey, entityKey, StringComparison.Ordinal))
return false;

View file

@ -1255,13 +1255,8 @@ private void Update()
try
{
var current = ResolveClient()
.Aetheria()
.Current
.Entity
.LatestAsync()
.GetAwaiter()
.GetResult();
var state = ResolveClient().Aetheria();
var current = state.Latest<AetheriaRuntimeCurrentEntityDocument>();
if (current != null && string.Equals(current.EntityKey, entityKey, StringComparison.Ordinal))
{
equipment = current.Equipment ?? Array.Empty<AetheriaRuntimeRtsInventoryItem>();
@ -1269,7 +1264,7 @@ private void Update()
return true;
}
var refit = ResolveStationRefit();
var refit = state.Latest<AetheriaRuntimeStationRefitDocument>();
var entityIndex = -1;
if (refit != null)
{
@ -1284,13 +1279,7 @@ private void Update()
if (entityIndex < 0)
return false;
var inventory = ResolveClient()
.Aetheria()
.Details
.Inventory(entityIndex)
.LatestAsync()
.GetAwaiter()
.GetResult();
var inventory = state.Details.LatestInventory(entityIndex);
if (inventory == null || !string.Equals(inventory.EntityKey, entityKey, StringComparison.Ordinal))
return false;

View file

@ -461,6 +461,16 @@ namespace GameCult.Aetheria.State.Verse
{
return _inventory(entityIndex);
}
public Task<AetheriaRuntimeInventoryDocument> LatestInventoryAsync(int entityIndex)
{
return Inventory(entityIndex).LatestAsync();
}
public AetheriaRuntimeInventoryDocument LatestInventory(int entityIndex)
{
return LatestInventoryAsync(entityIndex).ConfigureAwait(false).GetAwaiter().GetResult();
}
}
public sealed class AetheriaClientStarbridgeState