Aetheria/Aetheria.State.Verify/Program.cs
2026-06-16 19:55:50 +01:00

3140 lines
132 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);
RequireTypedBehaviorMetadataCoverage(root);
RequireNoDeadRuntimeProjectionCaches(root);
RequireNoDeadInspectorMetadata(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: player name, 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: dead generic reflection inspector path is deleted");
Console.WriteLine("Typed behavior metadata authority: live heat/mining/thermotoggle payload kinds stay owned by package metadata");
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"),
Path.Combine(root, "Assets", "Scripts", "ServerShared", "ItemManager.cs"),
Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs")
};
var forbiddenSymbols = new[]
{
"IRuntimeItemCatalogReader",
"AetheriaRuntimeItemCatalog",
"_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 lookup authority must stay on the typed snapshot; legacy-ID indexes and redundant catalog adapters 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 RequireNoDeadRuntimeProjectionCaches(string root)
{
var path = Path.Combine(root, "Assets", "Scripts", "ServerShared", "RuntimeProjection", "ReflectionExtensions.cs");
if (!File.Exists(path))
return;
var forbiddenSymbols = new[]
{
"GetAllInterfaceClasses(",
"GetParentTypes(",
"GetAllChildClasses(",
"GetAllGenericChildClasses(",
"IsAssignableToGenericType(",
"GetFullName(",
"InterfaceClasses",
"ParentTypes",
"ChildClasses",
"GenericChildClasses",
"AppDomain.CurrentDomain.GetAssemblies()"
};
var hits = File.ReadLines(path)
.Select((line, index) => new { Line = line, LineNumber = index + 1 })
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
.Select(line => $"Assets/Scripts/ServerShared/RuntimeProjection/ReflectionExtensions.cs:{line.LineNumber}: {line.Line.Trim()}")
.ToArray();
if (hits.Length > 0)
{
throw new InvalidOperationException(
"Dead runtime-projection reflection caches should stay deleted; keep only the live string-formatting helpers: " +
string.Join("; ", hits));
}
}
static void RequireNoDeadInspectorMetadata(string root)
{
var checkedFiles = new[]
{
Path.Combine(root, "Assets", "Scripts", "Gameplay", "FieldDriver.cs"),
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Corporations.cs"),
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "AetherDrive.cs"),
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "StatModifier.cs"),
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "VelocityLimit.cs"),
Path.Combine(root, "Assets", "Scripts", "ServerShared", "ExponentialCurve.cs"),
Path.Combine(root, "Assets", "Scripts", "ServerShared", "RuntimeGeometry.cs"),
Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesPanel.cs")
};
var forbiddenSymbols = new[]
{
"InspectableAttribute",
"PreferredInspectorAttribute",
"InspectableTextAttribute",
"InspectablePrefabAttribute",
"InspectableTextureAttribute",
"InspectableTextAssetAttribute",
"InspectableTemperatureAttribute",
"InspectableAnimationCurveAttribute",
"InspectableColorAttribute",
"InspectableSoundBankAttribute",
"InspectableAudioParameterAttribute",
"InspectableSchematicShapeAttribute",
"InspectableEnumValuesAttribute",
"InspectableRangedFloatAttribute",
"InspectableRangedIntAttribute",
"OrderAttribute",
"EntityTypeRestrictionAttribute",
"InspectorHeaderAttribute",
"[Inspectable",
"[InspectableText",
"[InspectablePrefab",
"[InspectableTexture",
"[InspectableTextAsset",
"[InspectableTemperature",
"[InspectableAnimationCurve",
"[InspectableColor",
"[InspectableSoundBank",
"[InspectableAudioParameter",
"[InspectableSchematicShape",
"[InspectableEnumValues",
"[InspectableRangedFloat",
"[InspectableRangedInt",
"[Order(",
"EntityTypeRestriction(",
"[InspectorHeader("
};
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(
"Dead inspector-specialization metadata should stay deleted; no generic Inspectable attribute path should remain in live runtime code: " +
string.Join("; ", hits));
}
var deletedFiles = new[]
{
Path.Combine(root, "Assets", "Scripts", "ServerShared", "RuntimeProjection", "Attributes.cs"),
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Attributes.cs")
};
var stillPresent = deletedFiles.Where(File.Exists).Select(path => Path.GetRelativePath(root, path)).ToArray();
if (stillPresent.Length > 0)
{
throw new InvalidOperationException(
"Dead inspector attribute definition files should be deleted once the generic reflection inspector path is gone: " +
string.Join(", ", stillPresent));
}
}
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");
var sharedCommandsPath = Path.Combine(
root,
"Packages",
"org.gamecult.aetheria.state",
"Runtime",
"AetheriaRuntimePlayerSettingsCommands.cs");
var surfaceBuilderPath = Path.Combine(
root,
"Packages",
"org.gamecult.aetheria.state",
"Runtime",
"AetheriaRuntimePlayerSettingsSurfaceBuilder.cs");
if (!File.Exists(mainMenuPath))
{
throw new InvalidOperationException("Cannot verify main-menu settings commit path; MainMenu.cs is missing.");
}
if (!File.Exists(sharedCommandsPath))
{
throw new InvalidOperationException(
"Cannot verify main-menu settings command contract; AetheriaRuntimePlayerSettingsCommands.cs is missing.");
}
if (!File.Exists(surfaceBuilderPath))
{
throw new InvalidOperationException(
"Cannot verify main-menu settings surface contract; AetheriaRuntimePlayerSettingsSurfaceBuilder.cs is missing.");
}
var source = File.ReadAllText(mainMenuPath);
var sharedCommands = File.ReadAllText(sharedCommandsPath);
var surfaceBuilder = File.ReadAllText(surfaceBuilderPath);
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",
"CommitRuntimePlayerSettingsCommand"
};
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 requiredMainMenuAuthoritySymbols = new[]
{
"ActionGameManager.CommitRuntimePlayerSettingsCommand"
};
var missingUiCalls = requiredMainMenuAuthoritySymbols
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (missingUiCalls.Length > 0)
{
throw new InvalidOperationException(
"MainMenu no longer routes settings changes through ActionGameManager: " +
string.Join(", ", missingUiCalls));
}
var requiredSharedCommands = new[]
{
"SetPlayerName",
"CycleTemperatureUnit",
"DecrementSignificantDigits",
"IncrementSignificantDigits",
"CycleNebulaQuality",
"ToggleShowAsteroidsInMinimap"
};
var missingSharedCommands = requiredSharedCommands
.Where(symbol => !sharedCommands.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (missingSharedCommands.Length > 0)
{
throw new InvalidOperationException(
"Shared player-settings Eve command contract is incomplete: " +
string.Join(", ", missingSharedCommands));
}
var requiredSurfaceBuilderSymbols = new[]
{
"AetheriaRuntimePlayerSettingsSurfaceBuilder",
"AetheriaRuntimePlayerSettingsCommands.SetPlayerName",
"\"control.text\"",
"AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit",
"AetheriaRuntimePlayerSettingsCommands.DecrementSignificantDigits",
"AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits",
"AetheriaRuntimePlayerSettingsCommands.CycleNebulaQuality",
"AetheriaRuntimePlayerSettingsCommands.ToggleShowAsteroidsInMinimap"
};
var missingSurfaceBuilderSymbols = requiredSurfaceBuilderSymbols
.Where(symbol => !surfaceBuilder.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (missingSurfaceBuilderSymbols.Length > 0)
{
throw new InvalidOperationException(
"Shared player-settings Eve surface builder is incomplete: " +
string.Join(", ", missingSurfaceBuilderSymbols));
}
var requiredMainMenuSymbols = new[]
{
"AetheriaRuntimePlayerSettingsSurfaceBuilder.Build",
"new EveUiToolkitSurfaceLowerer",
"ActionGameManager.CommitRuntimePlayerSettingsCommand(request.Command, request.Payload)"
};
var missingMainMenuSymbols = requiredMainMenuSymbols
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (missingMainMenuSymbols.Length > 0)
{
throw new InvalidOperationException(
"MainMenu no longer lowers the shared player-settings Eve surface contract: " +
string.Join(", ", missingMainMenuSymbols));
}
var forbiddenMainMenuSymbols = new[]
{
"new TextField(\"Name\")",
"ActionGameManager.CommitRuntimePlayerName(evt.newValue)"
};
var mainMenuHits = forbiddenMainMenuSymbols
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (mainMenuHits.Length > 0)
{
throw new InvalidOperationException(
"MainMenu should not keep renderer-local player-name input authority outside the shared Eve surface: " +
string.Join(", ", mainMenuHits));
}
var forbiddenUiFieldSymbols = new[]
{
"AddField(\"Temperature Unit\"",
"AddField(\"Significant Digits\"",
"AddField(\"Nebula Quality\"",
"AddField(\"Show Asteroids in Minimap\"",
"ShowGameplaySettings()",
"ShowGraphicsSettings()"
};
var uiFieldHits = File.ReadLines(mainMenuPath)
.Select((line, index) => new { LineNumber = index + 1, Line = line })
.Where(line => forbiddenUiFieldSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
.Select(line => $"{Path.GetRelativePath(root, mainMenuPath)}:{line.LineNumber}: {line.Line.Trim()}")
.ToArray();
if (uiFieldHits.Length > 0)
{
throw new InvalidOperationException(
"MainMenu still uses legacy field widgets for Eve-owned gameplay/graphics settings: " +
string.Join("; ", uiFieldHits));
}
}
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");
var sharedCommandsPath = Path.Combine(
root,
"Packages",
"org.gamecult.aetheria.state",
"Runtime",
"AetheriaRuntimePlayerSettingsCommands.cs");
var surfaceBuilderPath = Path.Combine(
root,
"Packages",
"org.gamecult.aetheria.state",
"Runtime",
"AetheriaRuntimePlayerSettingsSurfaceBuilder.cs");
if (!File.Exists(projectorPath))
{
throw new InvalidOperationException("Player settings Eve surface projector is missing.");
}
var projector = File.ReadAllText(projectorPath);
var requiredProjectorSymbols = new[]
{
"AetheriaRuntimePlayerSettingsCommands.SurfaceId",
"AetheriaRuntimePlayerSettingsSurfaceBuilder.Build",
"AetheriaRuntimePlayerSettingsSurfaceState",
"settings.ActiveRunKey"
};
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.");
var sharedCommands = File.Exists(sharedCommandsPath)
? File.ReadAllText(sharedCommandsPath)
: throw new InvalidOperationException("Shared player-settings Eve command contract is missing.");
var surfaceBuilder = File.Exists(surfaceBuilderPath)
? File.ReadAllText(surfaceBuilderPath)
: throw new InvalidOperationException("Shared player-settings Eve surface builder is missing.");
if (!bridge.Contains("AppliedPlayerSettingsCommands", StringComparison.Ordinal) ||
!bridge.Contains("ApplyPlayerSettingsCommandAsync", StringComparison.Ordinal) ||
!bridge.Contains("PutPlayerSettingsSurfaceAsync", StringComparison.Ordinal) ||
!bridge.Contains("SetPlayerName", StringComparison.Ordinal) ||
!bridge.Contains("command.Payload.TryGetValue(\"value\"", 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("AetheriaRuntimePlayerSettingsCommands.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.");
}
if (!sharedCommands.Contains("SurfaceId = \"aetheria.player_settings\"", StringComparison.Ordinal) ||
!sharedCommands.Contains("public static bool IsKnown", StringComparison.Ordinal) ||
!sharedCommands.Contains("SetPlayerName", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Shared player-settings Eve command contract is missing the surface id or command registry helper.");
}
if (!surfaceBuilder.Contains("public static class AetheriaRuntimePlayerSettingsSurfaceBuilder", StringComparison.Ordinal) ||
!surfaceBuilder.Contains("\"control.text\"", StringComparison.Ordinal) ||
!surfaceBuilder.Contains("SetPlayerName", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Shared player-settings Eve surface builder no longer owns the portable settings surface contract.");
}
}
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[]
{
"Inspect(object obj",
"Inspect(object obj, FieldInfo field",
"GetCustomAttribute<InspectableAttribute>",
"type.GetCustomAttribute<InspectableAttribute>()",
"field.GetValue(obj)",
"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 should not keep the dead generic reflection inspector path or renderer-local write authority: " +
string.Join(", ", hits));
}
}
static void RequireTypedBehaviorMetadataCoverage(string root)
{
var metadataPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeBehaviorMetadata.cs");
var propertiesPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesPanel.cs");
var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs");
var metadata = File.Exists(metadataPath)
? File.ReadAllText(metadataPath)
: throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; AetheriaRuntimeBehaviorMetadata.cs is missing.");
var propertiesPanel = File.Exists(propertiesPanelPath)
? File.ReadAllText(propertiesPanelPath)
: throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; PropertiesPanel.cs is missing.");
var tradeMenu = File.Exists(tradeMenuPath)
? File.ReadAllText(tradeMenuPath)
: throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; TradeMenu.cs is missing.");
var requiredMetadataSymbols = new[]
{
"AetheriaRuntimeBehaviorFieldValueKind.Temperature",
"Behavior(\"Heat\", \"\", Stat(\"Heat\", 1))",
"Behavior(\"MiningTool\", \"\", Stat(\"DamagePerSecond\", 1), Stat(\"Efficiency\", 2), Stat(\"Penetration\", 3), Stat(\"Range\", 4))",
"Behavior(\"Switch\", \"\")",
"Behavior(\"Thermotoggle\", \"\", Temperature(\"TargetTemperature\", 1))",
"Behavior(\"Trigger\", \"\")",
"Temperature(\"TemperatureFloor\", 3)"
};
var missingMetadataSymbols = requiredMetadataSymbols
.Where(symbol => !metadata.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (missingMetadataSymbols.Length > 0)
{
throw new InvalidOperationException(
"Typed behavior metadata no longer covers live heat/mining/thermotoggle payloads and switch/trigger shells: " +
string.Join(", ", missingMetadataSymbols));
}
var requiredUiSymbols = new[]
{
"AetheriaRuntimeBehaviorFieldValueKind.Temperature",
"FormatTemperature"
};
var missingPropertiesSymbols = requiredUiSymbols
.Where(symbol => !propertiesPanel.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (missingPropertiesSymbols.Length > 0)
{
throw new InvalidOperationException(
"PropertiesPanel no longer renders typed temperature-bearing behavior metadata: " +
string.Join(", ", missingPropertiesSymbols));
}
var missingTradeSymbols = requiredUiSymbols
.Where(symbol => !tradeMenu.Contains(symbol, StringComparison.Ordinal))
.ToArray();
if (missingTradeSymbols.Length > 0)
{
throw new InvalidOperationException(
"TradeMenu no longer renders typed temperature-bearing behavior metadata: " +
string.Join(", ", missingTradeSymbols));
}
}
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}.");
}
}