2777 lines
117 KiB
C#
2777 lines
117 KiB
C#
using Aetheria.State;
|
|
using Aetheria.State.Documents;
|
|
using GameCult.Aetheria.State.Unity;
|
|
|
|
var root = args.Length > 0 ? Path.GetFullPath(args[0]) : Directory.GetCurrentDirectory();
|
|
var statePath = args.Length > 1
|
|
? Path.GetFullPath(args[1])
|
|
: AetheriaStatePaths.ResolveDefaultStatePath(root);
|
|
|
|
RequireGameplaySourcePurity(root);
|
|
RequirePackageSerializerBoundary(root);
|
|
RequireTypedPendingCommitKeys(root);
|
|
RequireTypedRuntimeFactionKeys(root);
|
|
RequireTypedGalaxyFactionRelationships(root);
|
|
RequireRuntimeCatalogKeyOnlyLookups(root);
|
|
RequireTypedBehaviorBodyKeys(root);
|
|
RequireTypedOrbitTaskKeys(root);
|
|
RequireTypedOrbitalEntityOrbitKeys(root);
|
|
RequireTypedOrbitConsumerKeys(root);
|
|
RequireKeyedOrbitRuntimeWrappers(root);
|
|
RequireNativeZoneKeyResolution(root);
|
|
RequireTypedZoneRuntimeCollections(root);
|
|
RequireTypedZoneConstructionKeys(root);
|
|
RequireTypedZoneStateSnapshotKeys(root);
|
|
RequireTypedAsteroidZoneApi(root);
|
|
RequireTypedFactionShellLinks(root);
|
|
RequireFactionKeyIdentity(root);
|
|
RequireEveRuntimeBootstrap(root);
|
|
RequireNoRendererLocalConsole(root);
|
|
RequireNoRendererLocalDebugPanels(root);
|
|
RequireMainMenuSettingsCommit(root);
|
|
RequirePlayerSettingsEveSurface(root);
|
|
RequireMainMenuContinueRunState(root);
|
|
RequirePropertiesPanelReadOnlyInspector(root);
|
|
RequireRuntimeSimulationTuningCommits(root);
|
|
RequireHullConductivityCommitAuthority(root);
|
|
RequireInventoryEntityRenameCommitAuthority(root);
|
|
RequireWeaponGroupCommitAuthority(root);
|
|
RequireInventoryDoubleClickTransferCommitAuthority(root);
|
|
RequireLootPickupCommitAuthority(root);
|
|
RequireEntityDestroyedCommitAuthority(root);
|
|
RequireDroppedPickupCheckpointState(root);
|
|
RequireTradePurchaseCommitAuthority(root);
|
|
RequireInventoryLoadoutRestoreCommitAuthority(root);
|
|
RequireDockedCurrentShipCommitAuthority(root);
|
|
|
|
await using var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-state-verify");
|
|
|
|
var ledger = await node.GetMigrationLedgerAsync()
|
|
?? throw new InvalidOperationException("Missing typed migration ledger.");
|
|
var quarantine = await node.GetLegacyCatalogQuarantineAsync()
|
|
?? throw new InvalidOperationException("Missing legacy catalog quarantine document.");
|
|
var publishedSurface = await node.GetCatalogSurfaceAsync()
|
|
?? throw new InvalidOperationException("Missing Aetheria catalog Eve surface document.");
|
|
|
|
var items = node.Cache.GetAll<AetheriaItemDefinition>().ToArray();
|
|
var corporations = node.Cache.GetAll<AetheriaCorporation>().ToArray();
|
|
var nameFiles = node.Cache.GetAll<AetheriaNameFile>().ToArray();
|
|
var catalog = node.ReadCatalogSnapshot();
|
|
var surface = AetheriaCatalogSurfaceProjector.Build(catalog, DateTimeOffset.UtcNow.ToString("O"));
|
|
|
|
RequireCount(ledger, "aetheria.item_definition.v1", items.Length);
|
|
RequireCount(ledger, "aetheria.corporation.v2", corporations.Length);
|
|
RequireCount(ledger, "aetheria.name_file.v2", nameFiles.Length);
|
|
|
|
if (items.Length == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed state has no item definitions.");
|
|
}
|
|
|
|
if (corporations.Length == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed state has no corporations.");
|
|
}
|
|
|
|
if (nameFiles.Length == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed state has no name files.");
|
|
}
|
|
|
|
if (nameFiles.Any(nameFile => nameFile.Names.Length == 0 || nameFile.Names.Length != nameFile.NameCount))
|
|
{
|
|
throw new InvalidOperationException("Typed name files did not import their full name arrays.");
|
|
}
|
|
|
|
var pricedItems = items.Count(item => item.Price > 0);
|
|
var manufacturedItems = items.Count(item => !string.IsNullOrWhiteSpace(item.ManufacturerLegacyId));
|
|
var specificHeatItems = items.Count(item => item.SpecificHeat > 0);
|
|
var conductiveItems = items.Count(item => item.Conductivity > 0);
|
|
var shapedItems = items.Count(item => item.ShapeWidth > 0 && item.ShapeHeight > 0 && item.OccupiedCells > 0);
|
|
var shapedMaskItems = items.Count(item => item.ShapeCells.Length > 0);
|
|
var interiorShapeItems = items.Count(item => item.InteriorShapeCells.Length > 0);
|
|
var hardpointHostItems = items.Count(item => item.Hardpoints.Length > 0);
|
|
var hardpointCount = items.Sum(item => item.Hardpoints.Length);
|
|
var behaviorItems = items.Count(item => item.BehaviorCount > 0 && item.BehaviorKinds.Length > 0);
|
|
var behaviorPayloadItems = items.Count(item => item.BehaviorPayloads.Length > 0);
|
|
var behaviorPayloadCount = items.Sum(item => item.BehaviorPayloads.Length);
|
|
var behaviorFieldCount = items.SelectMany(item => item.BehaviorPayloads).Sum(behavior => behavior.Fields.Length);
|
|
var behaviorLegacyRefCount = items
|
|
.SelectMany(item => item.BehaviorPayloads)
|
|
.SelectMany(behavior => behavior.Fields)
|
|
.Count(field => ContainsBehaviorValueKind(field.Value, "legacy-id"));
|
|
var behaviorItemRefsMissingItemKeys = items
|
|
.SelectMany(item => item.BehaviorPayloads)
|
|
.Sum(CountRequiredBehaviorItemRefsMissingItemKeys);
|
|
var hardpointItems = items.Count(item => !string.IsNullOrWhiteSpace(item.HardpointType));
|
|
var hullItems = items.Count(item => !string.IsNullOrWhiteSpace(item.HullType));
|
|
var hullPrefabItems = items.Count(item => !string.IsNullOrWhiteSpace(item.HullPrefab));
|
|
var hullArmorItems = items.Count(item => !string.IsNullOrWhiteSpace(item.HullType) && item.HullArmor > 0);
|
|
var hullPhysicalFacetItems = items.Count(item =>
|
|
!string.IsNullOrWhiteSpace(item.HullType) &&
|
|
item.HullArmor > 0 &&
|
|
item.HullDrag >= 0 &&
|
|
!double.IsNaN(item.HullGridOffset));
|
|
var simpleCommodityCategoryItems = items.Count(item => !string.IsNullOrWhiteSpace(item.SimpleCommodityCategory));
|
|
var compoundCommodityCategoryItems = items.Count(item => !string.IsNullOrWhiteSpace(item.CompoundCommodityCategory));
|
|
var weaponItems = items.Count(item =>
|
|
!string.IsNullOrWhiteSpace(item.WeaponType) &&
|
|
!string.IsNullOrWhiteSpace(item.WeaponRange) &&
|
|
!string.IsNullOrWhiteSpace(item.WeaponCaliber));
|
|
var maxDurabilityItems = items
|
|
.Where(item => !string.IsNullOrWhiteSpace(item.HullType) || !string.IsNullOrWhiteSpace(item.WeaponType))
|
|
.ToArray();
|
|
var durableMaxDurabilityItems = maxDurabilityItems.Count(item => item.Durability > 0);
|
|
var importedThermalRangeItems = items.Count(item => item.MaximumTemperature > item.MinimumTemperature);
|
|
var importedThermalCurveItems = items.Count(item => item.ThermalPerformanceCurveKeys.Length > 0);
|
|
var thermalResilienceItems = items.Count(item => item.ThermalResilience > 0);
|
|
var audioStatItems = items.Count(item => item.AudioStats.Length > 0);
|
|
var audioStatCount = items.Sum(item => item.AudioStats.Length);
|
|
var consumableItems = items.Count(item => item.Category == AetheriaRuntimeItemCategories.Consumable);
|
|
var consumableDurationItems = items.Count(item => item.Category == AetheriaRuntimeItemCategories.Consumable && item.Duration > 0);
|
|
var consumableEffectivenessItems = items.Count(item => item.EffectivenessCurveKeys.Length > 0);
|
|
var actionBarIconItems = items.Count(item => !string.IsNullOrWhiteSpace(item.ActionBarIcon));
|
|
var dockingBayItems = items.Count(item => item.Category == AetheriaRuntimeItemCategories.DockingBay);
|
|
var dockingBayMaxSizeItems = items.Count(item =>
|
|
item.Category == AetheriaRuntimeItemCategories.DockingBay &&
|
|
item.DockingMaxSizeX > 0 &&
|
|
item.DockingMaxSizeY > 0);
|
|
var describedCorporations = corporations.Count(corporation => !string.IsNullOrWhiteSpace(corporation.Description));
|
|
var corporationNameLinks = corporations.Count(corporation => !string.IsNullOrWhiteSpace(corporation.GeonameFileLegacyId));
|
|
var corporationAllegianceEdges = corporations.Sum(corporation => corporation.Allegiances.Length);
|
|
var legacyDtoCategories = items
|
|
.Where(item => item.Category.EndsWith("Data", StringComparison.Ordinal))
|
|
.Select(item => $"{item.Name}:{item.Category}")
|
|
.ToArray();
|
|
|
|
if (pricedItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any prices.");
|
|
}
|
|
|
|
if (legacyDtoCategories.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed item categories still contain legacy DTO class names: " +
|
|
string.Join(", ", legacyDtoCategories.Take(5)));
|
|
}
|
|
|
|
if (manufacturedItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any manufacturer legacy IDs.");
|
|
}
|
|
|
|
if (specificHeatItems != items.Length)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed item definitions did not import positive specific heat for every item: {specificHeatItems}/{items.Length}.");
|
|
}
|
|
|
|
if (conductiveItems != items.Length)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed item definitions did not import positive conductivity for every item: {conductiveItems}/{items.Length}.");
|
|
}
|
|
|
|
if (thermalResilienceItems != items.Length)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed item definitions did not import positive thermal resilience for every item: {thermalResilienceItems}/{items.Length}.");
|
|
}
|
|
|
|
if (shapedItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any shape dimensions.");
|
|
}
|
|
|
|
if (shapedMaskItems != shapedItems)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed item shape mask import mismatch: masks={shapedMaskItems}, shaped={shapedItems}.");
|
|
}
|
|
|
|
foreach (var item in items.Where(item => item.ShapeCells.Length > 0))
|
|
{
|
|
if (item.ShapeCells.Length != item.OccupiedCells)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed item shape mask cell count mismatch for {item.Name}: cells={item.ShapeCells.Length}, occupied={item.OccupiedCells}.");
|
|
}
|
|
|
|
if (item.ShapeCells.Any(cell => cell.X < 0 || cell.Y < 0 || cell.X >= item.ShapeWidth || cell.Y >= item.ShapeHeight))
|
|
{
|
|
throw new InvalidOperationException($"Typed item shape mask has out-of-bounds cells for {item.Name}.");
|
|
}
|
|
}
|
|
|
|
foreach (var item in items.Where(item => item.InteriorShapeCells.Length > 0))
|
|
{
|
|
if (item.InteriorShapeCells.Length != item.InteriorOccupiedCells)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed item interior shape mask count mismatch for {item.Name}: cells={item.InteriorShapeCells.Length}, occupied={item.InteriorOccupiedCells}.");
|
|
}
|
|
|
|
if (item.InteriorShapeCells.Any(cell =>
|
|
cell.X < 0 || cell.Y < 0 || cell.X >= item.InteriorShapeWidth || cell.Y >= item.InteriorShapeHeight))
|
|
{
|
|
throw new InvalidOperationException($"Typed item interior shape mask has out-of-bounds cells for {item.Name}.");
|
|
}
|
|
}
|
|
|
|
if (interiorShapeItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any interior shape masks.");
|
|
}
|
|
|
|
if (hardpointHostItems == 0 || hardpointCount == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any hull hardpoints.");
|
|
}
|
|
|
|
var missingMaxDurability = maxDurabilityItems
|
|
.Where(item => item.Durability <= 0)
|
|
.Select(item => item.Name)
|
|
.ToArray();
|
|
if (missingMaxDurability.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed hull/weapon max durability missing for: {string.Join(", ", missingMaxDurability)}.");
|
|
}
|
|
|
|
if (importedThermalRangeItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any thermal ranges.");
|
|
}
|
|
|
|
if (importedThermalCurveItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any thermal performance curves.");
|
|
}
|
|
|
|
foreach (var item in items.Where(item => item.ThermalPerformanceCurveKeys.Length > 0))
|
|
{
|
|
if (item.ThermalPerformanceCurveKeys.Any(key => key.Time < 0 || key.Time > 1))
|
|
{
|
|
throw new InvalidOperationException($"Typed thermal performance curve has out-of-range key time for {item.Name}.");
|
|
}
|
|
}
|
|
|
|
foreach (var item in items.Where(item => item.Hardpoints.Length > 0))
|
|
{
|
|
foreach (var hardpoint in item.Hardpoints)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(hardpoint.Type))
|
|
{
|
|
throw new InvalidOperationException($"Typed item hardpoint has no type for {item.Name}.");
|
|
}
|
|
|
|
if (hardpoint.ShapeCells.Length != hardpoint.OccupiedCells)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed item hardpoint shape count mismatch for {item.Name}: cells={hardpoint.ShapeCells.Length}, occupied={hardpoint.OccupiedCells}.");
|
|
}
|
|
|
|
if (hardpoint.ShapeCells.Any(cell =>
|
|
cell.X < 0 || cell.Y < 0 || cell.X >= hardpoint.ShapeWidth || cell.Y >= hardpoint.ShapeHeight))
|
|
{
|
|
throw new InvalidOperationException($"Typed item hardpoint shape has out-of-bounds cells for {item.Name}.");
|
|
}
|
|
|
|
// Legacy content includes at least one overhanging hardpoint. Preserve the
|
|
// payload faithfully here; content repair belongs to a separate pass.
|
|
}
|
|
}
|
|
|
|
if (behaviorItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any behavior fingerprints.");
|
|
}
|
|
|
|
if (behaviorPayloadItems != behaviorItems || behaviorPayloadCount == 0 || behaviorFieldCount == 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed behavior payload import mismatch: payloadItems={behaviorPayloadItems}, behaviorItems={behaviorItems}, payloads={behaviorPayloadCount}, fields={behaviorFieldCount}.");
|
|
}
|
|
|
|
if (behaviorItemRefsMissingItemKeys > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed behavior payload item refs missing item-key projections: {behaviorItemRefsMissingItemKeys}.");
|
|
}
|
|
|
|
foreach (var item in items.Where(item => item.BehaviorPayloads.Length > 0))
|
|
{
|
|
if (item.BehaviorPayloads.Length != item.BehaviorCount)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed item behavior payload count mismatch for {item.Name}: payloads={item.BehaviorPayloads.Length}, count={item.BehaviorCount}.");
|
|
}
|
|
|
|
foreach (var behavior in item.BehaviorPayloads)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(behavior.Kind) || behavior.UnionKey < 0 || behavior.Fields.Length == 0)
|
|
{
|
|
throw new InvalidOperationException($"Typed item behavior payload is incomplete for {item.Name}.");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hardpointItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any hardpoint types.");
|
|
}
|
|
|
|
if (hullItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any hull types.");
|
|
}
|
|
|
|
if (hullPrefabItems != hullItems)
|
|
{
|
|
throw new InvalidOperationException($"Typed hull prefab import mismatch: hulls={hullItems}, prefabs={hullPrefabItems}.");
|
|
}
|
|
|
|
if (hullArmorItems != hullItems || hullPhysicalFacetItems != hullItems)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed hull physical facet import mismatch: hulls={hullItems}, armor={hullArmorItems}, physical={hullPhysicalFacetItems}.");
|
|
}
|
|
|
|
if (simpleCommodityCategoryItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed simple commodity category import produced no categories.");
|
|
}
|
|
|
|
if (compoundCommodityCategoryItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed compound commodity category import produced no categories.");
|
|
}
|
|
|
|
if (weaponItems == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed item definitions did not import any weapon facets.");
|
|
}
|
|
|
|
if (dockingBayItems == 0 || dockingBayMaxSizeItems != dockingBayItems)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed docking bay max-size import mismatch: dockingBays={dockingBayItems}, maxSizes={dockingBayMaxSizeItems}.");
|
|
}
|
|
|
|
if (describedCorporations == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed corporations did not import descriptions from legacy key 3.");
|
|
}
|
|
|
|
if (corporationNameLinks == 0)
|
|
{
|
|
throw new InvalidOperationException("Typed corporations did not import geoname file legacy IDs.");
|
|
}
|
|
|
|
if (corporationAllegianceEdges == 0 || corporations.Any(corporation => corporation.Allegiances.Length != corporation.AllegianceCount))
|
|
{
|
|
throw new InvalidOperationException("Typed corporations did not import full allegiance edges.");
|
|
}
|
|
|
|
var tradeItems = catalog.TradeItems.ToArray();
|
|
if (tradeItems.Length != pricedItems)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed catalog trade item query mismatch: query={tradeItems.Length}, priced={pricedItems}.");
|
|
}
|
|
|
|
var equipmentItems = catalog.EquipmentItems.ToArray();
|
|
if (equipmentItems.Length != hardpointItems)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed catalog equipment item query mismatch: query={equipmentItems.Length}, hardpoint={hardpointItems}.");
|
|
}
|
|
|
|
var behaviorKind = items.SelectMany(item => item.BehaviorKinds).FirstOrDefault()
|
|
?? throw new InvalidOperationException("Cannot verify typed catalog behavior query: no behavior kinds.");
|
|
if (!catalog.FindItemsByBehavior(behaviorKind).Any())
|
|
{
|
|
throw new InvalidOperationException($"Typed catalog behavior query failed for {behaviorKind}.");
|
|
}
|
|
|
|
var hardpointType = items.FirstOrDefault(item => !string.IsNullOrWhiteSpace(item.HardpointType))?.HardpointType
|
|
?? throw new InvalidOperationException("Cannot verify typed catalog hardpoint query: no hardpoint types.");
|
|
if (!catalog.FindItemsByHardpoint(hardpointType).Any())
|
|
{
|
|
throw new InvalidOperationException($"Typed catalog hardpoint query failed for {hardpointType}.");
|
|
}
|
|
|
|
var manufacturedItem = items.FirstOrDefault(item => !string.IsNullOrWhiteSpace(item.ManufacturerLegacyId))
|
|
?? throw new InvalidOperationException("Cannot verify typed catalog manufacturer lookup: no manufactured item.");
|
|
if (string.IsNullOrWhiteSpace(manufacturedItem.ManufacturerKey) ||
|
|
catalog.FindCorporation(manufacturedItem.ManufacturerKey) == null ||
|
|
catalog.GetManufacturer(manufacturedItem) == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed catalog manufacturer-key lookup failed for item {manufacturedItem.Name}.");
|
|
}
|
|
|
|
var corporationWithNames = corporations.FirstOrDefault(corporation =>
|
|
!string.IsNullOrWhiteSpace(corporation.GeonameFileLegacyId))
|
|
?? throw new InvalidOperationException("Cannot verify typed catalog name-file lookup: no linked corporation.");
|
|
if (string.IsNullOrWhiteSpace(corporationWithNames.GeonameFileKey) ||
|
|
catalog.FindNameFile(corporationWithNames.GeonameFileKey) == null ||
|
|
catalog.GetNameFile(corporationWithNames) == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Typed catalog name-file-key lookup failed for corporation {corporationWithNames.Name}.");
|
|
}
|
|
|
|
if (surface.Schema != "gamecult.eve.surface.v1" ||
|
|
surface.Surface.Root.Kind != "surface" ||
|
|
surface.Surface.Root.Children.Length == 0)
|
|
{
|
|
throw new InvalidOperationException("Aetheria catalog Eve surface projection is not renderable.");
|
|
}
|
|
|
|
if (publishedSurface.Schema != surface.Schema ||
|
|
publishedSurface.Surface.Id != AetheriaCatalogSurfaceProjector.SurfaceId ||
|
|
publishedSurface.Surface.Root.Kind != "surface")
|
|
{
|
|
throw new InvalidOperationException("Published Aetheria catalog Eve surface is not the expected typed surface.");
|
|
}
|
|
|
|
await RequireLegacyLookupAsync(
|
|
items[0].LegacyId,
|
|
() => node.GetItemDefinitionByLegacyIdAsync(items[0].LegacyId),
|
|
"item definition");
|
|
await RequireLegacyLookupAsync(
|
|
corporations[0].LegacyId,
|
|
() => node.GetCorporationByLegacyIdAsync(corporations[0].LegacyId),
|
|
"corporation");
|
|
await RequireLegacyLookupAsync(
|
|
nameFiles[0].LegacyId,
|
|
() => node.GetNameFileByLegacyIdAsync(nameFiles[0].LegacyId),
|
|
"name file");
|
|
|
|
if (string.IsNullOrWhiteSpace(quarantine.CatalogFingerprint))
|
|
{
|
|
throw new InvalidOperationException("Legacy catalog quarantine has no catalog fingerprint.");
|
|
}
|
|
|
|
Console.WriteLine($"Aetheria typed state verify passed: {statePath}");
|
|
Console.WriteLine($"Catalog fingerprint: {quarantine.CatalogFingerprint}");
|
|
Console.WriteLine($"Item definitions: {items.Length}");
|
|
Console.WriteLine($"Priced/manufactured/specific-heat/conductive/shaped items: {pricedItems}/{manufacturedItems}/{specificHeatItems}/{conductiveItems}/{shapedItems}");
|
|
Console.WriteLine($"Thermal resilience items: {thermalResilienceItems}");
|
|
Console.WriteLine($"Audio stat items/stats: {audioStatItems}/{audioStatCount}");
|
|
Console.WriteLine($"Consumable duration/effectiveness items: {consumableDurationItems}/{consumableEffectivenessItems} of {consumableItems}");
|
|
Console.WriteLine($"Shape masks: {shapedMaskItems}");
|
|
Console.WriteLine($"Interior masks/hardpoint hosts/hardpoints: {interiorShapeItems}/{hardpointHostItems}/{hardpointCount}");
|
|
Console.WriteLine($"Behavior payload items/payloads/fields/legacy refs: {behaviorPayloadItems}/{behaviorPayloadCount}/{behaviorFieldCount}/{behaviorLegacyRefCount}");
|
|
Console.WriteLine($"Behavior/hardpoint/hull/weapon items: {behaviorItems}/{hardpointItems}/{hullItems}/{weaponItems}");
|
|
Console.WriteLine($"Hull prefab items: {hullPrefabItems}");
|
|
Console.WriteLine($"Hull armor/physical facet items: {hullArmorItems}/{hullPhysicalFacetItems}");
|
|
Console.WriteLine($"Simple/compound commodity category items: {simpleCommodityCategoryItems}/{compoundCommodityCategoryItems}");
|
|
Console.WriteLine($"Durable hull/weapon items: {durableMaxDurabilityItems}/{maxDurabilityItems.Length}");
|
|
Console.WriteLine($"Thermal range/curve items: {importedThermalRangeItems}/{importedThermalCurveItems}");
|
|
Console.WriteLine($"Action-bar icon items: {actionBarIconItems}");
|
|
Console.WriteLine($"Docking bay max-size items: {dockingBayMaxSizeItems}/{dockingBayItems}");
|
|
Console.WriteLine($"Typed catalog trade items: {tradeItems.Length}");
|
|
Console.WriteLine($"Eve catalog surface: {surface.Surface.Id} ({surface.Surface.Root.Children.Length} root children)");
|
|
Console.WriteLine($"Corporations: {corporations.Length}");
|
|
Console.WriteLine($"Described/geoname-linked corporations: {describedCorporations}/{corporationNameLinks}");
|
|
Console.WriteLine($"Corporation allegiance edges: {corporationAllegianceEdges}");
|
|
Console.WriteLine($"Name files: {nameFiles.Length}");
|
|
Console.WriteLine("Live gameplay source purity: no serializer or legacy database symbols in Assets/Scripts");
|
|
Console.WriteLine("Package serializer boundary: MessagePack symbols remain in named CultCache transport files only");
|
|
Console.WriteLine("Eve runtime bootstrap: operations surface mounts through UI Toolkit presenter");
|
|
Console.WriteLine("Renderer-local console authority: deleted; UI commands flow through Eve command documents");
|
|
Console.WriteLine("Renderer-local debug panels: obsolete uGUI field tester authority is deleted");
|
|
Console.WriteLine("Main-menu settings authority: gameplay and graphics settings return through typed player-settings commits");
|
|
Console.WriteLine("Main-menu Continue authority: Continue selects typed run state instead of a null button");
|
|
Console.WriteLine("PropertiesPanel inspector authority: reflection inspection is read-only display");
|
|
Console.WriteLine("Runtime simulation tuning authority: UI writes flow through gameplay checkpoint commits");
|
|
Console.WriteLine("Hull conductivity authority: inventory UI toggles flow through gameplay checkpoint commits");
|
|
Console.WriteLine("Inventory entity rename authority: UI rename flows through gameplay checkpoint commits");
|
|
Console.WriteLine("Weapon group authority: UI assignment flows through gameplay checkpoint commits");
|
|
Console.WriteLine("Inventory transfer authority: UI transfer and drag/drop requests flow through gameplay checkpoint commits");
|
|
Console.WriteLine("Loot pickup authority: collision pickup requests flow through gameplay checkpoint commits");
|
|
Console.WriteLine("Entity destruction authority: hull-death observers flow through gameplay checkpoint commits");
|
|
Console.WriteLine("Dropped pickup state: zone checkpoints carry typed dropped-pickup snapshots and keyed live lowering");
|
|
Console.WriteLine("Trade purchase authority: UI buy requests flow through gameplay checkpoint commits");
|
|
Console.WriteLine("Inventory loadout restore authority: UI restore requests flow through gameplay checkpoint commits");
|
|
Console.WriteLine("Docked current-ship authority: UI selection requests flow through gameplay checkpoint commits");
|
|
|
|
static void RequireCount(AetheriaMigrationLedger ledger, string documentType, int actual)
|
|
{
|
|
var expected = ledger.Counts.FirstOrDefault(count => count.DocumentType == documentType)?.Count;
|
|
if (expected == null)
|
|
{
|
|
throw new InvalidOperationException($"Migration ledger is missing count for {documentType}.");
|
|
}
|
|
|
|
if (expected.Value != actual)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Migration ledger count mismatch for {documentType}: ledger={expected.Value}, actual={actual}.");
|
|
}
|
|
}
|
|
|
|
static void RequireGameplaySourcePurity(string root)
|
|
{
|
|
var gameplayRoot = Path.Combine(root, "Assets", "Scripts");
|
|
if (!Directory.Exists(gameplayRoot))
|
|
{
|
|
throw new InvalidOperationException($"Cannot verify live gameplay source purity; missing path: {gameplayRoot}");
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"MessagePack",
|
|
"MessagePackObject",
|
|
"MessagePackSerializer",
|
|
"MessagePackReader",
|
|
"IMessagePackFormatter",
|
|
"JsonConvert",
|
|
"Newtonsoft",
|
|
"JsonKnownTypes",
|
|
"RethinkDB",
|
|
"DatabaseEntry",
|
|
"DatabaseLink",
|
|
"[Union(",
|
|
"[Key("
|
|
};
|
|
|
|
var hits = Directory.EnumerateFiles(gameplayRoot, "*.cs", SearchOption.AllDirectories)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.Take(10)
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Live gameplay source still contains serializer or legacy database symbols: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequirePackageSerializerBoundary(string root)
|
|
{
|
|
var packageRuntimeRoot = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime");
|
|
if (!Directory.Exists(packageRuntimeRoot))
|
|
{
|
|
throw new InvalidOperationException($"Cannot verify package serializer boundary; missing path: {packageRuntimeRoot}");
|
|
}
|
|
|
|
var allowedFiles = new HashSet<string>(StringComparer.Ordinal)
|
|
{
|
|
"AetheriaRuntimeCatalogStore.cs",
|
|
"AetheriaRuntimePendingCultCacheStore.cs",
|
|
"AetheriaRuntimeStateCommitDocument.cs",
|
|
"AetheriaRuntimeEveCommandDocument.cs"
|
|
};
|
|
|
|
var serializerSymbols = new[]
|
|
{
|
|
"MessagePack",
|
|
"MessagePackObject",
|
|
"MessagePackSerializer",
|
|
"MessagePackReader",
|
|
"MessagePackWriter",
|
|
"IMessagePackFormatter",
|
|
"[Union(",
|
|
"[Key("
|
|
};
|
|
|
|
var hits = Directory.EnumerateFiles(packageRuntimeRoot, "*.cs", SearchOption.AllDirectories)
|
|
.Where(path => !allowedFiles.Contains(Path.GetFileName(path)))
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => serializerSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.Take(10)
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Package serializer symbols escaped the named CultCache transport boundary: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedPendingCommitKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateCommitDocument.cs"),
|
|
Path.Combine(root, "Aetheria.State", "AetheriaRuntimeCommitLogApplier.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"CorporationLegacyId",
|
|
"OrbitLegacyId",
|
|
"ParentLegacyId",
|
|
"BodyLegacyId",
|
|
"ResourceScannerTargetBodyId { get; set; }",
|
|
"MiningToolAsteroidBeltId { get; set; }",
|
|
"ReferenceKey(entity.FactionKey",
|
|
"ReferenceKey(orbit.OrbitKey",
|
|
"ReferenceKey(body.BodyKey",
|
|
"ReferenceKey(relationship.FactionKey"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Pending runtime commit authority still accepts legacy ID fallback fields: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedRuntimeFactionKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "EntityConstructionBlueprintProjector.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"public Guid Faction",
|
|
"blueprint.Faction =",
|
|
"blueprint.Faction)",
|
|
"blueprint.Faction,",
|
|
"blueprint.Faction;",
|
|
"ResolveFaction(blueprint.Faction",
|
|
"LegacyId(blueprint.Faction",
|
|
"ReferenceKey(blueprint.FactionKey",
|
|
"CorporationKey(entity.Faction?.ID",
|
|
"CorporationKey(pair.Key.ID",
|
|
"private static string CorporationKey"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime faction commits must use typed FactionKey authority, not legacy GUID fallback: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedGalaxyFactionRelationships(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Corporations.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Galaxy.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "LoadoutGenerator.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"Dictionary<Guid, float> Allegiance",
|
|
".Allegiance[",
|
|
"ContainsFaction(Guid",
|
|
"ResolveFaction(Guid",
|
|
"CorporationLegacyId",
|
|
"factionsByLegacyId",
|
|
"_containedFactions"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Galaxy faction relationships must use typed corporation keys, not legacy GUID allegiance state: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireRuntimeCatalogKeyOnlyLookups(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogSnapshot.cs"),
|
|
Path.Combine(root, "Aetheria.State.Unity.Smoke", "Program.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"_itemsByLegacyId",
|
|
"_corporationsByLegacyId",
|
|
"_nameFilesByLegacyId",
|
|
"FindItemByLegacyId",
|
|
"FindCorporationByLegacyId",
|
|
"FindNameFileByLegacyId",
|
|
"public string LegacyId { get; }",
|
|
"public string ManufacturerLegacyId",
|
|
"public string GeonameFileLegacyId",
|
|
"public string BossHullLegacyId",
|
|
"public string CorporationLegacyId",
|
|
".ManufacturerLegacyId",
|
|
".GeonameFileLegacyId",
|
|
".BossHullLegacyId",
|
|
".CorporationLegacyId"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime catalog snapshot must expose typed-key lookups only; legacy-ID indexes belong to migration boundaries: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedBehaviorBodyKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs"),
|
|
Path.Combine(root, "Aetheria.State", "AetheriaRuntimeCommitLogApplier.cs"),
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogSnapshot.cs"),
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"),
|
|
Path.Combine(root, "Aetheria.State.Smoke", "Program.cs"),
|
|
Path.Combine(root, "Aetheria.State.Unity.Smoke", "Program.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"ResourceScannerTargetBodyId",
|
|
"MiningToolAsteroidBeltId",
|
|
"ParseLegacyGuidFromReferenceKey",
|
|
"ParseLegacyIdFromReferenceKey",
|
|
"ParseBodyGuidFromKey(",
|
|
"private static string OrbitKey(Guid",
|
|
"private static string BodyKey(Guid",
|
|
"$\"aetheria.orbit:legacy:",
|
|
"$\"aetheria.body:legacy:",
|
|
"public Guid ScanTarget",
|
|
"public Guid AsteroidBelt",
|
|
"RestoreRuntimeState(Guid scanTarget",
|
|
"RestoreRuntimeState(Guid asteroidBelt"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime behavior body references must stay on typed BodyKey surfaces, with Zone owning key resolution and behaviors no longer storing raw GUID body ids: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs");
|
|
var zoneSource = File.Exists(zoneSourcePath)
|
|
? File.ReadAllText(zoneSourcePath)
|
|
: throw new InvalidOperationException("Cannot verify body/orbit key ownership; Zone source is missing.");
|
|
var requiredZoneSymbols = new[]
|
|
{
|
|
"public const string OrbitKeyPrefix",
|
|
"public const string BodyKeyPrefix",
|
|
"public string BodyKey { get; }",
|
|
"public string OrbitKey { get; }",
|
|
"public string ParentOrbitKey { get; }",
|
|
"public string GetBodyKey(Guid bodyId)",
|
|
"public bool TryGetPlanet(string bodyKey, out Planet planet)",
|
|
"public bool TryGetAsteroidBelt(string bodyKey, out AsteroidBelt belt)"
|
|
};
|
|
var missingZoneSymbols = requiredZoneSymbols
|
|
.Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingZoneSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Zone runtime body/orbit objects must own typed projection key surfaces: " +
|
|
string.Join(", ", missingZoneSymbols));
|
|
}
|
|
|
|
var requiredBehaviorSymbols = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "ResourceScanner.cs")] = new[]
|
|
{
|
|
"private string _scanTargetBodyKey = \"\";",
|
|
"public string ScanTargetBodyKey",
|
|
"Entity.Zone.TryGetAsteroidBelt(ScanTargetBodyKey, out var belt)",
|
|
"Entity.Zone.TryGetPlanet(ScanTargetBodyKey, out var planet)",
|
|
"RestoreRuntimeState(",
|
|
"string scanTargetBodyKey"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "MiningTool.cs")] = new[]
|
|
{
|
|
"public string AsteroidBeltBodyKey = \"\";",
|
|
"Entity.Zone.TryGetAsteroidBelt(AsteroidBeltBodyKey, out var belt)",
|
|
"Entity.Zone.AsteroidExists(AsteroidBeltBodyKey, Asteroid)",
|
|
"Entity.Zone.MineAsteroid(",
|
|
"AsteroidBeltBodyKey,",
|
|
"string asteroidBeltBodyKey"
|
|
}
|
|
};
|
|
var missingBehaviorSymbols = requiredBehaviorSymbols
|
|
.Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal)))
|
|
.SelectMany(pair =>
|
|
{
|
|
var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : "";
|
|
return pair.Value
|
|
.Where(symbol => !text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}");
|
|
})
|
|
.ToArray();
|
|
if (missingBehaviorSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ResourceScanner and MiningTool must own typed body-key runtime references rather than raw GUID fields: " +
|
|
string.Join("; ", missingBehaviorSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedOrbitTaskKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "States", "MoveTo.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "PatrolOrbits.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "StationTowing.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"public Guid Orbit { get; set; }",
|
|
"Guid[] Circuit",
|
|
"public Guid OrbitParent",
|
|
"Select(x => x.Key)"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Orbit-targeting agent tasks and move states must stay on typed OrbitKey surfaces, with Zone owning orbit-key resolution: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs");
|
|
var zoneSource = File.Exists(zoneSourcePath)
|
|
? File.ReadAllText(zoneSourcePath)
|
|
: throw new InvalidOperationException("Cannot verify orbit-key task ownership; Zone source is missing.");
|
|
var requiredZoneSymbols = new[]
|
|
{
|
|
"public bool TryGetOrbit(string orbitKey, out Orbit orbit)",
|
|
"public float2 GetOrbitPosition(string orbitKey)"
|
|
};
|
|
var missingZoneSymbols = requiredZoneSymbols
|
|
.Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingZoneSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Zone must own typed orbit-key parsing and resolution for agent/runtime orbit movement: " +
|
|
string.Join(", ", missingZoneSymbols));
|
|
}
|
|
|
|
var requiredTaskSymbols = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "States", "MoveTo.cs")] = new[]
|
|
{
|
|
"public string OrbitKey { get; set; } = \"\";",
|
|
"_agent.Ship.Zone.GetOrbitPosition(OrbitKey)"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "PatrolOrbits.cs")] = new[]
|
|
{
|
|
"public string[] Circuit = Array.Empty<string>();",
|
|
"public string CurrentTarget",
|
|
"patrolMoveState.OrbitKey = CurrentTarget"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "StationTowing.cs")] = new[]
|
|
{
|
|
"public string OrbitParentKey = \"\";"
|
|
}
|
|
};
|
|
var missingTaskSymbols = requiredTaskSymbols
|
|
.Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal)))
|
|
.SelectMany(pair =>
|
|
{
|
|
var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : "";
|
|
return pair.Value
|
|
.Where(symbol => !text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}");
|
|
})
|
|
.ToArray();
|
|
if (missingTaskSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Orbit-targeting agent tasks must retain typed orbit keys rather than raw GUIDs: " +
|
|
string.Join("; ", missingTaskSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedOrbitalEntityOrbitKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "OrbitalEntity.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "EntityConstructionBlueprintProjector.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "LoadoutGenerator.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneGenerator.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"public Guid OrbitId;",
|
|
"public Guid Orbit;",
|
|
"Orbit = orbital.OrbitId",
|
|
"blueprint.Orbit,",
|
|
"Zone.Orbits[orbital.OrbitId]",
|
|
"GetOrbitPosition(OrbitId)",
|
|
"GetOrbitVelocity(OrbitId)",
|
|
"new OrbitalEntity(ItemManager, null, hull, Guid.Empty"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Orbital runtime entities and construction blueprints must retain typed OrbitKey state, with Zone owning orbit-key resolution: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredSymbols = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "OrbitalEntity.cs")] = new[]
|
|
{
|
|
"public string OrbitKey = \"\";",
|
|
"OrbitKey = orbitKey ?? \"\";",
|
|
"Zone.GetOrbitPosition(OrbitKey)",
|
|
"Zone.GetOrbitVelocity(OrbitKey)"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "EntityConstructionBlueprintProjector.cs")] = new[]
|
|
{
|
|
"OrbitKey = orbital.OrbitKey,",
|
|
"blueprint.OrbitKey",
|
|
"public string OrbitKey = \"\";"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "LoadoutGenerator.cs")] = new[]
|
|
{
|
|
"new OrbitalEntity(ItemManager, null, hull, \"\", ItemManager.GameplaySettings.DefaultEntitySettings)"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneGenerator.cs")] = new[]
|
|
{
|
|
"turret.OrbitKey = turretOrbit.OrbitKey;",
|
|
"station.OrbitKey = lagrangeOrbit.OrbitKey;"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs")] = new[]
|
|
{
|
|
"Zone.TryGetOrbit(orbital.OrbitKey, out var orbit)",
|
|
"planet => planet.OrbitKey == parentOrbitKey"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs")] = new[]
|
|
{
|
|
"public float2 GetOrbitVelocity(string orbitKey)"
|
|
}
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal)))
|
|
.SelectMany(pair =>
|
|
{
|
|
var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : "";
|
|
return pair.Value
|
|
.Where(symbol => !text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Orbital runtime seam must stay on typed orbit keys through the entity owner, blueprint projector, and docking path: " +
|
|
string.Join("; ", missingSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedOrbitConsumerKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "ResourceScanner.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"planet => planet.OrbitId == followOrbit",
|
|
"planet => planet.OrbitId == rootOrbit",
|
|
"body => body.OrbitId == orbit.ID",
|
|
"body => body.Orbit == orbit.ID",
|
|
"GetOrbitPosition(planetInstance.OrbitId)",
|
|
"Zone.Orbits[planetInstance.OrbitId].Parent",
|
|
"zone.Orbits[belt.Orbit]",
|
|
"GetOrbitPosition(planet.OrbitId)"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Orbit consumer edges must read typed OrbitKey surfaces rather than wrapper GUID fields in gameplay, renderer, and sensor code: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredSymbols = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs")] = new[]
|
|
{
|
|
"Zone.Orbits.Values.MinBy(orbit => lengthsq(Zone.GetOrbitPosition(orbit.OrbitKey) - entityPosition))",
|
|
"planet => planet.OrbitKey == followOrbit.OrbitKey",
|
|
"planet => planet.OrbitKey == rootOrbit.OrbitKey",
|
|
"Zone.TryGetOrbit(rootOrbit.ParentOrbitKey, out var parentOrbit)",
|
|
"Zone.GetOrbitPosition(followOrbit.OrbitKey)",
|
|
"Zone.GetOrbitPosition(rootOrbit.OrbitKey)"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs")] = new[]
|
|
{
|
|
"body => body.OrbitKey == orbit.OrbitKey",
|
|
"Zone.GetOrbitPosition(planetInstance.OrbitKey)",
|
|
"Zone.GetOrbitPosition(planetInstance.Orbit.ParentOrbitKey)"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "ResourceScanner.cs")] = new[]
|
|
{
|
|
"Entity.Zone.GetOrbitPosition(planet.OrbitKey)"
|
|
}
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal)))
|
|
.SelectMany(pair =>
|
|
{
|
|
var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : "";
|
|
return pair.Value
|
|
.Where(symbol => !text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Orbit consumer edges must stay keyed at the public runtime wrapper surface: " +
|
|
string.Join("; ", missingSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireKeyedOrbitRuntimeWrappers(string root)
|
|
{
|
|
var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs");
|
|
var zoneSource = File.Exists(zoneSourcePath)
|
|
? File.ReadAllText(zoneSourcePath)
|
|
: throw new InvalidOperationException("Cannot verify keyed orbit runtime wrappers; Zone source is missing.");
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"public Guid OrbitId { get; }",
|
|
"public Guid Orbit { get; }",
|
|
"OrbitId = data.Orbit;",
|
|
"Orbit = data.Orbit;",
|
|
"AsteroidBelts[belt.ID] = new AsteroidBelt(belt);",
|
|
"var orbit = Orbits[belt.Orbit];"
|
|
};
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => zoneSource.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: contains {symbol}")
|
|
.ToArray();
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Planet and asteroid-belt runtime wrappers must not publish wrapper orbit GUID fields once keyed/object orbit surfaces exist: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"public Orbit Orbit;",
|
|
"public Orbit Orbit { get; }",
|
|
"var runtimeBelt = new AsteroidBelt(belt, Orbits[belt.OrbitKey]);",
|
|
"AsteroidBelts[runtimeBelt.BodyKey] = runtimeBelt;",
|
|
"belt.NewOrbitPosition = GetOrbitPosition(belt.Orbit.ParentOrbitKey);",
|
|
"public AsteroidBelt(AsteroidBeltConstructionData data, Orbit orbit)",
|
|
"Orbit = orbit;"
|
|
};
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Planet and asteroid-belt runtime wrappers must keep orbit authority on Orbit objects plus OrbitKey surfaces: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedFactionShellLinks(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Corporations.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Galaxy.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"Guid GeonameFile",
|
|
"Guid BossHull",
|
|
"GeonameFile =",
|
|
"BossHull =",
|
|
"ParseOptionalLegacyId",
|
|
"GeonameFileLegacyId",
|
|
"BossHullLegacyId",
|
|
".BossHull != Guid.Empty",
|
|
"public Guid ID",
|
|
"ID = ParseLegacyId",
|
|
"ParseLegacyId(corporation.LegacyId"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Temporary Faction shell links must use typed key fields, not geoname/boss-hull legacy GUID projections: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireNativeZoneKeyResolution(string root)
|
|
{
|
|
var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs");
|
|
var zoneSource = File.Exists(zoneSourcePath)
|
|
? File.ReadAllText(zoneSourcePath)
|
|
: throw new InvalidOperationException("Cannot verify native Zone key resolution; Zone source is missing.");
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"public static Guid ParseOrbitGuid(string orbitKey)",
|
|
"public static Guid ParseBodyGuid(string bodyKey)",
|
|
"PlanetInstances.TryGetValue(ParseBodyGuid(bodyKey), out planet)",
|
|
"AsteroidBelts.TryGetValue(ParseBodyGuid(bodyKey), out belt)",
|
|
"Orbits.TryGetValue(ParseOrbitGuid(orbitKey), out orbit)",
|
|
"return GetOrbitPosition(ParseOrbitGuid(orbitKey));",
|
|
"return GetOrbitVelocity(ParseOrbitGuid(orbitKey));",
|
|
"public float2 GetOrbitPosition(Guid orbitID)",
|
|
"public float2 GetOrbitVelocity(Guid orbit)"
|
|
};
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => zoneSource.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: contains {symbol}")
|
|
.ToArray();
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Zone key-facing APIs must resolve typed body/orbit keys natively instead of reparsing GUIDs from those keys: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"public Dictionary<string, Planet> PlanetInstances = new Dictionary<string, Planet>(StringComparer.Ordinal);",
|
|
"public Dictionary<string, Orbit> Orbits = new Dictionary<string, Orbit>(StringComparer.Ordinal);",
|
|
"public Dictionary<string, AsteroidBelt> AsteroidBelts = new Dictionary<string, AsteroidBelt>(StringComparer.Ordinal);",
|
|
"private readonly Dictionary<Guid, Planet> _planetsById = new Dictionary<Guid, Planet>();",
|
|
"private readonly Dictionary<Guid, AsteroidBelt> _asteroidBeltsById = new Dictionary<Guid, AsteroidBelt>();",
|
|
"PlanetInstances[runtimeSun.BodyKey] = runtimeSun;",
|
|
"_planetsById[runtimeSun.ID] = runtimeSun;",
|
|
"PlanetInstances[runtimeGas.BodyKey] = runtimeGas;",
|
|
"_planetsById[runtimeGas.ID] = runtimeGas;",
|
|
"PlanetInstances[runtimePlanet.BodyKey] = runtimePlanet;",
|
|
"_planetsById[runtimePlanet.ID] = runtimePlanet;",
|
|
"AsteroidBelts[runtimeBelt.BodyKey] = runtimeBelt;",
|
|
"_asteroidBeltsById[runtimeBelt.ID] = runtimeBelt;",
|
|
"Orbits[runtimeOrbit.OrbitKey] = runtimeOrbit;",
|
|
"return PlanetInstances.TryGetValue(bodyKey ?? \"\", out planet);",
|
|
"return AsteroidBelts.TryGetValue(bodyKey ?? \"\", out belt);",
|
|
"return Orbits.TryGetValue(orbitKey ?? \"\", out orbit);",
|
|
"if (_planetsById.TryGetValue(bodyId, out var planet))",
|
|
"if (_asteroidBeltsById.TryGetValue(bodyId, out var belt))",
|
|
"var parentPosition = string.IsNullOrWhiteSpace(orbit.ParentOrbitKey)",
|
|
": GetOrbitPosition(orbit.ParentOrbitKey);",
|
|
"return TryGetOrbit(orbitKey, out var orbit) ? orbit.Velocity : float2.zero;",
|
|
"var runtimeBelt = new AsteroidBelt(belt, Orbits[belt.OrbitKey]);",
|
|
"var runtimeSun = new Sun(settings, sun, Orbits[planet.OrbitKey]);",
|
|
"var runtimeGas = new GasGiant(settings, gas, Orbits[planet.OrbitKey]);",
|
|
"var runtimePlanet = new Planet(settings, planet, Orbits[planet.OrbitKey]);",
|
|
"BodyKey = data.BodyKey ?? \"\";",
|
|
"OrbitKey = data.OrbitKey ?? \"\";",
|
|
"ParentOrbitKey = data.ParentOrbitKey ?? \"\";"
|
|
};
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Zone must keep native key-indexed runtime lookup tables for body and orbit resolution: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedZoneRuntimeCollections(string root)
|
|
{
|
|
var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs");
|
|
var zoneRendererPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs");
|
|
var zoneSource = File.Exists(zoneSourcePath)
|
|
? File.ReadAllText(zoneSourcePath)
|
|
: throw new InvalidOperationException("Cannot verify typed Zone runtime collections; Zone source is missing.");
|
|
var zoneRenderer = File.Exists(zoneRendererPath)
|
|
? File.ReadAllText(zoneRendererPath)
|
|
: throw new InvalidOperationException("Cannot verify typed Zone runtime collections; ZoneRenderer source is missing.");
|
|
|
|
var forbiddenZoneSymbols = new[]
|
|
{
|
|
"public Dictionary<Guid, Planet> PlanetInstances",
|
|
"public Dictionary<Guid, Orbit> Orbits",
|
|
"public Dictionary<Guid, AsteroidBelt> AsteroidBelts",
|
|
"private HashSet<Guid> _updatedOrbits",
|
|
"orbit.Value.Position = GetOrbitPosition(orbit.Key);"
|
|
};
|
|
var zoneHits = forbiddenZoneSymbols
|
|
.Where(symbol => zoneSource.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: contains {symbol}");
|
|
|
|
var forbiddenRendererSymbols = new[]
|
|
{
|
|
"public Dictionary<Guid, PlanetObject> Planets",
|
|
"private Dictionary<Guid, AsteroidBeltUI> _beltObjects",
|
|
"private Dictionary<Guid, InstancedMesh[]> _beltMeshes",
|
|
"private Dictionary<Guid, Matrix4x4[][]> _beltMatrices",
|
|
"_beltMeshes[runtimeBelt.ID] =",
|
|
"_beltObjects[runtimeBelt.ID] =",
|
|
"Planets[planetInstance.ID] = planet;"
|
|
};
|
|
var rendererHits = forbiddenRendererSymbols
|
|
.Where(symbol => zoneRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"Assets/Scripts/Zone Display/ZoneRenderer.cs: contains {symbol}");
|
|
|
|
var hits = zoneHits.Concat(rendererHits).ToArray();
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Zone runtime collections and renderer caches must keep typed OrbitKey/BodyKey ownership rather than Guid dictionary keys: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredZoneSymbols = new[]
|
|
{
|
|
"public Dictionary<string, Planet> PlanetInstances = new Dictionary<string, Planet>(StringComparer.Ordinal);",
|
|
"public Dictionary<string, Orbit> Orbits = new Dictionary<string, Orbit>(StringComparer.Ordinal);",
|
|
"public Dictionary<string, AsteroidBelt> AsteroidBelts = new Dictionary<string, AsteroidBelt>(StringComparer.Ordinal);",
|
|
"private HashSet<string> _updatedOrbits = new HashSet<string>(StringComparer.Ordinal);",
|
|
"foreach (var orbit in Orbits.Values)",
|
|
"orbit.Position = GetOrbitPosition(orbit.OrbitKey);"
|
|
};
|
|
var missingZoneSymbols = requiredZoneSymbols
|
|
.Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: missing {symbol}");
|
|
|
|
var requiredRendererSymbols = new[]
|
|
{
|
|
"public Dictionary<string, PlanetObject> Planets = new Dictionary<string, PlanetObject>(StringComparer.Ordinal);",
|
|
"private Dictionary<string, AsteroidBeltUI> _beltObjects = new Dictionary<string, AsteroidBeltUI>(StringComparer.Ordinal);",
|
|
"private Dictionary<string, InstancedMesh[]> _beltMeshes = new Dictionary<string, InstancedMesh[]>(StringComparer.Ordinal);",
|
|
"private Dictionary<string, Matrix4x4[][]> _beltMatrices = new Dictionary<string, Matrix4x4[][]>(StringComparer.Ordinal);",
|
|
"_beltMeshes[runtimeBelt.BodyKey] = meshes.ToArray();",
|
|
"_beltObjects[runtimeBelt.BodyKey] = belt;",
|
|
"Planets[planetInstance.BodyKey] = planet;"
|
|
};
|
|
var missingRendererSymbols = requiredRendererSymbols
|
|
.Where(symbol => !zoneRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"Assets/Scripts/Zone Display/ZoneRenderer.cs: missing {symbol}");
|
|
|
|
var missing = missingZoneSymbols.Concat(missingRendererSymbols).ToArray();
|
|
if (missing.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Zone runtime collections and renderer caches must expose typed keyed ownership end to end: " +
|
|
string.Join("; ", missing));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedZoneConstructionKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneConstructionData.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneGenerator.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"var runtimeBelt = new AsteroidBelt(belt, Orbits[OrbitKey(belt.Orbit)]);",
|
|
"var runtimeSun = new Sun(settings, sun, Orbits[OrbitKey(planet.Orbit)]);",
|
|
"var runtimeGas = new GasGiant(settings, gas, Orbits[OrbitKey(planet.Orbit)]);",
|
|
"var runtimePlanet = new Planet(settings, planet, Orbits[OrbitKey(planet.Orbit)]);",
|
|
"turret.OrbitKey = Zone.OrbitKey(turretOrbit.ID);",
|
|
"station.OrbitKey = Zone.OrbitKey(lagrangeOrbit.ID);"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Zone construction and generation must hand orbit/body identity through typed key fields instead of recomputing runtime authority from legacy GUID refs: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredSymbols = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneConstructionData.cs")] = new[]
|
|
{
|
|
"public string BodyKey = \"\";",
|
|
"public string OrbitKey = \"\";",
|
|
"public string ParentOrbitKey = \"\";"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneGenerator.cs")] = new[]
|
|
{
|
|
"data.OrbitKey = Zone.OrbitKey(data.ID);",
|
|
"data.ParentOrbitKey = orbitInverseMap[data].Parent != null",
|
|
"planetData.BodyKey = Zone.BodyKey(planetData.ID);",
|
|
"planetData.OrbitKey = orbitMap[planet].OrbitKey;",
|
|
"lagrangeOrbit.OrbitKey = Zone.OrbitKey(lagrangeOrbit.ID);",
|
|
"ParentOrbitKey = baseOrbit.ParentOrbitKey,",
|
|
"turretOrbit.OrbitKey = Zone.OrbitKey(turretOrbit.ID);",
|
|
"ParentOrbitKey = orbit.ParentOrbitKey,",
|
|
"turret.OrbitKey = turretOrbit.OrbitKey;",
|
|
"station.OrbitKey = lagrangeOrbit.OrbitKey;"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs")] = new[]
|
|
{
|
|
"var runtimeBelt = new AsteroidBelt(belt, Orbits[belt.OrbitKey]);",
|
|
"var runtimeSun = new Sun(settings, sun, Orbits[planet.OrbitKey]);",
|
|
"var runtimeGas = new GasGiant(settings, gas, Orbits[planet.OrbitKey]);",
|
|
"var runtimePlanet = new Planet(settings, planet, Orbits[planet.OrbitKey]);",
|
|
"BodyKey = data.BodyKey ?? \"\";",
|
|
"OrbitKey = data.OrbitKey ?? \"\";",
|
|
"ParentOrbitKey = data.ParentOrbitKey ?? \"\";"
|
|
}
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal)))
|
|
.SelectMany(pair =>
|
|
{
|
|
var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : "";
|
|
return pair.Value
|
|
.Where(symbol => !text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Zone construction inputs and generator seams must retain typed orbit/body keys as the handoff authority: " +
|
|
string.Join("; ", missingSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedZoneStateSnapshotKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs"),
|
|
Path.Combine(root, "Aetheria.State", "AetheriaRuntimeCommitLogApplier.cs"),
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogSnapshot.cs"),
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"public string OrbitId { get; set; } = \"\";",
|
|
"public string ParentId { get; set; } = \"\";",
|
|
"public string BodyId { get; set; } = \"\";",
|
|
"public AetheriaRuntimeOrbitSnapshot(string orbitId, string parentId",
|
|
"string bodyId,",
|
|
"string orbitId,",
|
|
"OrbitId = orbit.OrbitKey ?? \"\",",
|
|
"ParentId = orbit.ParentOrbitKey ?? \"\",",
|
|
"BodyId = body.BodyKey ?? \"\",",
|
|
"var orbitId = ReadFieldString(ref reader, orbitFields, 0);",
|
|
"var parentId = ReadFieldString(ref reader, orbitFields, 1);",
|
|
"var bodyId = ReadFieldString(ref reader, bodyFields, 0);",
|
|
"var orbitId = ReadFieldString(ref reader, bodyFields, 3);"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed zone-state documents and runtime snapshots must name orbit/body identity as keys, not legacy ids: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredSymbols = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs")] = new[]
|
|
{
|
|
"public string OrbitKey { get; set; } = \"\";",
|
|
"public string ParentOrbitKey { get; set; } = \"\";",
|
|
"public string BodyKey { get; set; } = \"\";"
|
|
},
|
|
[Path.Combine(root, "Aetheria.State", "AetheriaRuntimeCommitLogApplier.cs")] = new[]
|
|
{
|
|
"OrbitKey = orbit.OrbitKey ?? \"\",",
|
|
"ParentOrbitKey = orbit.ParentOrbitKey ?? \"\",",
|
|
"BodyKey = body.BodyKey ?? \"\",",
|
|
"OrbitKey = body.OrbitKey ?? \"\","
|
|
},
|
|
[Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogSnapshot.cs")] = new[]
|
|
{
|
|
"public AetheriaRuntimeOrbitSnapshot(string orbitKey, string parentOrbitKey",
|
|
"OrbitKey = orbitKey;",
|
|
"ParentOrbitKey = parentOrbitKey;",
|
|
"public string OrbitKey { get; }",
|
|
"public string ParentOrbitKey { get; }",
|
|
"BodyKey = bodyKey;",
|
|
"OrbitKey = orbitKey;",
|
|
"public string BodyKey { get; }",
|
|
"public string OrbitKey { get; }"
|
|
},
|
|
[Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs")] = new[]
|
|
{
|
|
"var orbitKey = ReadFieldString(ref reader, orbitFields, 0);",
|
|
"var parentOrbitKey = ReadFieldString(ref reader, orbitFields, 1);",
|
|
"var bodyKey = ReadFieldString(ref reader, bodyFields, 0);",
|
|
"var orbitKey = ReadFieldString(ref reader, bodyFields, 3);",
|
|
"new AetheriaRuntimeOrbitSnapshot(",
|
|
"new AetheriaRuntimeBodySnapshot("
|
|
}
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal)))
|
|
.SelectMany(pair =>
|
|
{
|
|
var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : "";
|
|
return pair.Value
|
|
.Where(symbol => !text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed zone-state documents and runtime catalog readers must expose orbit/body key ownership explicitly: " +
|
|
string.Join("; ", missingSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedAsteroidZoneApi(string root)
|
|
{
|
|
var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs");
|
|
var zoneSource = File.Exists(zoneSourcePath)
|
|
? File.ReadAllText(zoneSourcePath)
|
|
: throw new InvalidOperationException("Cannot verify typed asteroid Zone API; Zone source is missing.");
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"public int NearestAsteroid(Guid planetDataID, float2 position)",
|
|
"public bool AsteroidExists(Guid planetDataID, int asteroid)",
|
|
"public void MineAsteroid(Entity miner, Guid asteroidBelt, int asteroid, float damage, float efficiency, float penetration)",
|
|
"BeltUpdates.Add(Task.Run(() => UpdateAsteroidTransforms(belt.Key)));",
|
|
"private void UpdateAsteroidTransforms(Guid planetDataID)",
|
|
"MineAsteroid(miner, belt.ID, asteroid, damage, efficiency, penetration);"
|
|
};
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => zoneSource.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: contains {symbol}")
|
|
.ToArray();
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Zone asteroid helpers must keep typed body-key or runtime-object authority rather than Guid overloads: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"foreach (var belt in AsteroidBelts.Values)",
|
|
"BeltUpdates.Add(Task.Run(() => UpdateAsteroidTransforms(belt)));",
|
|
"public int NearestAsteroid(string asteroidBeltKey, float2 position)",
|
|
"if (!TryGetAsteroidBelt(asteroidBeltKey, out var belt))",
|
|
"public bool AsteroidExists(string asteroidBeltKey, int asteroid)",
|
|
"private void UpdateAsteroidTransforms(AsteroidBelt belt)",
|
|
"public void MineAsteroid(Entity miner, string asteroidBeltKey, int asteroid, float damage, float efficiency, float penetration)"
|
|
};
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Zone asteroid helpers must route through typed body keys or runtime belt objects: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireFactionKeyIdentity(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Corporations.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Entity.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Galaxy.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Narrative", "ZoneConstraints.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorMap.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"ID.GetHashCode",
|
|
"faction.ID == ID",
|
|
"Faction.ID ==",
|
|
"faction.ID ==",
|
|
".ID == TargetFaction",
|
|
".ID == GalaxyZone.Owner",
|
|
".ID != zone.Owner",
|
|
"pair.Key.ID",
|
|
"f.ID != zone.Owner",
|
|
"Owner.ID ==",
|
|
"zone.Owner == adjacentZone.Owner",
|
|
"zone.Owner != mega"
|
|
};
|
|
|
|
var hits = checkedFiles
|
|
.Where(File.Exists)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Faction identity must compare and order by FactionKey; Faction.ID is temporary projection residue only: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireEveRuntimeBootstrap(string root)
|
|
{
|
|
var bootstrapPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.eve-runtime",
|
|
"Runtime",
|
|
"AetheriaEveRuntimeBootstrap.cs");
|
|
var presenterPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.eve-runtime",
|
|
"Runtime",
|
|
"AetheriaEveSurfacePresenter.cs");
|
|
|
|
if (!File.Exists(bootstrapPath))
|
|
{
|
|
throw new InvalidOperationException("Aetheria Eve runtime bootstrap is missing.");
|
|
}
|
|
|
|
if (!File.Exists(presenterPath))
|
|
{
|
|
throw new InvalidOperationException("Aetheria Eve surface presenter is missing.");
|
|
}
|
|
|
|
var bootstrap = File.ReadAllText(bootstrapPath);
|
|
if (!bootstrap.Contains("RuntimeInitializeOnLoadMethod", StringComparison.Ordinal) ||
|
|
!bootstrap.Contains("DefaultSurfaceId = \"aetheria.operations\"", StringComparison.Ordinal) ||
|
|
!bootstrap.Contains("AetheriaEveSurfacePresenter", StringComparison.Ordinal) ||
|
|
!bootstrap.Contains("UIDocument", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve runtime bootstrap no longer mounts the operations surface through the UI Toolkit presenter.");
|
|
}
|
|
|
|
var presenter = File.ReadAllText(presenterPath);
|
|
if (!presenter.Contains("AetheriaRuntimeEveCommandLog.QueueCommand", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve presenter no longer queues renderer commands through the typed Eve command log.");
|
|
}
|
|
}
|
|
|
|
static void RequireNoRendererLocalConsole(string root)
|
|
{
|
|
var consoleFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "ConsoleController.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "ConsoleView.cs")
|
|
};
|
|
|
|
var existingConsoleFiles = consoleFiles
|
|
.Where(File.Exists)
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
|
|
if (existingConsoleFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Renderer-local console source files still exist: " +
|
|
string.Join(", ", existingConsoleFiles));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"ConsoleController",
|
|
"ConsoleView",
|
|
"AddCommand("
|
|
};
|
|
|
|
var hits = Directory.EnumerateFiles(Path.Combine(root, "Assets"), "*.*", SearchOption.AllDirectories)
|
|
.Where(path =>
|
|
path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
|
|
path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase) ||
|
|
path.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase))
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.Take(10)
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Renderer-local console authority is still wired in Assets: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireNoRendererLocalDebugPanels(string root)
|
|
{
|
|
var forbiddenFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "FieldTester.cs")
|
|
};
|
|
|
|
var existingFiles = forbiddenFiles
|
|
.Where(File.Exists)
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
|
|
if (existingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Renderer-local debug panel source files still exist: " +
|
|
string.Join(", ", existingFiles));
|
|
}
|
|
|
|
var hits = Directory.EnumerateFiles(Path.Combine(root, "Assets"), "*.*", SearchOption.AllDirectories)
|
|
.Where(path =>
|
|
path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
|
|
path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase) ||
|
|
path.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase))
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => line.Line.Contains("FieldTester", StringComparison.Ordinal))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.Take(10)
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Renderer-local FieldTester authority is still wired in Assets: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireMainMenuSettingsCommit(string root)
|
|
{
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
if (!File.Exists(mainMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify main-menu settings commit path; MainMenu.cs is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(mainMenuPath);
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify main-menu settings authority; ActionGameManager.cs is missing.");
|
|
|
|
var requiredCommits = new[]
|
|
{
|
|
"CommitRuntimePlayerName",
|
|
"CommitRuntimeTemperatureUnit",
|
|
"CommitRuntimeSignificantDigits",
|
|
"CommitRuntimeNebulaQuality",
|
|
"CommitRuntimeShowAsteroidsInMinimap"
|
|
};
|
|
|
|
var missingCommits = requiredCommits
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingCommits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager no longer owns complete runtime player-settings commit primitives: " +
|
|
string.Join(", ", missingCommits));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"ActionGameManager.RuntimePlayerSettings.Name =",
|
|
"ActionGameManager.RuntimePlayerSettings.GameplaySettings.TemperatureUnit =",
|
|
"ActionGameManager.RuntimePlayerSettings.GameplaySettings.SignificantDigits =",
|
|
"ActionGameManager.RuntimePlayerSettings.GraphicsSettings.NebulaQuality =",
|
|
"ActionGameManager.RuntimePlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap ="
|
|
};
|
|
|
|
var hits = File.ReadLines(mainMenuPath)
|
|
.Select((line, index) => new { LineNumber = index + 1, Line = line })
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, mainMenuPath)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still owns direct RuntimePlayerSettings mutation: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var missingUiCalls = requiredCommits
|
|
.Where(symbol => !source.Contains("ActionGameManager." + symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingUiCalls.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu no longer routes settings changes through ActionGameManager: " +
|
|
string.Join(", ", missingUiCalls));
|
|
}
|
|
}
|
|
|
|
static void RequirePlayerSettingsEveSurface(string root)
|
|
{
|
|
var projectorPath = Path.Combine(root, "Aetheria.State", "AetheriaPlayerSettingsSurfaceProjector.cs");
|
|
var bridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs");
|
|
var providerPath = Path.Combine(root, "Aetheria.State", "AetheriaProviderAdvertisementProjector.cs");
|
|
var serverPath = Path.Combine(root, "Economy.Server", "Program.cs");
|
|
|
|
if (!File.Exists(projectorPath))
|
|
{
|
|
throw new InvalidOperationException("Player settings Eve surface projector is missing.");
|
|
}
|
|
|
|
var projector = File.ReadAllText(projectorPath);
|
|
var requiredProjectorSymbols = new[]
|
|
{
|
|
"SurfaceId = \"aetheria.player_settings\"",
|
|
"aetheria.player_settings.gameplay.temperature_unit.cycle",
|
|
"aetheria.player_settings.gameplay.significant_digits.increment",
|
|
"aetheria.player_settings.graphics.nebula_quality.cycle",
|
|
"aetheria.player_settings.graphics.show_asteroids.toggle"
|
|
};
|
|
|
|
var missingProjectorSymbols = requiredProjectorSymbols
|
|
.Where(symbol => !projector.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingProjectorSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Player settings Eve surface projector is missing required typed controls: " +
|
|
string.Join(", ", missingProjectorSymbols));
|
|
}
|
|
|
|
var bridge = File.Exists(bridgePath)
|
|
? File.ReadAllText(bridgePath)
|
|
: throw new InvalidOperationException("Player settings Eve command bridge is missing.");
|
|
var provider = File.Exists(providerPath)
|
|
? File.ReadAllText(providerPath)
|
|
: throw new InvalidOperationException("Player settings provider advertisement projector is missing.");
|
|
var server = File.Exists(serverPath)
|
|
? File.ReadAllText(serverPath)
|
|
: throw new InvalidOperationException("Economy.Server program is missing.");
|
|
|
|
if (!bridge.Contains("AppliedPlayerSettingsCommands", StringComparison.Ordinal) ||
|
|
!bridge.Contains("ApplyPlayerSettingsCommandAsync", StringComparison.Ordinal) ||
|
|
!bridge.Contains("PutPlayerSettingsSurfaceAsync", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve command bridge no longer owns the typed player-settings surface mutation path.");
|
|
}
|
|
|
|
if (!provider.Contains("AetheriaPlayerSettingsSurfaceProjector.SurfaceId", StringComparison.Ordinal) ||
|
|
!provider.Contains("aetheria.player_settings.refresh", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Provider advertisement no longer publishes the player-settings Eve surface and commands.");
|
|
}
|
|
|
|
if (!server.Contains("PublishPlayerSettingsSurfaceAsync", StringComparison.Ordinal) ||
|
|
!server.Contains("PutPlayerSettingsSurfaceAsync", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Economy.Server no longer republishes the provider-owned player-settings Eve surface.");
|
|
}
|
|
}
|
|
|
|
static void RequireMainMenuContinueRunState(string root)
|
|
{
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
var mainMenu = File.Exists(mainMenuPath)
|
|
? File.ReadAllText(mainMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify main-menu Continue path; MainMenu.cs is missing.");
|
|
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue run authority; ActionGameManager.cs is missing.");
|
|
var packageSnapshotPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeCatalogSnapshot.cs");
|
|
var packageSnapshot = File.Exists(packageSnapshotPath)
|
|
? File.ReadAllText(packageSnapshotPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue entity identity; package runtime snapshots are missing.");
|
|
var packageStorePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeCatalogStore.cs");
|
|
var packageStore = File.Exists(packageStorePath)
|
|
? File.ReadAllText(packageStorePath)
|
|
: throw new InvalidOperationException("Cannot verify Continue entity readback; package runtime store is missing.");
|
|
|
|
var requiredMenuSymbols = new[]
|
|
{
|
|
"LatestContinueRun",
|
|
"AetheriaRuntimeCatalogStore",
|
|
"ReadRunStates(ActionGameManager.RuntimeStateFilePath)",
|
|
"ContinueGame(continueRun)",
|
|
"ActionGameManager.ContinueRunState = run",
|
|
"SceneManager.LoadScene(\"ARPG\")"
|
|
};
|
|
|
|
var missingMenuSymbols = requiredMenuSymbols
|
|
.Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingMenuSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu Continue no longer selects typed run state: " +
|
|
string.Join(", ", missingMenuSymbols));
|
|
}
|
|
|
|
if (mainMenu.Contains("AddButton(\"Continue\", null)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("MainMenu Continue regressed to a null button.");
|
|
}
|
|
|
|
var requiredGameplaySymbols = new[]
|
|
{
|
|
"AetheriaRuntimeRunStateSnapshot ContinueRunState",
|
|
"ResolveStartZone(continuingRun)",
|
|
"if (continuingRun != null)",
|
|
"RestoreEntityGraphFromTypedRun(continuingRun)",
|
|
"ReadEntitySnapshots(RuntimeStateFilePath)",
|
|
"entity.RecordKey",
|
|
"run.CurrentEntityKey",
|
|
"ResolveCurrentEntityRecordKey(run)",
|
|
"ReplaceZoneEntitiesFromTypedSnapshots",
|
|
"Zone.Agents.Clear()",
|
|
"FlattenEntityGraph(Zone)",
|
|
"RestoreChildAndDockingRelationships",
|
|
"RestoreCurrentEntityBinding",
|
|
"RestoreEntityContactsFromTypedSnapshot",
|
|
"entity.Target.Value = target",
|
|
"string.Equals(entitySnapshot.RecordKey, currentEntityKey",
|
|
"BindToEntity(currentEntity)",
|
|
"RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot)",
|
|
"RestoreRuntimeBehaviorStateFromTypedSnapshot(entity, entitySnapshot, restoredEntities)",
|
|
"ResolveRuntimeBehavior(entity, weaponState.OwnerKind, weaponState.OwnerIndex, weaponState.BehaviorIndex)",
|
|
"lockWeapon.RestoreRuntimeState(",
|
|
"drive.RestoreRuntimeState(",
|
|
"resourceScanner.RestoreRuntimeState(",
|
|
"entity.RestoreStatGrids(entitySnapshot.StatGrids)",
|
|
"RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia)",
|
|
"entity.HeatsinksEnabled = entitySnapshot.HeatsinksEnabled",
|
|
"ContinueRunState = null",
|
|
"RestoreDroppedPickupsFromTypedZoneState"
|
|
};
|
|
|
|
var missingGameplaySymbols = requiredGameplaySymbols
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingGameplaySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager no longer has the typed Continue boot path: " +
|
|
string.Join(", ", missingGameplaySymbols));
|
|
}
|
|
|
|
var forbiddenGameplaySymbols = new[]
|
|
{
|
|
"if (run == null ||\r\n run.CurrentZoneIndex < 0 ||\r\n run.CurrentZoneEntityIndex < 0)",
|
|
"var currentEntityKey = $\"{zoneEntityKeyPrefix}{run.CurrentZoneEntityIndex}.v1\""
|
|
};
|
|
|
|
var forbiddenGameplayHits = forbiddenGameplaySymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (forbiddenGameplayHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still reconstructs Continue current-entity ownership from integer slot state: " +
|
|
string.Join(", ", forbiddenGameplayHits));
|
|
}
|
|
|
|
var canonicalRunStatePath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs");
|
|
var canonicalRunState = File.Exists(canonicalRunStatePath)
|
|
? File.ReadAllText(canonicalRunStatePath)
|
|
: throw new InvalidOperationException("Cannot verify canonical run-state authority; AetheriaRuntimeStateDocuments.cs is missing.");
|
|
|
|
if (canonicalRunState.Contains("CurrentZoneEntityIndex", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Canonical typed run state still exposes integer current-entity slot authority.");
|
|
}
|
|
|
|
if (packageSnapshot.Contains("CurrentZoneEntityIndex", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity package runtime run snapshot still exposes integer current-entity slot authority.");
|
|
}
|
|
|
|
var pendingCommitPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateCommitDocument.cs");
|
|
var pendingCommit = File.Exists(pendingCommitPath)
|
|
? File.ReadAllText(pendingCommitPath)
|
|
: throw new InvalidOperationException("Cannot verify pending commit authority; AetheriaRuntimeStateCommitDocument.cs is missing.");
|
|
if (pendingCommit.Contains("CurrentZoneEntityIndex", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Pending runtime commit transport still exposes integer current-entity slot authority.");
|
|
}
|
|
|
|
var requiredPackageSymbols = new[]
|
|
{
|
|
"public string RecordKey",
|
|
"ReadEntitySnapshotPayload(record.Key, record.Payload)"
|
|
};
|
|
|
|
var missingPackageSymbols = requiredPackageSymbols
|
|
.Where(symbol =>
|
|
!packageSnapshot.Contains(symbol, StringComparison.Ordinal) &&
|
|
!packageStore.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingPackageSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity package entity readback no longer preserves record identity for Continue: " +
|
|
string.Join(", ", missingPackageSymbols));
|
|
}
|
|
|
|
var entitySourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Entity.cs");
|
|
var entitySource = File.Exists(entitySourcePath)
|
|
? File.ReadAllText(entitySourcePath)
|
|
: throw new InvalidOperationException("Cannot verify Continue entity restore ownership; Entity.cs is missing.");
|
|
var requiredEntityRestoreSymbols = new[]
|
|
{
|
|
"RestoreThermalExposure",
|
|
"RestoreActiveConsumable",
|
|
"RestoreStatGrids",
|
|
"RestoreHullConductivityGrid",
|
|
"new ConsumableItemEffect(item, this, remainingDuration, duration)"
|
|
};
|
|
var missingEntityRestoreSymbols = requiredEntityRestoreSymbols
|
|
.Where(symbol => !entitySource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingEntityRestoreSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Entity no longer exposes narrow runtime-owned restore primitives for typed Continue state: " +
|
|
string.Join(", ", missingEntityRestoreSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequirePropertiesPanelReadOnlyInspector(string root)
|
|
{
|
|
var propertiesPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesPanel.cs");
|
|
if (!File.Exists(propertiesPanelPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify PropertiesPanel inspector authority; PropertiesPanel.cs is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(propertiesPanelPath);
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"readWrite",
|
|
"field.SetValue",
|
|
"f => field.SetValue",
|
|
"i => field.SetValue",
|
|
"b => field.SetValue"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"PropertiesPanel reflection inspection still has renderer-local write authority: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireRuntimeSimulationTuningCommits(string root)
|
|
{
|
|
var requiredActionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(requiredActionGameManagerPath)
|
|
? File.ReadAllText(requiredActionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify runtime simulation tuning authority; ActionGameManager.cs is missing.");
|
|
|
|
var requiredCommitMethods = new[]
|
|
{
|
|
"CommitEntityOverrideShutdown",
|
|
"CommitEquippedItemOverrideShutdown",
|
|
"CommitThermotoggleTargetTemperature",
|
|
"CommitEntityShutdownPerformance"
|
|
};
|
|
|
|
var missingMethods = requiredCommitMethods
|
|
.Where(method => !actionGameManager.Contains(method, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingMethods.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime simulation tuning commit methods are missing from ActionGameManager: " +
|
|
string.Join(", ", missingMethods));
|
|
}
|
|
|
|
var forbiddenUiWrites = new[]
|
|
{
|
|
"EquippableItem.OverrideShutdown =",
|
|
"thermotoggle.TargetTemperature =",
|
|
"Settings.ShutdownPerformance =",
|
|
"CurrentEntity.OverrideShutdown ="
|
|
};
|
|
|
|
var uiRoot = Path.Combine(root, "Assets", "Scripts", "UI");
|
|
var hits = Directory.EnumerateFiles(uiRoot, "*.cs", SearchOption.AllDirectories)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenUiWrites.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.Take(10)
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime simulation tuning still has renderer-local UI write authority: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireHullConductivityCommitAuthority(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify hull conductivity authority; ActionGameManager.cs is missing.");
|
|
|
|
if (!actionGameManager.Contains("CommitHullConductivityToggle", StringComparison.Ordinal) ||
|
|
!actionGameManager.Contains("QueueRunCheckpoint(\"hull-conductivity-toggle\")", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Hull conductivity no longer has a gameplay-owned checkpoint commit primitive.");
|
|
}
|
|
|
|
var uiRoot = Path.Combine(root, "Assets", "Scripts", "UI");
|
|
var hits = Directory.EnumerateFiles(uiRoot, "*.cs", SearchOption.AllDirectories)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line =>
|
|
line.Line.Contains("HullConductivity[", StringComparison.Ordinal) &&
|
|
line.Line.Contains("=", StringComparison.Ordinal) &&
|
|
!line.Line.Contains("=>", StringComparison.Ordinal))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.Take(10)
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Hull conductivity still has renderer-local UI write authority: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryEntityRenameCommitAuthority(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify inventory entity rename authority; ActionGameManager.cs is missing.");
|
|
|
|
if (!actionGameManager.Contains("CommitEntityName", StringComparison.Ordinal) ||
|
|
!actionGameManager.Contains("QueueRunCheckpoint(\"entity-name\")", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Entity rename no longer has a gameplay-owned checkpoint commit primitive.");
|
|
}
|
|
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var inventoryPanel = File.Exists(inventoryPanelPath)
|
|
? File.ReadAllText(inventoryPanelPath)
|
|
: throw new InvalidOperationException("Cannot verify inventory entity rename authority; InventoryPanel.cs is missing.");
|
|
|
|
if (inventoryPanel.Contains("_displayedEntity.Name =", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still renames entities directly instead of using the gameplay checkpoint commit primitive.");
|
|
}
|
|
|
|
if (!inventoryPanel.Contains("GameManager.CommitEntityName", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("InventoryPanel no longer routes entity rename through ActionGameManager.");
|
|
}
|
|
}
|
|
|
|
static void RequireWeaponGroupCommitAuthority(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify weapon group authority; ActionGameManager.cs is missing.");
|
|
|
|
if (!actionGameManager.Contains("CommitWeaponGroupMembership", StringComparison.Ordinal) ||
|
|
!actionGameManager.Contains("QueueRunCheckpoint(\"weapon-group-membership\")", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Weapon group membership no longer has a gameplay-owned checkpoint commit primitive.");
|
|
}
|
|
|
|
var assignmentPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "WeaponGroupAssignment.cs");
|
|
var assignment = File.Exists(assignmentPath)
|
|
? File.ReadAllText(assignmentPath)
|
|
: throw new InvalidOperationException("Cannot verify weapon group authority; WeaponGroupAssignment.cs is missing.");
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
".WeaponGroups[i1].items.Add",
|
|
".WeaponGroups[i1].items.Remove",
|
|
".WeaponGroups[i1].weapons.Add",
|
|
".WeaponGroups[i1].weapons.Remove"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => assignment.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"WeaponGroupAssignment still mutates weapon groups directly: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
if (!assignment.Contains("CommitWeaponGroupMembership", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("WeaponGroupAssignment no longer routes membership changes through ActionGameManager.");
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryDoubleClickTransferCommitAuthority(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify inventory transfer authority; ActionGameManager.cs is missing.");
|
|
|
|
var requiredCommits = new[]
|
|
{
|
|
"CommitCargoItemTransfer",
|
|
"CommitCargoItemEquip",
|
|
"CommitEquippedItemStore",
|
|
"CommitEquippedItemEquip",
|
|
"QueueRunCheckpoint(\"cargo-item-transfer\")",
|
|
"QueueRunCheckpoint(\"cargo-item-equip\")",
|
|
"QueueRunCheckpoint(\"equipped-item-store\")",
|
|
"QueueRunCheckpoint(\"equipped-item-equip\")"
|
|
};
|
|
|
|
var missingCommits = requiredCommits
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingCommits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory transfer no longer has complete gameplay-owned checkpoint commit primitives: " +
|
|
string.Join(", ", missingCommits));
|
|
}
|
|
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var inventoryPanel = File.Exists(inventoryPanelPath)
|
|
? File.ReadAllText(inventoryPanelPath)
|
|
: throw new InvalidOperationException("Cannot verify inventory transfer authority; InventoryPanel.cs is missing.");
|
|
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var inventoryMenu = File.Exists(inventoryMenuPath)
|
|
? File.ReadAllText(inventoryMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify inventory transfer authority; InventoryMenu.cs is missing.");
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
".DropItem(",
|
|
".CargoBay.Remove(",
|
|
".TryUnequip(",
|
|
".TryEquip(",
|
|
".TryStore(",
|
|
".OriginInventory.Remove("
|
|
};
|
|
|
|
var hits = new[] { inventoryMenuPath, inventoryPanelPath }
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory UI still owns direct inventory mutation: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredUiCalls = new[]
|
|
{
|
|
"GameManager.CommitCargoItemTransfer",
|
|
"GameManager.CommitCargoItemEquip",
|
|
"GameManager.CommitEquippedItemStore",
|
|
"GameManager.CommitEquippedItemEquip"
|
|
};
|
|
|
|
var missingUiCalls = requiredUiCalls
|
|
.Where(symbol =>
|
|
!inventoryMenu.Contains(symbol, StringComparison.Ordinal) &&
|
|
!inventoryPanel.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingUiCalls.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory UI no longer routes transfer requests through ActionGameManager: " +
|
|
string.Join(", ", missingUiCalls));
|
|
}
|
|
}
|
|
|
|
static void RequireTradePurchaseCommitAuthority(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify trade purchase authority; ActionGameManager.cs is missing.");
|
|
|
|
var requiredCommits = new[]
|
|
{
|
|
"CommitTradePurchase",
|
|
"QueueRunCheckpoint(\"trade-purchase\")",
|
|
"Credits -=",
|
|
"new Ship("
|
|
};
|
|
|
|
var missingCommits = requiredCommits
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingCommits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Trade purchase no longer has a gameplay-owned checkpoint commit primitive: " +
|
|
string.Join(", ", missingCommits));
|
|
}
|
|
|
|
var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs");
|
|
var tradeMenu = File.Exists(tradeMenuPath)
|
|
? File.ReadAllText(tradeMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify trade purchase authority; TradeMenu.cs is missing.");
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"GameManager.Credits -=",
|
|
"Inventory.TryTransferItem(",
|
|
"new Ship(",
|
|
".SetParent(GameManager.DockedEntity)"
|
|
};
|
|
|
|
var hits = File.ReadLines(tradeMenuPath)
|
|
.Select((line, index) => new { LineNumber = index + 1, Line = line })
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, tradeMenuPath)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu still owns direct purchase mutation: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
if (!tradeMenu.Contains("GameManager.CommitTradePurchase", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("TradeMenu no longer routes purchases through ActionGameManager.");
|
|
}
|
|
}
|
|
|
|
static void RequireLootPickupCommitAuthority(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify loot pickup authority; ActionGameManager.cs is missing.");
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"CommitLootPickup",
|
|
"QueueRunCheckpoint(\"loot-pickup\")",
|
|
".TryStore(item)"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loot pickup no longer has a gameplay-owned checkpoint commit primitive: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var shieldManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ShieldManager.cs");
|
|
var shieldManager = File.Exists(shieldManagerPath)
|
|
? File.ReadAllText(shieldManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify loot pickup authority; ShieldManager.cs is missing.");
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
".TryStore(",
|
|
".CargoBays.Any("
|
|
};
|
|
|
|
var hits = File.ReadLines(shieldManagerPath)
|
|
.Select((line, index) => new { LineNumber = index + 1, Line = line })
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, shieldManagerPath)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ShieldManager still owns direct loot cargo mutation: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
if (!shieldManager.Contains("ActionGameManager.Instance.CommitLootPickup", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("ShieldManager no longer routes loot pickup through ActionGameManager.");
|
|
}
|
|
}
|
|
|
|
static void RequireEntityDestroyedCommitAuthority(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify entity destruction authority; ActionGameManager.cs is missing.");
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"CommitEntityDestroyed",
|
|
"QueueRunCheckpoint(\"entity-destroyed\")",
|
|
"entity.Zone.Entities.Remove(entity)",
|
|
"zoneRenderer.DropItem("
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Entity destruction no longer has a gameplay-owned checkpoint commit primitive: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var entityInstancePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "EntityInstance.cs");
|
|
var entityInstance = File.Exists(entityInstancePath)
|
|
? File.ReadAllText(entityInstancePath)
|
|
: throw new InvalidOperationException("Cannot verify entity destruction authority; EntityInstance.cs is missing.");
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"ZoneRenderer.DropItem(",
|
|
"entity.Zone.Entities.Remove(entity)",
|
|
"Random.onUnitSphere",
|
|
"LootDropProbability"
|
|
};
|
|
|
|
var hits = File.ReadLines(entityInstancePath)
|
|
.Select((line, index) => new { LineNumber = index + 1, Line = line })
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, entityInstancePath)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"EntityInstance still owns direct destruction/drop mutation: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
if (!entityInstance.Contains("ActionGameManager.Instance?.CommitEntityDestroyed", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("EntityInstance no longer routes destruction through ActionGameManager.");
|
|
}
|
|
}
|
|
|
|
static void RequireDroppedPickupCheckpointState(string root)
|
|
{
|
|
var requiredFiles = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateCommitDocument.cs")] = new[]
|
|
{
|
|
"AetheriaRuntimeDroppedPickupCommit",
|
|
"DroppedPickups"
|
|
},
|
|
[Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs")] = new[]
|
|
{
|
|
"AetheriaDroppedPickupSnapshot",
|
|
"DroppedPickups"
|
|
},
|
|
[Path.Combine(root, "Aetheria.State", "AetheriaRuntimeCommitLogApplier.cs")] = new[]
|
|
{
|
|
"ToDroppedPickups",
|
|
"DroppedPickups = ToDroppedPickups(zone.DroppedPickups)"
|
|
},
|
|
[Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs")] = new[]
|
|
{
|
|
"ReadZoneStatePayload(record.Key, record.Payload)",
|
|
"ReadFieldDroppedPickups",
|
|
"AetheriaRuntimeDroppedPickupSnapshot"
|
|
},
|
|
[Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogSnapshot.cs")] = new[]
|
|
{
|
|
"public string RecordKey",
|
|
"AetheriaRuntimeDroppedPickupSnapshot",
|
|
"public IReadOnlyList<AetheriaRuntimeDroppedPickupSnapshot> DroppedPickups"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs")] = new[]
|
|
{
|
|
"ProjectDroppedPickups",
|
|
"DroppedPickups = ProjectDroppedPickups(zone)",
|
|
"RestoreDroppedPickupsFromTypedZoneState",
|
|
"AetheriaRuntimeCatalogStore.ReadZoneStates(RuntimeStateFilePath)",
|
|
"ZoneRenderer.DropItem("
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs")] = new[]
|
|
{
|
|
"ActiveLoot"
|
|
},
|
|
[Path.Combine(root, "Aetheria.State.Unity.Smoke", "Program.cs")] = new[]
|
|
{
|
|
"new AetheriaRuntimeDroppedPickupCommit",
|
|
"packageZones[0].RecordKey",
|
|
"packageZones[0].DroppedPickups"
|
|
}
|
|
};
|
|
|
|
var missing = requiredFiles
|
|
.SelectMany(pair =>
|
|
{
|
|
if (!File.Exists(pair.Key))
|
|
return new[] { $"{Path.GetRelativePath(root, pair.Key)}: missing file" };
|
|
|
|
var text = File.ReadAllText(pair.Key);
|
|
return pair.Value
|
|
.Where(symbol => !text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (missing.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Dropped pickup checkpoint state is incomplete: " +
|
|
string.Join("; ", missing));
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryLoadoutRestoreCommitAuthority(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify loadout restore authority; ActionGameManager.cs is missing.");
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"CommitRuntimeLoadoutRestore",
|
|
"EntityConstructionBlueprintProjector.InstantiateFromBlueprint",
|
|
"QueueRunCheckpoint(\"loadout-restore\")",
|
|
"Credits -="
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loadout restore no longer has a gameplay-owned checkpoint commit primitive: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var inventoryPanel = File.Exists(inventoryPanelPath)
|
|
? File.ReadAllText(inventoryPanelPath)
|
|
: throw new InvalidOperationException("Cannot verify loadout restore authority; InventoryPanel.cs is missing.");
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"EntityConstructionBlueprintProjector.InstantiateFromBlueprint",
|
|
"GameManager.Credits -=",
|
|
".SetParent(GameManager.DockedEntity)"
|
|
};
|
|
|
|
var hits = File.ReadLines(inventoryPanelPath)
|
|
.Select((line, index) => new { LineNumber = index + 1, Line = line })
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, inventoryPanelPath)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still owns direct loadout restore mutation: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
if (!inventoryPanel.Contains("GameManager.CommitRuntimeLoadoutRestore", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("InventoryPanel no longer routes loadout restore through ActionGameManager.");
|
|
}
|
|
}
|
|
|
|
static void RequireDockedCurrentShipCommitAuthority(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify docked current-ship authority; ActionGameManager.cs is missing.");
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"CommitDockedCurrentShip",
|
|
"QueueRunCheckpoint(\"docked-current-ship\")",
|
|
"DockingBay.DockedShip = ship"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Docked current-ship selection no longer has a gameplay-owned checkpoint commit primitive: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"GameManager.CurrentEntity =",
|
|
"GameManager.DockingBay.DockedShip ="
|
|
};
|
|
|
|
var hits = File.ReadLines(inventoryPanelPath)
|
|
.Select((line, index) => new { LineNumber = index + 1, Line = line })
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, inventoryPanelPath)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still owns direct docked current-ship mutation: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var inventoryPanel = File.ReadAllText(inventoryPanelPath);
|
|
if (!inventoryPanel.Contains("GameManager.CommitDockedCurrentShip", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("InventoryPanel no longer routes current-ship selection through ActionGameManager.");
|
|
}
|
|
}
|
|
|
|
static bool ContainsBehaviorValueKind(AetheriaBehaviorValue value, string kind)
|
|
{
|
|
return string.Equals(value.Kind, kind, StringComparison.OrdinalIgnoreCase) ||
|
|
value.Children.Any(child => ContainsBehaviorValueKind(child, kind)) ||
|
|
value.MapEntries.Any(entry => ContainsBehaviorValueKind(entry.Value, kind));
|
|
}
|
|
|
|
static int CountRequiredBehaviorItemRefsMissingItemKeys(AetheriaBehaviorPayload payload)
|
|
{
|
|
return payload.Fields.Count(field =>
|
|
IsBehaviorItemRefField(payload.Kind, field.Key) &&
|
|
IsNonEmptyLegacyRefMissingItemKey(field.Value));
|
|
}
|
|
|
|
static bool IsBehaviorItemRefField(string behaviorKind, int fieldKey)
|
|
{
|
|
return (string.Equals(behaviorKind, "ItemUsage", StringComparison.OrdinalIgnoreCase) && fieldKey == 1) ||
|
|
(IsWeaponBehavior(behaviorKind) && fieldKey == 12);
|
|
}
|
|
|
|
static bool IsWeaponBehavior(string behaviorKind)
|
|
{
|
|
return behaviorKind is "GuidedWeapon" or "InstantWeapon" or "ConstantWeapon" or "ChargedWeapon" or "AutoWeapon" or "LockWeapon";
|
|
}
|
|
|
|
static bool IsNonEmptyLegacyRefMissingItemKey(AetheriaBehaviorValue value)
|
|
{
|
|
return string.Equals(value.Kind, "legacy-id", StringComparison.OrdinalIgnoreCase) &&
|
|
!IsEmptyLegacyId(value.LegacyIdValue) &&
|
|
string.IsNullOrWhiteSpace(value.ItemKeyValue);
|
|
}
|
|
|
|
static bool IsEmptyLegacyId(string legacyId)
|
|
{
|
|
return string.IsNullOrWhiteSpace(legacyId) ||
|
|
(Guid.TryParse(legacyId, out var parsed) && parsed == Guid.Empty);
|
|
}
|
|
|
|
static async Task RequireLegacyLookupAsync<T>(
|
|
string legacyId,
|
|
Func<Task<T?>> lookup,
|
|
string label) where T : class
|
|
{
|
|
if (string.IsNullOrWhiteSpace(legacyId))
|
|
{
|
|
throw new InvalidOperationException($"Cannot verify {label}: legacy id is empty.");
|
|
}
|
|
|
|
var value = await lookup().ConfigureAwait(false);
|
|
if (value == null)
|
|
{
|
|
throw new InvalidOperationException($"Typed legacy-id lookup failed for {label} {legacyId}.");
|
|
}
|
|
}
|