21351 lines
1 MiB
21351 lines
1 MiB
using Aetheria.State;
|
|
using Aetheria.State.Documents;
|
|
using GameCult.Aetheria.State.Verse;
|
|
using GameCult.Eve.Surface;
|
|
using GameCult.Mesh;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
|
|
var root = args.Length > 0 ? Path.GetFullPath(args[0]) : Directory.GetCurrentDirectory();
|
|
var statePath = args.Length > 1
|
|
? Path.GetFullPath(args[1])
|
|
: AetheriaStatePaths.ResolveDefaultStatePath(root);
|
|
var verificationDirectory = Path.Combine(
|
|
Path.GetTempPath(),
|
|
"aetheria-state-verify",
|
|
Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(verificationDirectory);
|
|
var verificationStatePath = Path.Combine(verificationDirectory, "aetheria-world.cc");
|
|
File.Copy(statePath, verificationStatePath, overwrite: true);
|
|
|
|
RequireGameplaySourcePurity(root);
|
|
RequireManagedContentOnlyAssetDelivery(root);
|
|
RequireClientTransportPlaneOwnership(root);
|
|
RequirePackageSerializerBoundary(root);
|
|
RequireSharedEvePackagesImportedFromEveRepo(root);
|
|
RequireSharedRuntimeSurfaceCommandsUseCultMeshTransport(root);
|
|
RequireSharedRuntimeSurfacesUseClientNeutralVocabulary(root);
|
|
RequireTypedPendingCommitKeys(root);
|
|
RequireTypedRuntimeFactionKeys(root);
|
|
RequireTypedGalaxyFactionRelationships(root);
|
|
RequireRuntimeCatalogKeyOnlyLookups(root);
|
|
RequireDaemonPlayableRunGenerationAuthority(root);
|
|
RequireTypedBehaviorBodyKeys(root);
|
|
RequireTypedOrbitTaskKeys(root);
|
|
RequireTypedAgentTaskKeys(root);
|
|
RequireTypedOrbitalEntityOrbitKeys(root);
|
|
RequireTypedOrbitConsumerKeys(root);
|
|
RequireKeyedOrbitRuntimeWrappers(root);
|
|
RequireNativeZoneKeyResolution(root);
|
|
RequireTypedZoneRuntimeCollections(root);
|
|
RequireDaemonRenderQueryAuthority(root);
|
|
RequireTypedZoneConstructionKeys(root);
|
|
RequireTypedZoneStateSnapshotKeys(root);
|
|
RequireTypedAsteroidZoneApi(root);
|
|
RequireTypedFactionShellLinks(root);
|
|
RequireFactionKeyIdentity(root);
|
|
RequireTypedRuntimeBehaviorCoverage(root);
|
|
RequireEveRuntimeBootstrap(root);
|
|
RequireNoRendererLocalConsole(root);
|
|
RequireNoRendererLocalDebugPanels(root);
|
|
RequireMainMenuSettingsCommands(root);
|
|
RequireMainMenuRootUsesEveSurface(root);
|
|
RequireMainMenuSettingsShellUsesEveSurface(root);
|
|
RequireConfirmationDialogOwnsMinimalPromptShell(root);
|
|
RequireMainMenuInputSettingsDelegateToRuntimeScreen(root);
|
|
RequireRuntimeInputScreenUsesEveSurface(root);
|
|
RequireActionGameManagerInputScreenUsesSharedFullscreenPrimitive(root);
|
|
RequireSectorMapZoneDetailsUseEveSurface(root);
|
|
RequireRuntimeMenuTabsUseEveSurface(root);
|
|
RequireInventoryShipSettingsUseEveSurface(root);
|
|
RequireInventoryCargoItemDetailsUseEveSurface(root);
|
|
RequireInventoryEquippedItemDetailsUseEveSurface(root);
|
|
RequireTradeCargoSelectorUseEveSurface(root);
|
|
RequireTradeFilterAndRowActionsUseEveSurface(root);
|
|
RequireTradeItemDetailsUseEveSurface(root);
|
|
RequireTradeItemValuesUseRuntimeQueries(root);
|
|
RequireItemTierValuesUseRuntimeQueries(root);
|
|
RequireInventoryDropdownUseEveSurface(root);
|
|
RequireStationRefitDockingBaysUseTypedDocuments(root);
|
|
RequireNoDeadPopupShells(root);
|
|
RequirePlayerSettingsEveSurface(root);
|
|
RequireVerseHostSettingsAuthority(root);
|
|
RequireClientTargetBootAuthority(root);
|
|
RequireClientRuntimeConformanceContract(root);
|
|
RequireVerseReplicaTool(root);
|
|
RequireVerseSettingsShellAndBridge(root);
|
|
RequireTypedStatRecipeOperations(root);
|
|
RequireTypedDaemonCommandPayloads(root);
|
|
RequireUnityPublicRequestVocabulary(root);
|
|
RequireCatalogSurfaceUsesManagedRuntimeCatalog(root);
|
|
RequireDaemonLoadoutRarityAuthority(root);
|
|
RequireDaemonVersePublication(root);
|
|
RequireUnityRuntimeCatalogClientUsesManagedDocument(root);
|
|
RequireAetheriaRuntimeVerseClientContract(root);
|
|
RequireTypedEveCommandBodies(root);
|
|
RequireMainMenuVerseHostDocumentAccess(root);
|
|
RequireMainMenuContinueRunState(root);
|
|
RequireUnityObserverDoesNotTickLocalSimulation(root);
|
|
RequireUnityDoesNotCallSharedSimulationTicks(root);
|
|
RequireDaemonEnergyAndThermalAuthority(root);
|
|
RequireThermalPresentationAssetAuthority(root);
|
|
RequireCatalogHullPresentationAuthority(root);
|
|
RequireUnityPhysicsIsNotGameplayAuthority(root);
|
|
RequireDeadPropertiesPanelShellDeleted(root);
|
|
RequireTypedBehaviorMetadataCoverage(root);
|
|
RequireNameToolsUsesUiToolkit(root);
|
|
RequireRuntimeStateReaderOwnsUnityStateAcquisition(root);
|
|
RequireNoScopedReactiveFacadeWrappers(root);
|
|
RequireNoDeadRuntimeProjectionCaches(root);
|
|
RequireNoDeadRuntimeProjectionClasses(root);
|
|
RequireNoDeadInspectorMetadata(root);
|
|
RequireDaemonHostDoesNotDrainRuntimeCommits(root);
|
|
RequireRuntimeSimulationTuningRequests(root);
|
|
RequireHullConductivityRequestAuthority(root);
|
|
RequireInventoryEntityRenameRequestAuthority(root);
|
|
RequireWeaponGroupRequestAuthority(root);
|
|
RequireActionBarBindingRequestAuthority(root);
|
|
RequireInventoryDoubleClickTransferRequestAuthority(root);
|
|
RequireLootPickupRequestAuthority(root);
|
|
RequireEntityDestroyedRequestAuthority(root);
|
|
RequireDroppedPickupCheckpointState(root);
|
|
RequireTradePurchaseRequestAuthority(root);
|
|
RequireTradeSaleAuthority(root);
|
|
RequireInventoryDocumentSlotIdentity(root);
|
|
RequireInventoryValidationUsesManagedTypedDocuments(root);
|
|
RequireMenuDockingUsesManagedTypedSnapshot(root);
|
|
RequireUnitySharedDocumentAccessorErgonomics(root);
|
|
RequireUnityViewportAndMapReadsUseManagedAccessors(root);
|
|
RequireAetheriaManagedStateAccessorsCoverDomainDocuments(root);
|
|
RequireInventoryLoadoutSaveRequestAuthority(root);
|
|
RequireInventoryLoadoutRestoreRequestAuthority(root);
|
|
RequireDockedCurrentShipRequestAuthority(root);
|
|
RequireAuthoritySmokeUsesManagedPointers(root);
|
|
RequireAetheriaStateNodeUsesManagedPointers(root);
|
|
|
|
await using var node = await AetheriaStateNode.OpenAsync(
|
|
verificationStatePath,
|
|
"aetheria-state-verify",
|
|
enableDurableShardLogs: false);
|
|
await node.RefreshRuntimeCatalogAsync().ConfigureAwait(false);
|
|
|
|
var ledger = await node.MutableDocument<AetheriaMigrationLedger>(AetheriaStateNode.MigrationLedgerKey).ReadAsync()
|
|
?? throw new InvalidOperationException("Missing typed migration ledger.");
|
|
var quarantine = await node.MutableDocument<AetheriaLegacyCatalogQuarantine>(AetheriaStateNode.LegacyCatalogQuarantineKey).ReadAsync()
|
|
?? throw new InvalidOperationException("Missing legacy catalog quarantine document.");
|
|
var publishedSurface = await node.CatalogSurface().LatestAsync().ConfigureAwait(false);
|
|
if (publishedSurface == null)
|
|
throw new InvalidOperationException("Missing managed 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 = await node.RuntimeCatalog().LatestAsync().ConfigureAwait(false);
|
|
var surface = AetheriaEveSurfaceDocuments.BuildCatalogSurface(catalog, DateTimeOffset.UtcNow.ToString("O"));
|
|
var tradeValuePolicy = await node.MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey).ReadAsync()
|
|
?? throw new InvalidOperationException("Missing authored trade value policy document.");
|
|
await RequireTradeValuePolicyEveCommandPersistsAsync();
|
|
await RequireMainMenuEveCommandPersistsAsync();
|
|
var runtimeCatalog = catalog;
|
|
|
|
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.");
|
|
}
|
|
if (tradeValuePolicy.QualityPriceModifier == null ||
|
|
tradeValuePolicy.QualityPriceModifier.Exponent <= 0 ||
|
|
tradeValuePolicy.QualityPriceModifier.Maximum <= tradeValuePolicy.QualityPriceModifier.Minimum ||
|
|
tradeValuePolicy.Tiers.Length == 0)
|
|
{
|
|
throw new InvalidOperationException("Authored trade value policy is missing quality pricing or rarity tiers.");
|
|
}
|
|
|
|
if (runtimeCatalog.TradeValueSettings.Tiers.Count != tradeValuePolicy.Tiers.Length ||
|
|
Math.Abs(runtimeCatalog.TradeValueSettings.QualityPriceModifier.Exponent - tradeValuePolicy.QualityPriceModifier.Exponent) > 0.0001)
|
|
{
|
|
throw new InvalidOperationException("Runtime catalog did not read the authored trade value policy.");
|
|
}
|
|
|
|
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 = catalog.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 = catalog.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 typedHardpointItem = catalog.Items.FirstOrDefault(item => item.TryGetHardpointType<AetheriaVerifyHardpointType>(out _))
|
|
?? throw new InvalidOperationException("Typed catalog item hardpoint enum accessor failed.");
|
|
var typedSimpleCommodityItem = catalog.Items.FirstOrDefault(item => item.TryGetSimpleCommodityCategory<AetheriaVerifySimpleCommodityCategory>(out _))
|
|
?? throw new InvalidOperationException("Typed catalog item simple commodity enum accessor failed.");
|
|
var typedCompoundCommodityItem = catalog.Items.FirstOrDefault(item => item.TryGetCompoundCommodityCategory<AetheriaVerifyCompoundCommodityCategory>(out _))
|
|
?? throw new InvalidOperationException("Typed catalog item compound commodity enum accessor failed.");
|
|
if (string.IsNullOrWhiteSpace(typedHardpointItem.HardpointType) ||
|
|
string.IsNullOrWhiteSpace(typedSimpleCommodityItem.SimpleCommodityCategory) ||
|
|
string.IsNullOrWhiteSpace(typedCompoundCommodityItem.CompoundCommodityCategory))
|
|
{
|
|
throw new InvalidOperationException("Typed catalog item enum accessors returned an item without its source field.");
|
|
}
|
|
|
|
var manufacturedItem = catalog.Items.FirstOrDefault(item => !string.IsNullOrWhiteSpace(item.ManufacturerKey))
|
|
?? 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 = catalog.Corporations.FirstOrDefault(corporation =>
|
|
!string.IsNullOrWhiteSpace(corporation.GeonameFileKey))
|
|
?? 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.Surface.Root.Kind != "surface" ||
|
|
surface.Surface.Root.Children.Count == 0)
|
|
{
|
|
throw new InvalidOperationException("Aetheria catalog Eve surface projection is not renderable.");
|
|
}
|
|
|
|
if (publishedSurface.Surface.Id != AetheriaEveSurfaceDocuments.CatalogSurfaceId ||
|
|
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.MutableDocument<AetheriaItemDefinition>(AetheriaCatalogKeys.ItemDefinitionFromLegacyId(items[0].LegacyId)).ReadAsync(),
|
|
"item definition");
|
|
await RequireLegacyLookupAsync(
|
|
corporations[0].LegacyId,
|
|
() => node.MutableDocument<AetheriaCorporation>(AetheriaCatalogKeys.CorporationFromLegacyId(corporations[0].LegacyId)).ReadAsync(),
|
|
"corporation");
|
|
await RequireLegacyLookupAsync(
|
|
nameFiles[0].LegacyId,
|
|
() => node.MutableDocument<AetheriaNameFile>(AetheriaCatalogKeys.NameFileFromLegacyId(nameFiles[0].LegacyId)).ReadAsync(),
|
|
"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.Count} 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 execution remains in named CultCache transport files; document DTOs may carry schema annotations");
|
|
Console.WriteLine("Shared Eve package ownership: generic Unity Eve packages import from the neighboring Eve repo instead of local staged copies");
|
|
Console.WriteLine("Typed runtime behavior coverage: daemon behavior-state rows derive from catalog payloads; Unity behavior objects are presentation restore only");
|
|
Console.WriteLine("Zone construction/runtime key authority: body/orbit wrappers and task shells retain typed keys instead of GUID sidecars");
|
|
Console.WriteLine("Eve runtime bootstrap: daemon-published game 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 send typed Eve commands");
|
|
Console.WriteLine("Main-menu root shell: root navigation lowers through an Eve UI Toolkit surface instead of the legacy PropertiesPanel/fade shell");
|
|
Console.WriteLine("Main-menu settings shell: settings/input subpages lower through Eve UI Toolkit surfaces, and the fake audio page is deleted until a typed audio owner exists");
|
|
Console.WriteLine("Confirmation dialog shell: runtime prompts no longer inherit the generic PropertiesPanel machinery");
|
|
Console.WriteLine("Main-menu input shell: the Eve input page delegates to the live runtime remap screen when that owner exists");
|
|
Console.WriteLine("Runtime input screen shell: input rebinding lowers through an Eve UI Toolkit surface instead of the old drag/drop uGUI screen");
|
|
Console.WriteLine("Runtime input-screen authority: hotkey and menu handoff share the same fullscreen-menu primitive");
|
|
Console.WriteLine("Sector-map zone details shell: shared zone detail surface lowers selected zone state through Eve UI Toolkit");
|
|
Console.WriteLine("Runtime menu tab shell: MenuPanel owns its client-local tab surface and lowers it through Eve UI Toolkit");
|
|
Console.WriteLine("Inventory ship-settings shell: shared ship settings surface lowers selected ship state through Eve UI Toolkit");
|
|
Console.WriteLine("Inventory cargo-item shell: shared cargo detail surface lowers selected item state through Eve UI Toolkit");
|
|
Console.WriteLine("Inventory equipped-item shell: shared equipped item surface lowers selected equipment state and controls through Eve UI Toolkit");
|
|
Console.WriteLine("Trade cargo-selector shell: shared cargo selector surface lowers target cargo options through Eve UI Toolkit");
|
|
Console.WriteLine("Trade filter and row-action shells: shared trade interaction surfaces lower filter and row actions through Eve UI Toolkit");
|
|
Console.WriteLine("Trade item-details shell: shared trade item surface lowers selected market item state through Eve UI Toolkit");
|
|
Console.WriteLine("Inventory dropdown shell: shared inventory dropdown surface lowers entity and loadout navigation through Eve UI Toolkit");
|
|
Console.WriteLine("Main-menu Continue authority: Continue selects typed run state instead of a null button");
|
|
Console.WriteLine("Generic popup inspector shell: PropertiesPanel, PropertiesList, and DropdownMenu are deleted from source and serialized assets");
|
|
Console.WriteLine("Typed behavior metadata authority: live heat/mining/thermotoggle payload kinds stay owned by package metadata");
|
|
Console.WriteLine("NameTools editor shell: the remaining name helper window lowers through UI Toolkit instead of IMGUI");
|
|
Console.WriteLine("Verse client state authority: Unity gameplay/UI read typed state through the shared Verse client instead of direct store spelunking");
|
|
Console.WriteLine("Verse host authority: daemon-owned typed verse host settings now drive provider advertisement, operations telemetry, and served Verse discovery");
|
|
Console.WriteLine("Client target boot authority: Unity boot resolves the active Verse through a typed client target instead of local path folklore");
|
|
Console.WriteLine("Client runtime conformance: Hermodr is the generic Eve RTS check; Godot is the generic Eve ARPG check");
|
|
Console.WriteLine("Verse replica authority: remote client targets resolve to cache-only replica .cc files fed from the daemon");
|
|
Console.WriteLine("Verse settings shell: client target edits and Verse-host visibility commands lower through typed Eve surfaces");
|
|
Console.WriteLine("Main-menu Verse document access: the Unity Eve shell composes daemon-owned verse identity from typed documents instead of ad-libbing local menu copy");
|
|
Console.WriteLine("Unity observer authority: gameplay scenes restore daemon frames and do not tick local simulation");
|
|
Console.WriteLine("Shared simulation authority: Zone/Entity/Agent ticks stay out of Unity gameplay callers");
|
|
Console.WriteLine("Ymir spatial kernel: authoritative gameplay physics uses Ymir computation and has no Unity collision callback fallback");
|
|
Console.WriteLine("Runtime simulation tuning authority: UI sends daemon operations instead of local simulation mutation");
|
|
Console.WriteLine("Hull conductivity authority: inventory UI sends daemon operations for toggles instead of checkpoint rewrites");
|
|
Console.WriteLine("Inventory entity rename authority: UI sends daemon operations for renames instead of local graph edits");
|
|
Console.WriteLine("Weapon group authority: UI sends daemon operations for assignments instead of checkpoint rewrites");
|
|
Console.WriteLine("Action-bar binding authority: Unity owns local input bindings; activations send daemon operations");
|
|
Console.WriteLine("Inventory transfer authority: UI transfer and drag/drop send daemon operations");
|
|
Console.WriteLine("Loot pickup authority: daemon cargo collection occurs exactly once from ship/pickup XZ proximity");
|
|
Console.WriteLine("Entity destruction authority: lethal daemon simulation commits exactly-once destruction and loot; clients cannot request erasure");
|
|
Console.WriteLine("Dropped pickup state: daemon frames carry typed dropped-pickup snapshots and keyed live lowering");
|
|
Console.WriteLine("Trade purchase authority: UI requests item and placement; daemon derives price, station, product kind, capacity, and ship creation");
|
|
Console.WriteLine("Inventory loadout restore authority: UI restore requests send daemon operations instead of local mutation");
|
|
Console.WriteLine("Docked current-ship authority: UI selection requests send daemon operations instead of checkpoint rewrites");
|
|
|
|
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 RequireSharedEvePackagesImportedFromEveRepo(string root)
|
|
{
|
|
var manifestPath = Path.Combine(root, "Aetheria.Unity", "Packages", "manifest.json");
|
|
var lockPath = Path.Combine(root, "Aetheria.Unity", "Packages", "packages-lock.json");
|
|
var authoringManifestPath = Path.Combine(root, "Packages", "manifest.json");
|
|
var authoringLockPath = Path.Combine(root, "Packages", "packages-lock.json");
|
|
var unityAsmdefPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"GameCult.Aetheria.State.Unity.asmdef");
|
|
var localSurfacePath = Path.Combine(root, "Packages", "org.gamecult.eve.surface");
|
|
var localUnityUiToolkitPath = Path.Combine(root, "Packages", "org.gamecult.eve.unity-uitoolkit");
|
|
var evePackagesRoot = Path.GetFullPath(Path.Combine(root, "..", "EveUnity", "packages"));
|
|
var upstreamSurfacePath = Path.Combine(evePackagesRoot, "org.gamecult.eve.surface", "package.json");
|
|
var upstreamSurfaceDocumentPath = Path.Combine(
|
|
evePackagesRoot,
|
|
"org.gamecult.eve.surface",
|
|
"Runtime",
|
|
"EveSurfaceDocument.cs");
|
|
var upstreamSurfaceAsmdefPath = Path.Combine(
|
|
evePackagesRoot,
|
|
"org.gamecult.eve.surface",
|
|
"Runtime",
|
|
"GameCult.Eve.Surface.asmdef");
|
|
var upstreamUnityUiToolkitPath = Path.Combine(evePackagesRoot, "org.gamecult.eve.unity-uitoolkit", "package.json");
|
|
var upstreamUnityUiToolkitLowererPath = Path.Combine(
|
|
evePackagesRoot,
|
|
"org.gamecult.eve.unity-uitoolkit",
|
|
"Runtime",
|
|
"EveUiToolkitSurfaceLowerer.cs");
|
|
var runtimeSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimePlayerSettingsSurfaceBuilder.cs");
|
|
var runtimeSurfaceDocumentsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeSurfaceDocuments.cs");
|
|
var deletedRuntimeEveSurfaceAdapterPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeEveSurfaceAdapter.cs");
|
|
var runtimeCultCacheDocumentStorePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeCultCacheDocumentStore.cs");
|
|
var playerSettingsSurfaceDocumentsPath = Path.Combine(
|
|
root,
|
|
"Aetheria.State",
|
|
"AetheriaEveSurfaceDocuments.cs");
|
|
var catalogSurfaceDocumentsPath = Path.Combine(
|
|
root,
|
|
"Aetheria.State",
|
|
"AetheriaEveSurfaceDocuments.cs");
|
|
var operationsSurfaceDocumentsPath = Path.Combine(
|
|
root,
|
|
"Aetheria.State",
|
|
"AetheriaEveSurfaceDocuments.cs");
|
|
|
|
var manifest = File.Exists(manifestPath)
|
|
? File.ReadAllText(manifestPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; manifest.json is missing.");
|
|
var packageLock = File.Exists(lockPath)
|
|
? File.ReadAllText(lockPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; packages-lock.json is missing.");
|
|
var authoringManifest = File.Exists(authoringManifestPath)
|
|
? File.ReadAllText(authoringManifestPath)
|
|
: throw new InvalidOperationException("Cannot verify asset-authoring package ownership; manifest.json is missing.");
|
|
var authoringPackageLock = File.Exists(authoringLockPath)
|
|
? File.ReadAllText(authoringLockPath)
|
|
: throw new InvalidOperationException("Cannot verify asset-authoring package ownership; packages-lock.json is missing.");
|
|
var unityAsmdef = File.Exists(unityAsmdefPath)
|
|
? File.ReadAllText(unityAsmdefPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; GameCult.Aetheria.State.Unity.asmdef is missing.");
|
|
var upstreamSurfaceDocument = File.Exists(upstreamSurfaceDocumentPath)
|
|
? File.ReadAllText(upstreamSurfaceDocumentPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; upstream EveSurfaceDocument.cs is missing.");
|
|
var upstreamSurfaceAsmdef = File.Exists(upstreamSurfaceAsmdefPath)
|
|
? File.ReadAllText(upstreamSurfaceAsmdefPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; upstream GameCult.Eve.Surface.asmdef is missing.");
|
|
var upstreamUnityUiToolkitLowerer = File.Exists(upstreamUnityUiToolkitLowererPath)
|
|
? File.ReadAllText(upstreamUnityUiToolkitLowererPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; upstream EveUiToolkitSurfaceLowerer.cs is missing.");
|
|
var runtimeSurfaceBuilder = File.Exists(runtimeSurfaceBuilderPath)
|
|
? File.ReadAllText(runtimeSurfaceBuilderPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaRuntimePlayerSettingsSurfaceBuilder.cs is missing.");
|
|
var runtimeSurfaceDocuments = File.Exists(runtimeSurfaceDocumentsPath)
|
|
? File.ReadAllText(runtimeSurfaceDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaRuntimeSurfaceDocuments.cs is missing.");
|
|
var runtimeCultCacheDocumentStore = File.Exists(runtimeCultCacheDocumentStorePath)
|
|
? File.ReadAllText(runtimeCultCacheDocumentStorePath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaRuntimeCultCacheDocumentStore.cs is missing.");
|
|
var playerSettingsSurfaceDocuments = File.Exists(playerSettingsSurfaceDocumentsPath)
|
|
? File.ReadAllText(playerSettingsSurfaceDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaEveSurfaceDocuments.cs is missing.");
|
|
var catalogSurfaceDocuments = File.Exists(catalogSurfaceDocumentsPath)
|
|
? File.ReadAllText(catalogSurfaceDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaEveSurfaceDocuments.cs is missing.");
|
|
var operationsSurfaceDocuments = File.Exists(operationsSurfaceDocumentsPath)
|
|
? File.ReadAllText(operationsSurfaceDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaEveSurfaceDocuments.cs is missing.");
|
|
|
|
using var manifestDocument = JsonDocument.Parse(manifest);
|
|
using var lockDocument = JsonDocument.Parse(packageLock);
|
|
using var authoringManifestDocument = JsonDocument.Parse(authoringManifest);
|
|
using var authoringLockDocument = JsonDocument.Parse(authoringPackageLock);
|
|
var manifestDependencies = manifestDocument.RootElement.GetProperty("dependencies");
|
|
var lockedDependencies = lockDocument.RootElement.GetProperty("dependencies");
|
|
var releasedPackageTags = new Dictionary<string, string>(StringComparer.Ordinal)
|
|
{
|
|
["org.gamecult.cultlib"] =
|
|
"https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v",
|
|
["org.gamecult.eve.plugin-fields"] =
|
|
"https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v",
|
|
["org.gamecult.eve.surface"] =
|
|
"https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v",
|
|
["org.gamecult.eve.unity-scene"] =
|
|
"https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v"
|
|
};
|
|
|
|
foreach (var (packageId, taggedUrlPrefix) in releasedPackageTags)
|
|
{
|
|
if (!manifestDependencies.TryGetProperty(packageId, out var manifestVersionElement))
|
|
throw new InvalidOperationException($"Aetheria manifest does not import required released package '{packageId}'.");
|
|
|
|
var manifestVersion = manifestVersionElement.GetString() ?? "";
|
|
if (!manifestVersion.StartsWith(taggedUrlPrefix, StringComparison.Ordinal) ||
|
|
manifestVersion.Length == taggedUrlPrefix.Length)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Aetheria package '{packageId}' must use a released Git tag under '{taggedUrlPrefix}'.");
|
|
}
|
|
|
|
if (!lockedDependencies.TryGetProperty(packageId, out var lockedPackage) ||
|
|
!string.Equals(lockedPackage.GetProperty("source").GetString(), "git", StringComparison.Ordinal) ||
|
|
!string.Equals(lockedPackage.GetProperty("version").GetString(), manifestVersion, StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Unity lockfile does not resolve '{packageId}' from the exact tagged manifest version '{manifestVersion}'.");
|
|
}
|
|
|
|
var resolvedHash = lockedPackage.TryGetProperty("hash", out var hashElement)
|
|
? hashElement.GetString() ?? ""
|
|
: "";
|
|
if (resolvedHash.Length != 40 || resolvedHash.Any(value => !Uri.IsHexDigit(value)))
|
|
throw new InvalidOperationException($"Unity lockfile has no immutable Git commit hash for '{packageId}'.");
|
|
}
|
|
|
|
const string requiredAuthoringCultLib =
|
|
"https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.43";
|
|
const string requiredAuthoringCultLibCommit = "f67f5122ed1bd11da016e7b820ed60145ccd0299";
|
|
var authoringDependencies = authoringManifestDocument.RootElement.GetProperty("dependencies");
|
|
var authoringLockedDependencies = authoringLockDocument.RootElement.GetProperty("dependencies");
|
|
if (!authoringDependencies.TryGetProperty("org.gamecult.cultlib", out var authoringCultLib) ||
|
|
!string.Equals(authoringCultLib.GetString(), requiredAuthoringCultLib, StringComparison.Ordinal) ||
|
|
!authoringLockedDependencies.TryGetProperty("org.gamecult.cultlib", out var authoringCultLibLock) ||
|
|
!string.Equals(authoringCultLibLock.GetProperty("version").GetString(), requiredAuthoringCultLib, StringComparison.Ordinal) ||
|
|
!string.Equals(authoringCultLibLock.GetProperty("hash").GetString(), requiredAuthoringCultLibCommit, StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The fossil asset-authoring Unity project is not pinned to the released CultLib body required by the generic scene package.");
|
|
}
|
|
|
|
if (Directory.Exists(localSurfacePath) || Directory.Exists(localUnityUiToolkitPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria should not keep local staged copies of the shared Eve Unity packages under Packages/.");
|
|
}
|
|
|
|
if (!File.Exists(upstreamSurfacePath) || !File.Exists(upstreamUnityUiToolkitPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The neighboring EveUnity repo is missing the shared Unity package roots Aetheria imports.");
|
|
}
|
|
|
|
if (!unityAsmdef.Contains("\"GameCult.Eve.Surface\"", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria.State.Unity no longer references the shared Eve surface package assembly.");
|
|
}
|
|
|
|
if (!upstreamSurfaceAsmdef.Contains("\"GameCult.Mesh\"", StringComparison.Ordinal) ||
|
|
!upstreamSurfaceDocument.Contains("using GameCult.Mesh;", StringComparison.Ordinal) ||
|
|
!upstreamSurfaceDocument.Contains("public IReadOnlyList<CultMeshStateBindingDescriptor> StateBindings", StringComparison.Ordinal) ||
|
|
!upstreamSurfaceDocument.Contains("public CultMeshOperationBindingDescriptor Operation", StringComparison.Ordinal) ||
|
|
!upstreamSurfaceDocument.Contains("public CultMeshOperationInvocationDescriptor Operation", StringComparison.Ordinal) ||
|
|
!upstreamSurfaceDocument.Contains("public CultMeshOperationPayload Payload", StringComparison.Ordinal) ||
|
|
!upstreamSurfaceDocument.Contains("public string Command => Operation.OperationId", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The shared Eve surface package no longer exposes first-class CultMesh state bindings and operation descriptors.");
|
|
}
|
|
|
|
if (upstreamSurfaceDocument.Contains("EveCommandTemplate(string command", StringComparison.Ordinal) ||
|
|
upstreamSurfaceDocument.Contains("string command,\r\n IReadOnlyDictionary<string, string> payload", StringComparison.Ordinal) ||
|
|
upstreamSurfaceDocument.Contains("CultMeshOperationInvocationDescriptor operation,\r\n IReadOnlyDictionary<string, string> payload", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The shared Eve surface package has reintroduced raw command or dictionary payload request constructors.");
|
|
}
|
|
|
|
if (!upstreamUnityUiToolkitLowerer.Contains("ResolveOperation(document, command)", StringComparison.Ordinal) ||
|
|
!upstreamUnityUiToolkitLowerer.Contains("CultMesh.OperationInvocation(template.Operation)", StringComparison.Ordinal) ||
|
|
!upstreamUnityUiToolkitLowerer.Contains("CultMesh.OperationPayload(component.Props)", StringComparison.Ordinal) ||
|
|
!upstreamUnityUiToolkitLowerer.Contains("LowerEmbeddedDocument(", StringComparison.Ordinal) ||
|
|
!upstreamUnityUiToolkitLowerer.Contains("component.EmbeddedDocuments", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The shared Eve Unity UI Toolkit lowerer no longer emits CultMesh operation invocation descriptors/payloads or nested document slots.");
|
|
}
|
|
|
|
if (runtimeSurfaceDocuments.Contains("public sealed class AetheriaRuntimeSurfaceStateBinding", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("public IReadOnlyList<CultMeshStateBindingDescriptor> StateBindings", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("public IReadOnlyList<AetheriaRuntimeEmbeddedDocumentSlot> EmbeddedDocuments", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("public sealed class AetheriaRuntimeEmbeddedDocumentSlot", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("public static CultMeshStateBindingDescriptor ForDaemonStateRef(", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("public CultMeshOperationBindingDescriptor Operation", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime surfaces should use CultMesh state, operation, and embedded document descriptors directly instead of local live binding DTOs.");
|
|
}
|
|
|
|
if (!runtimeSurfaceDocuments.Contains("component.StateBindings.Select(ToCultMeshStateBinding).ToArray()", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("component.EmbeddedDocuments.Select(ToEveEmbeddedDocumentSlot).ToArray()", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("private static CultMeshStateBindingDescriptor ToCultMeshStateBinding(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime surfaces no longer hand CultMesh state bindings and embedded document slots to the shared Eve component model.");
|
|
}
|
|
|
|
if (!runtimeSurfaceDocuments.Contains("CultMesh.StateBindingRecord(binding)", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("CultMesh.StateBindingRecord(", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains(".ToBinding()", StringComparison.Ordinal) ||
|
|
runtimeSurfaceDocuments.Contains("CultMesh.RouteRecord(binding.RouteHint)", StringComparison.Ordinal) ||
|
|
runtimeSurfaceDocuments.Contains("CultMesh.RouteRecord(binding.RouteKind, binding.RouteDescription)", StringComparison.Ordinal) ||
|
|
runtimeSurfaceBuilder.Contains("ParseRouteKind(", StringComparison.Ordinal) ||
|
|
runtimeSurfaceDocuments.Contains("ParseRouteKind(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime surfaces reintroduced local state binding flattening; state binding persistence belongs to CultMesh.StateBindingRecord.");
|
|
}
|
|
|
|
var deletedRuntimeSurfaceStateProjectorPath = Path.Combine(
|
|
root,
|
|
"Aetheria.State",
|
|
"AetheriaRuntimeEveSurfaceStateProjector.cs");
|
|
if (File.Exists(deletedRuntimeSurfaceStateProjectorPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeEveSurfaceStateProjector is dead conversion chaff; AetheriaRuntimeSurfaceDocuments owns runtime surface to Eve state lowering.");
|
|
}
|
|
|
|
if (File.Exists(deletedRuntimeEveSurfaceAdapterPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeEveSurfaceAdapter is dead conversion chaff; runtime surface lowering belongs on AetheriaRuntimeSurfaceDocuments.");
|
|
}
|
|
|
|
if (!runtimeCultCacheDocumentStore.Contains("CultMesh.OperationBindingRecord(command.Operation)", StringComparison.Ordinal) ||
|
|
!runtimeCultCacheDocumentStore.Contains("CultMesh.OperationBindingRecord(", StringComparison.Ordinal) ||
|
|
!runtimeCultCacheDocumentStore.Contains("routeDescription).ToBinding()", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("public static EveUiSurfaceDocument ToPortableSurface(AetheriaRuntimeSurfaceDocument document)", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("new EveUiCommandTemplate(command.Operation)", StringComparison.Ordinal) ||
|
|
runtimeSurfaceDocuments.Contains("GameCult.Mesh.EveSurface", StringComparison.Ordinal) ||
|
|
runtimeCultCacheDocumentStore.Contains("legacyTransportOrSchema", StringComparison.Ordinal) ||
|
|
runtimeCultCacheDocumentStore.Contains("commandFields > 3 ? legacyTransportOrSchema", StringComparison.Ordinal) ||
|
|
runtimeCultCacheDocumentStore.Contains("new CultMeshOperationBindingDescriptor(", StringComparison.Ordinal) ||
|
|
runtimeSurfaceDocuments.Contains("new CultMeshOperationBindingDescriptor(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime surfaces reintroduced local operation binding flattening; portable surface command persistence belongs to CultMesh Eve documents.");
|
|
}
|
|
}
|
|
|
|
static void RequireSharedRuntimeSurfaceCommandsUseCultMeshTransport(string root)
|
|
{
|
|
var runtimePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime");
|
|
if (!Directory.Exists(runtimePath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime surface command transport cannot be verified because the state runtime package is missing.");
|
|
}
|
|
|
|
var runtimeSources = Directory.EnumerateFiles(runtimePath, "*.cs", SearchOption.TopDirectoryOnly)
|
|
.ToDictionary(
|
|
path => Path.GetRelativePath(root, path),
|
|
File.ReadAllText,
|
|
StringComparer.Ordinal);
|
|
var combinedRuntime = string.Join("\n", runtimeSources.Values);
|
|
|
|
if (!combinedRuntime.Contains("public const string CultMeshTransport = \"cultmesh\"", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime surface command templates do not expose a neutral CultMesh transport constant.");
|
|
}
|
|
|
|
var unityToolkitTransportHits = runtimeSources
|
|
.Where(pair =>
|
|
pair.Value.Contains("\"unity-uitoolkit\"", StringComparison.Ordinal) &&
|
|
pair.Value.Contains("AetheriaRuntimeSurfaceCommandTemplate", StringComparison.Ordinal))
|
|
.Select(pair => pair.Key)
|
|
.ToArray();
|
|
if (unityToolkitTransportHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime surface command templates still advertise Unity UI Toolkit as the transport owner: " +
|
|
string.Join(", ", unityToolkitTransportHits));
|
|
}
|
|
|
|
Console.WriteLine("Shared runtime surface commands: command templates advertise CultMesh transport instead of Unity UI Toolkit");
|
|
}
|
|
|
|
static void RequireSharedRuntimeSurfacesUseClientNeutralVocabulary(string root)
|
|
{
|
|
var runtimePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime");
|
|
if (!Directory.Exists(runtimePath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime surface vocabulary cannot be verified because the state runtime package is missing.");
|
|
}
|
|
|
|
var forbiddenSurfaceVocabulary = new[]
|
|
{
|
|
"Unity supplies",
|
|
"Unity projects",
|
|
"Unity reads",
|
|
"Unity must observe",
|
|
"Unity runtime shells"
|
|
};
|
|
var hits = Directory.EnumerateFiles(runtimePath, "*.cs", SearchOption.TopDirectoryOnly)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSurfaceVocabulary.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(
|
|
"Shared runtime surfaces still describe their contract as Unity-owned instead of client-neutral CultMesh API: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
Console.WriteLine("Shared runtime surfaces: surface copy uses client-neutral observer vocabulary");
|
|
}
|
|
|
|
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 allowedSerializerExecutionFiles = new HashSet<string>(StringComparer.Ordinal)
|
|
{
|
|
"AetheriaRuntimeCatalogStore.cs",
|
|
"AetheriaRuntimeClientTargetStore.cs",
|
|
"AetheriaRuntimeCultCacheDocumentStore.cs",
|
|
"AetheriaRuntimeVerseClient.cs",
|
|
"AetheriaRuntimeSnapshotDocuments.cs"
|
|
};
|
|
|
|
var serializerExecutionSymbols = new[]
|
|
{
|
|
"MessagePackObject",
|
|
"MessagePackSerializer",
|
|
"MessagePackReader",
|
|
"MessagePackWriter",
|
|
"IMessagePackFormatter"
|
|
};
|
|
|
|
var hits = Directory.EnumerateFiles(packageRuntimeRoot, "*.cs", SearchOption.AllDirectories)
|
|
.Where(path => !allowedSerializerExecutionFiles.Contains(Path.GetFileName(path)))
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => serializerExecutionSymbols
|
|
.Where(symbol => symbol != "MessagePackObject")
|
|
.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 execution 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", "AetheriaRuntimeSnapshotDocuments.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", "EntityConstructionBlueprintMaterializer.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 RequireClientTransportPlaneOwnership(string root)
|
|
{
|
|
var daemonPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs");
|
|
var launcherPath = Path.Combine(root, "scripts", "run-aetheria-unity.ps1");
|
|
var editorLauncherPath = Path.Combine(root, "scripts", "start-aetheria-daemon-editor.ps1");
|
|
var editorControllerPath = Path.Combine(
|
|
root, "Aetheria.Unity", "Assets", "Editor", "AetheriaDaemonDevelopmentWindow.cs");
|
|
var daemon = File.Exists(daemonPath)
|
|
? File.ReadAllText(daemonPath)
|
|
: throw new InvalidOperationException("Cannot verify client transport ownership; daemon Program.cs is missing.");
|
|
var launcher = File.Exists(launcherPath)
|
|
? File.ReadAllText(launcherPath)
|
|
: throw new InvalidOperationException("Cannot verify client transport ownership; Unity launcher is missing.");
|
|
var editorLauncher = File.Exists(editorLauncherPath)
|
|
? File.ReadAllText(editorLauncherPath)
|
|
: throw new InvalidOperationException("Cannot verify client transport ownership; Unity editor daemon launcher is missing.");
|
|
var editorController = File.Exists(editorControllerPath)
|
|
? File.ReadAllText(editorControllerPath)
|
|
: throw new InvalidOperationException("Cannot verify client transport ownership; Unity editor daemon controller is missing.");
|
|
var required = new[]
|
|
{
|
|
"new TcpFramedCultNetSchemaServer(",
|
|
"new CultMeshTcpContentServer(",
|
|
"CultMeshTcpContentTransportConnector.Scheme",
|
|
"CultMeshQuicRealtimeServer.ListenAsync(",
|
|
"CultMeshQuicRealtimeTransportConnector.Scheme",
|
|
"new CultNetDatabaseSubscriptionServer(cultMeshClientHost.Control, node.Database)",
|
|
"cultnet+tcp://127.0.0.1:$Port"
|
|
};
|
|
var combined = daemon + "\n" + launcher;
|
|
var missing = required.Where(symbol => !combined.Contains(symbol, StringComparison.Ordinal)).ToArray();
|
|
if (missing.Length > 0)
|
|
throw new InvalidOperationException(
|
|
"Aetheria client transport planes lost their TCP control/content ownership: " + string.Join(", ", missing));
|
|
|
|
var discoveryEndpoints = Regex.Match(
|
|
daemon,
|
|
@"discoveryEndpoints\s*:\s*new\[\]\s*\{(?<items>[^}]*)\}",
|
|
RegexOptions.CultureInvariant | RegexOptions.Singleline);
|
|
if (!discoveryEndpoints.Success)
|
|
throw new InvalidOperationException("Aetheria provider Verse does not advertise an explicit transport endpoint set.");
|
|
var advertised = discoveryEndpoints.Groups["items"].Value
|
|
.Split(',')
|
|
.Select(endpoint => endpoint.Trim())
|
|
.Where(endpoint => endpoint.Length > 0)
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
var requiredAdvertisements = new[]
|
|
{
|
|
"advertisedEndpoint",
|
|
"advertisedContentEndpoint",
|
|
"advertisedRealtimeEndpoint"
|
|
};
|
|
var missingAdvertisements = requiredAdvertisements.Where(endpoint => !advertised.Contains(endpoint)).ToArray();
|
|
if (missingAdvertisements.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria provider Verse does not advertise every owned transport plane: " +
|
|
string.Join(", ", missingAdvertisements));
|
|
}
|
|
|
|
var forbidden = new[]
|
|
{
|
|
"RudpCultNetSchemaServer",
|
|
"new CultMeshContentServer(",
|
|
"new CultMeshBodyServer(",
|
|
"RunClientCultMeshPumpAsync",
|
|
"AETHERIA_TRACE_CLIENT_RUDP",
|
|
"rudp://127.0.0.1:$Port"
|
|
};
|
|
var present = forbidden.Where(symbol => combined.Contains(symbol, StringComparison.Ordinal)).ToArray();
|
|
if (present.Length > 0)
|
|
throw new InvalidOperationException(
|
|
"Aetheria reintroduced RUDP or schema-carried bulk state into the client boundary: " +
|
|
string.Join(", ", present));
|
|
|
|
var restoreIndex = daemon.IndexOf(
|
|
"using var physicsPersistence = await AetheriaYmirPersistenceCoordinator.OpenAsync(",
|
|
StringComparison.Ordinal);
|
|
var clientHostIndex = daemon.IndexOf(
|
|
"await using var cultMeshClientHost = await StartClientCultMeshHostAsync(",
|
|
StringComparison.Ordinal);
|
|
if (restoreIndex < 0 || clientHostIndex < 0 || clientHostIndex < restoreIndex)
|
|
throw new InvalidOperationException(
|
|
"Aetheria client transport must not listen before daemon-private Ymir restoration succeeds.");
|
|
|
|
var requiredEditorResetSymbols = new[]
|
|
{
|
|
"$ymirStatePath = \"$statePath.ymir.cc\"",
|
|
"$ymirRecordsPath = \"$ymirStatePath.records\"",
|
|
"$ymirStatePath, $ymirRecordsPath"
|
|
};
|
|
var missingEditorResetSymbols = requiredEditorResetSymbols
|
|
.Where(symbol => !editorLauncher.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingEditorResetSymbols.Length > 0)
|
|
throw new InvalidOperationException(
|
|
"Aetheria editor reimport does not reset every daemon-private Ymir state path: " +
|
|
string.Join(", ", missingEditorResetSymbols));
|
|
if (editorLauncher.Contains("Start-Process", StringComparison.Ordinal) ||
|
|
!editorController.Contains("Process.Start(new ProcessStartInfo(DaemonExePath, arguments)", StringComparison.Ordinal) ||
|
|
!editorController.Contains("SessionState.SetInt(ProcessIdSessionKey, daemon.Id)", StringComparison.Ordinal))
|
|
throw new InvalidOperationException(
|
|
"Unity must directly own the Debug daemon process after synchronous build/import preparation.");
|
|
|
|
Console.WriteLine("Client transport ownership: Ymir restores before transport listen; Unity directly owns the prepared Debug daemon and matching private state");
|
|
}
|
|
|
|
static void RequireDaemonPlayableRunGenerationAuthority(string root)
|
|
{
|
|
var daemonProgramPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs");
|
|
var daemonZoneGeneratorPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaDaemonZoneGenerator.cs");
|
|
var daemonLoadoutGeneratorPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaDaemonLoadoutGenerator.cs");
|
|
var sharedLoadoutGeneratorPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "LoadoutGenerator.cs");
|
|
|
|
var daemonProgram = File.Exists(daemonProgramPath)
|
|
? File.ReadAllText(daemonProgramPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon playable-run generation authority; daemon Program.cs is missing.");
|
|
var daemonZoneGenerator = File.Exists(daemonZoneGeneratorPath)
|
|
? File.ReadAllText(daemonZoneGeneratorPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon playable-run generation authority; daemon zone generator is missing.");
|
|
var daemonLoadoutGenerator = File.Exists(daemonLoadoutGeneratorPath)
|
|
? File.ReadAllText(daemonLoadoutGeneratorPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon playable-run generation authority; daemon loadout generator is missing.");
|
|
var sharedLoadoutGenerator = File.Exists(sharedLoadoutGeneratorPath)
|
|
? File.ReadAllText(sharedLoadoutGeneratorPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon playable-run generation authority; legacy Unity LoadoutGenerator.cs is missing.");
|
|
|
|
var requiredDaemonSymbols = new[]
|
|
{
|
|
"await AetheriaDaemonZoneGenerator.WritePlayableRunAsync(",
|
|
"internal static class AetheriaDaemonZoneGenerator",
|
|
"new AetheriaDaemonLoadoutGenerator(",
|
|
"GenerateEntities(loadouts, availabilityFactions, catalog, scenario)",
|
|
"AetheriaDaemonTerminusScenarios.Parse",
|
|
"ApplyReleasedClientProofScenario(entities, catalog, scenario)",
|
|
"TerminusScenario = terminusScenario",
|
|
"var rootRandom = new CultMath.Random(GenerationSeed)",
|
|
"(uint)rootRandom.NextInt(1, int.MaxValue)",
|
|
"Math.Pow(item.Price, _priceExponent)",
|
|
"ManufacturerDistance = _homeZones.TryGetValue(item.ManufacturerKey, out var home) ? Distance(_zoneIndex, home) : 1",
|
|
"FitsHardpoint(candidate, hardpoint)",
|
|
"PickHardpoint(availabilityFactionKey, hardpoint",
|
|
"var largestFootprint = candidates.Max(item => item.OccupiedCells)",
|
|
"HasBehavior(candidate, controllerKind)",
|
|
"for (var draw = 0; draw < 16 && pool.Count > 0; draw++)",
|
|
"FindFit(interiorCells, width, height, item, occupied)",
|
|
"slots.Add(Slot(hardpoint.PositionX, hardpoint.PositionY, item.ItemKey, rotation))",
|
|
"Rotation = RotationName(rotation)",
|
|
"AetheriaLoadoutGenerationReceipt",
|
|
"Selections = selectedKeys.Select"
|
|
};
|
|
|
|
var daemonText = daemonProgram + "\n" + daemonZoneGenerator + "\n" + daemonLoadoutGenerator;
|
|
var missingDaemonSymbols = requiredDaemonSymbols
|
|
.Where(symbol => !daemonText.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Playable run and loadout generation must be daemon-owned, not recovered through Unity ServerShared generators: " +
|
|
string.Join(", ", missingDaemonSymbols));
|
|
}
|
|
|
|
var forbiddenDaemonSymbols = new[]
|
|
{
|
|
"new LoadoutGenerator(",
|
|
"ZoneGenerator.Generate(",
|
|
"Assets.Scripts.ServerShared",
|
|
"ServerShared.LoadoutGenerator",
|
|
"ManufacturerScore(",
|
|
"FallbackHull(",
|
|
"sensorArrayCount",
|
|
"item.OccupiedCells != hardpoint.OccupiedCells"
|
|
};
|
|
var survivingDaemonSymbols = forbiddenDaemonSymbols
|
|
.Where(symbol => daemonText.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingDaemonSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon generation must not call the legacy Unity ServerShared loadout/zone generators: " +
|
|
string.Join(", ", survivingDaemonSymbols));
|
|
}
|
|
|
|
if (sharedLoadoutGenerator.Contains("// public LoadoutGenerator", StringComparison.Ordinal) ||
|
|
sharedLoadoutGenerator.Contains("// ref Random random", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Legacy Unity LoadoutGenerator must not carry commented constructor scars; Git history owns removed APIs.");
|
|
}
|
|
|
|
if (sharedLoadoutGenerator.Contains("item.OccupiedCells == hardpoint.OccupiedCells", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Hardpoint fit is same-type geometric containment; smaller equipment remains legal in larger mounts.");
|
|
}
|
|
}
|
|
|
|
static void RequireTypedBehaviorBodyKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.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, "Assets", "Scripts", "ServerShared", "Zone.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 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 document 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 RequireNoScopedReactiveFacadeWrappers(string root)
|
|
{
|
|
var providerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRuntimeClientProvider.cs");
|
|
var clientStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClientState.cs");
|
|
var requiredPaths = new[] { providerPath, clientStatePath };
|
|
var missingPaths = requiredPaths.Where(path => !File.Exists(path)).ToArray();
|
|
if (missingPaths.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot verify scoped reactive facade cleanup; missing sources: " +
|
|
string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path))));
|
|
}
|
|
|
|
var reactiveFacadeSymbols = new[]
|
|
{
|
|
"ReactiveProviderAdvertisement(",
|
|
"ReactiveHealth(",
|
|
"ReactiveCommandBoundary(",
|
|
"ReactiveAuthorityPolicy(",
|
|
"ReactiveStarbridgeScenario(",
|
|
"ReactiveStarbridgeSession(",
|
|
"ReactiveStarbridgeSummary(",
|
|
"ReactiveDaemonFrame(",
|
|
"ReactiveDaemonSoaView(",
|
|
"ReactiveZoneRender(",
|
|
"ReactiveLoadoutTemplates(",
|
|
"ReactiveSectorMap(",
|
|
"ReactiveCurrentZone(",
|
|
"ReactiveZoneContacts(",
|
|
"ReactiveVerseHostSettings(",
|
|
"ReactiveCurrentDocking(",
|
|
"ReactiveCurrentEntity(",
|
|
"ReactiveStationRefit(",
|
|
"ReactiveCatalogSnapshot(",
|
|
"ReactivePlayerSettingsDocument(",
|
|
"ReactiveEveSurface(",
|
|
"ReactiveGameViewport(",
|
|
"ReactiveObjectsViewport(",
|
|
"ReactiveGravityViewport(",
|
|
"ReactiveRenderSplatsViewport(",
|
|
"ReactiveZoneDetails(",
|
|
"ReactiveZoneDetailsSurface(",
|
|
"ReactiveInventoryPanelSurface(",
|
|
"ReactiveInventoryDropdownSurface(",
|
|
"ReactiveSelectedObject(",
|
|
"ReactiveInventory(",
|
|
"ReactiveStarbridgePlayerSeat("
|
|
};
|
|
|
|
var facadeHits = requiredPaths
|
|
.Select(path => new
|
|
{
|
|
Path = path,
|
|
Source = File.ReadAllText(path)
|
|
})
|
|
.SelectMany(entry => reactiveFacadeSymbols
|
|
.Where(symbol => entry.Source.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, entry.Path)}:{symbol}"))
|
|
.ToArray();
|
|
if (facadeHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria client/provider state must expose typed document handles, not scoped ReactiveX facade methods. Use Handle.Reactive() at the caller edge: " +
|
|
string.Join(", ", facadeHits));
|
|
}
|
|
|
|
var sourceRoots = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts"),
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime"),
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime")
|
|
};
|
|
var scopedReactiveCallHits = sourceRoots
|
|
.Where(Directory.Exists)
|
|
.SelectMany(path => Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories))
|
|
.Where(path => !path.Contains(Path.DirectorySeparatorChar + "Plugins" + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
|
|
.Select(path => new
|
|
{
|
|
Path = path,
|
|
Source = File.ReadAllText(path)
|
|
})
|
|
.SelectMany(entry => System.Text.RegularExpressions.Regex
|
|
.Matches(entry.Source, @"\.Reactive(?!Async\b)[A-Z][A-Za-z0-9_]*\s*\(")
|
|
.Select(match => $"{Path.GetRelativePath(root, entry.Path)}:{match.Value}"))
|
|
.Distinct(StringComparer.Ordinal)
|
|
.ToArray();
|
|
if (scopedReactiveCallHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria source still calls scoped ReactiveX wrappers. Grab the typed document handle once and call .Reactive() on that handle: " +
|
|
string.Join(", ", scopedReactiveCallHits));
|
|
}
|
|
|
|
Console.WriteLine("Scoped reactive facade cleanup: Unity/Aetheria source uses typed document handles plus Handle.Reactive(), not ReactiveX wrappers");
|
|
}
|
|
|
|
static void RequireNoDeadRuntimeProjectionCaches(string root)
|
|
{
|
|
var runtimeDocumentsFactoryDirectory = Path.Combine(root, "Assets", "Scripts", "ServerShared", "RuntimeProjection");
|
|
if (Directory.Exists(runtimeDocumentsFactoryDirectory))
|
|
{
|
|
var survivors = Directory.EnumerateFileSystemEntries(runtimeDocumentsFactoryDirectory)
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
throw new InvalidOperationException(
|
|
"Dead runtime-projection helper directory should stay deleted; move live helpers under explicit runtime owners: " +
|
|
string.Join(", ", survivors));
|
|
}
|
|
|
|
var selectionExtensionsPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "EnumerableSelectionExtensions.cs");
|
|
var sharedProjectPath = Path.Combine(root, "Aetheria.Shared.Unity.csproj");
|
|
var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs");
|
|
var selectionExtensions = File.Exists(selectionExtensionsPath)
|
|
? File.ReadAllText(selectionExtensionsPath)
|
|
: "";
|
|
var sharedProject = File.Exists(sharedProjectPath)
|
|
? File.ReadAllText(sharedProjectPath)
|
|
: "";
|
|
var tradeMenu = File.Exists(tradeMenuPath)
|
|
? File.ReadAllText(tradeMenuPath)
|
|
: "";
|
|
|
|
if (!selectionExtensions.Contains("public static T MaxBy<T, U>", StringComparison.Ordinal) ||
|
|
!selectionExtensions.Contains("public static T MinBy<T, U>", StringComparison.Ordinal) ||
|
|
!sharedProject.Contains(@"Assets\Scripts\ServerShared\EnumerableSelectionExtensions.cs", StringComparison.Ordinal) ||
|
|
sharedProject.Contains("RuntimeProjection", StringComparison.Ordinal) ||
|
|
tradeMenu.Contains(".FormatTypeName()", StringComparison.Ordinal) ||
|
|
!tradeMenu.Contains("private static string FormatTypeName(string typeName)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Live RuntimeProjection remnants must move into explicit owners: enumerable selection helpers under ServerShared and trade labels inside TradeMenu.");
|
|
}
|
|
}
|
|
|
|
static void RequireNoDeadRuntimeProjectionClasses(string root)
|
|
{
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeRtsSimulation",
|
|
"AetheriaRuntimeRtsProjection",
|
|
"AetheriaRuntimeStarbridgeProjection"
|
|
};
|
|
|
|
var hits = Directory.EnumerateFiles(root, "*.cs", SearchOption.AllDirectories)
|
|
.Where(path =>
|
|
!path.Contains($"{Path.DirectorySeparatorChar}Assets{Path.DirectorySeparatorChar}Plugins{Path.DirectorySeparatorChar}", StringComparison.Ordinal) &&
|
|
!path.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) &&
|
|
!path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) &&
|
|
!path.Contains($"{Path.DirectorySeparatorChar}Temp{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) &&
|
|
!path.EndsWith(Path.Combine("Aetheria.State.Verify", "Program.cs"), StringComparison.OrdinalIgnoreCase))
|
|
.SelectMany(path =>
|
|
{
|
|
var text = File.ReadAllText(path);
|
|
return forbiddenSymbols
|
|
.Where(symbol => text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, path)} contains {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Dead runtime projection class names must stay deleted; use typed document builders/handles instead: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var activeDocsRoot = Path.Combine(root, "Aetheria.State", "docs");
|
|
if (Directory.Exists(activeDocsRoot))
|
|
{
|
|
var docHits = Directory.EnumerateFiles(activeDocsRoot, "*.md", SearchOption.AllDirectories)
|
|
.SelectMany(path =>
|
|
{
|
|
var text = File.ReadAllText(path);
|
|
return forbiddenSymbols
|
|
.Where(symbol => text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, path)} contains {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (docHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Active architecture docs must not point future work at deleted runtime-specific shared authorities: " +
|
|
string.Join("; ", docHits));
|
|
}
|
|
|
|
var daemonRuntimeSpecificHookPhrases = new[]
|
|
{
|
|
"daemon RTS viewport",
|
|
"Daemon RTS viewport",
|
|
"temporary RUDP RTS viewport",
|
|
"RUDP RTS viewport",
|
|
"daemon RTS command",
|
|
"Daemon RTS command"
|
|
};
|
|
var hookPhraseHits = Directory.EnumerateFiles(activeDocsRoot, "*.md", SearchOption.AllDirectories)
|
|
.SelectMany(path =>
|
|
{
|
|
var text = File.ReadAllText(path);
|
|
return daemonRuntimeSpecificHookPhrases
|
|
.Where(phrase => text.Contains(phrase, StringComparison.Ordinal))
|
|
.Select(phrase => $"{Path.GetRelativePath(root, path)} contains {phrase}");
|
|
})
|
|
.ToArray();
|
|
if (hookPhraseHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Active architecture docs must describe daemon RUDP hooks as client-neutral compatibility, not RTS-specific daemon authority: " +
|
|
string.Join("; ", hookPhraseHits));
|
|
}
|
|
}
|
|
}
|
|
|
|
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 RequireTypedAgentTaskKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "AgentTask.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "Survey.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "Mining.cs")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"public Guid Zone",
|
|
"List<Guid> Planets",
|
|
"public Guid Asteroids"
|
|
};
|
|
|
|
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(
|
|
"Agent task shells must retain typed zone/body keys rather than raw GUID fields: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredSymbols = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "AgentTask.cs")] = new[]
|
|
{
|
|
"public string ZoneKey = \"\";"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "Survey.cs")] = new[]
|
|
{
|
|
"public List<string> PlanetBodyKeys;"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "Mining.cs")] = new[]
|
|
{
|
|
"public string AsteroidBeltBodyKey = \"\";"
|
|
}
|
|
};
|
|
|
|
var missing = requiredSymbols
|
|
.Where(entry => !File.Exists(entry.Key) ||
|
|
entry.Value.Any(symbol => !File.ReadAllText(entry.Key).Contains(symbol, StringComparison.Ordinal)))
|
|
.SelectMany(entry => entry.Value
|
|
.Where(symbol => !File.Exists(entry.Key) || !File.ReadAllText(entry.Key).Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, entry.Key)}: missing {symbol}"))
|
|
.ToArray();
|
|
|
|
if (missing.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Agent task shells must keep typed key placeholders on zone/body references: " +
|
|
string.Join("; ", missing));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedOrbitalEntityOrbitKeys(string root)
|
|
{
|
|
var checkedFiles = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "OrbitalEntity.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared", "EntityConstructionBlueprintMaterializer.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", "EntityConstructionBlueprintMaterializer.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", "AetheriaUnityCurrentEntityPresentation.cs")] = new[]
|
|
{
|
|
"AetheriaRuntimeZoneRenderDocument zoneRender",
|
|
"zoneRender?.BodyPoses ?? Array.Empty<AetheriaRuntimeZoneRenderBodyPose>()",
|
|
"string.Equals(body.OrbitKey ?? \"\", orbital.OrbitKey, StringComparison.Ordinal)",
|
|
"string.Equals(body.OrbitKey ?? \"\", parentOrbitKey, StringComparison.Ordinal)"
|
|
},
|
|
[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", "Zone Display", "ZoneRenderer.cs")] = new[]
|
|
{
|
|
"private readonly List<AetheriaRuntimeDaemonBodyView> _daemonBodyViews",
|
|
"private void SyncDaemonBodyViews()",
|
|
"private IReadOnlyList<AetheriaRuntimeBodySnapshotCommit> _zoneRenderBodies",
|
|
"_zoneRenderBodies = render?.Bodies ?? Array.Empty<AetheriaRuntimeBodySnapshotCommit>();",
|
|
"foreach (var bodyPose in _zoneRenderBodyPoses ?? Array.Empty<AetheriaRuntimeZoneRenderBodyPose>())",
|
|
"ResolveDaemonRenderViewport()",
|
|
"private AetheriaRuntimeXzRect ResolveDaemonRenderViewport()",
|
|
"private void UnloadBodyView(string bodyKey)",
|
|
"foreach (var bodyView in _daemonBodyViews)",
|
|
"foreach (var pose in _daemonBodyPoses)",
|
|
"beltPosesByBodyKey.TryGetValue(bodyKey, out var beltPose)",
|
|
"_daemonBodyPosesByBodyKey.TryGetValue(planet.Key, out var pose)",
|
|
"var p = new float2((float)pose.CenterX, (float)pose.CenterZ);",
|
|
"var parent = new float2((float)pose.ParentCenterX, (float)pose.ParentCenterZ);"
|
|
},
|
|
[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; }",
|
|
"public Guid ID { 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)",
|
|
"public string GetBodyKey(Guid bodyId)",
|
|
"private readonly Dictionary<Guid, Planet> _planetsById = new Dictionary<Guid, Planet>();",
|
|
"private readonly Dictionary<Guid, AsteroidBelt> _asteroidBeltsById = new Dictionary<Guid, AsteroidBelt>();"
|
|
};
|
|
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);",
|
|
"PlanetInstances[runtimeSun.BodyKey] = runtimeSun;",
|
|
"PlanetInstances[runtimeGas.BodyKey] = runtimeGas;",
|
|
"PlanetInstances[runtimePlanet.BodyKey] = runtimePlanet;",
|
|
"AsteroidBelts[runtimeBelt.BodyKey] = runtimeBelt;",
|
|
"Orbits[runtimeOrbit.OrbitKey] = runtimeOrbit;",
|
|
"return PlanetInstances.TryGetValue(bodyKey ?? \"\", out planet);",
|
|
"return AsteroidBelts.TryGetValue(bodyKey ?? \"\", out belt);",
|
|
"return Orbits.TryGetValue(orbitKey ?? \"\", out orbit);",
|
|
"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);",
|
|
"public int QueryGravityInfluenceBrushes(",
|
|
"AetheriaGravityInfluenceBrush"
|
|
};
|
|
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;",
|
|
"public Dictionary<string, PlanetObject> Planets",
|
|
"private readonly List<AetheriaGravityInfluenceBrush> _visibleGravityBrushes",
|
|
"public IReadOnlyList<AetheriaGravityInfluenceBrush> VisibleGravityBrushes",
|
|
"Zone.QueryGravityInfluenceBrushes(viewportMin, viewportMax, _visibleGravityBrushes);"
|
|
};
|
|
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[]
|
|
{
|
|
"private readonly Dictionary<string, PlanetObject> _bodyViewsByBodyKey = new Dictionary<string, PlanetObject>(StringComparer.Ordinal);",
|
|
"public bool TryGetBodyView(string bodyKey, out PlanetObject bodyView)",
|
|
"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[bodyKey] = meshes.ToArray();",
|
|
"_beltObjects[bodyKey] = belt;",
|
|
"_bodyViewsByBodyKey[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 RequireDaemonRenderQueryAuthority(string root)
|
|
{
|
|
var queryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonRenderQueries.cs");
|
|
var querySource = File.Exists(queryPath)
|
|
? File.ReadAllText(queryPath)
|
|
: throw new InvalidOperationException("Daemon render queries must live in the shared Aetheria runtime package, not in Unity ZoneRenderer.");
|
|
if (querySource.Contains("Unity XZ viewport", StringComparison.Ordinal) ||
|
|
querySource.Contains("through Unity axes", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared daemon render query types must use domain projection vocabulary, not runtime-specific Unity axis copy.");
|
|
}
|
|
|
|
var packageSnapshotPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs");
|
|
var packageSnapshot = File.Exists(packageSnapshotPath)
|
|
? File.ReadAllText(packageSnapshotPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon render query authority; package snapshot documents are missing.");
|
|
|
|
var indirectRendererPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonIndirectRenderer.cs");
|
|
var indirectRenderer = File.Exists(indirectRendererPath)
|
|
? File.ReadAllText(indirectRendererPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaDaemonIndirectRenderer.cs is missing.");
|
|
|
|
var zoneRendererPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs");
|
|
var zoneRenderer = File.Exists(zoneRendererPath)
|
|
? File.ReadAllText(zoneRendererPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon render query authority; ZoneRenderer.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 daemon render query authority; ActionGameManager.cs is missing.");
|
|
var targetPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityTargetPresentation.cs");
|
|
var targetPresentation = File.Exists(targetPresentationPath)
|
|
? File.ReadAllText(targetPresentationPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaUnityTargetPresentation.cs is missing.");
|
|
var pilotFrameControllerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotFrameController.cs");
|
|
var pilotFrameController = File.Exists(pilotFrameControllerPath)
|
|
? File.ReadAllText(pilotFrameControllerPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaUnityPilotFrameController.cs is missing.");
|
|
var gameplayInputShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayInputShell.cs");
|
|
var gameplayInputShell = File.Exists(gameplayInputShellPath)
|
|
? File.ReadAllText(gameplayInputShellPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaUnityGameplayInputShell.cs is missing.");
|
|
var renderSettingsBridgePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRenderSettingsBridge.cs");
|
|
var cockpitHudShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityCockpitHudShell.cs");
|
|
var cockpitHudShell = File.Exists(cockpitHudShellPath)
|
|
? File.ReadAllText(cockpitHudShellPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaUnityCockpitHudShell.cs is missing.");
|
|
var gameplayBootShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs");
|
|
var gameplayBootShell = File.Exists(gameplayBootShellPath)
|
|
? File.ReadAllText(gameplayBootShellPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaUnityGameplayBootShell.cs is missing.");
|
|
var unityRenderPresentation = actionGameManager + "\n" + targetPresentation + "\n" + pilotFrameController + "\n" + gameplayInputShell + "\n" + cockpitHudShell + "\n" + gameplayBootShell;
|
|
|
|
var canonicalSnapshotPath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs");
|
|
var canonicalSnapshot = File.Exists(canonicalSnapshotPath)
|
|
? File.ReadAllText(canonicalSnapshotPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon render query authority; canonical runtime state documents are missing.");
|
|
|
|
var requiredQuerySymbols = new[]
|
|
{
|
|
"public readonly struct AetheriaRuntimeDaemonRenderSettings",
|
|
"public readonly struct AetheriaRuntimeExponentialCurve",
|
|
"public double ConvergenceMinimumDistance { get; }",
|
|
"public double HypothermiaTemperature { get; }",
|
|
"public double HeatstrokeTemperature { get; }",
|
|
"public double SevereHeatstrokeRiskThreshold { get; }",
|
|
"public double TargetDetectionInfoThreshold { get; }",
|
|
"public double LockIndicatorNoiseAmplitude { get; }",
|
|
"public double HeatstrokePhasingFloor { get; }",
|
|
"public double HeatstrokePhasingFrequency { get; }",
|
|
"public double TargetSpottedBlinkFrequency { get; }",
|
|
"public double TargetSpottedBlinkOffset { get; }",
|
|
"public IReadOnlyList<double> MinimapZoomLevels { get; }",
|
|
"public int DefaultMinimapZoom { get; }",
|
|
"public double WormholeDistanceRatio { get; }",
|
|
"public double DefaultViewDistance { get; }",
|
|
"public double MinimapIconScale { get; }",
|
|
"public double MinimapAsteroidSize { get; }",
|
|
"public AetheriaRuntimeExponentialCurve BodyIconSizeCurve { get; }",
|
|
"public double MinimapZoneGravityRange { get; }",
|
|
"public double AsteroidVerticalOffset { get; }",
|
|
"public double PlanetRotationSpeed { get; }",
|
|
"public double ZoneBoundaryPower { get; }",
|
|
"public double ZoneBoundaryDepth { get; }",
|
|
"public int AsteroidMeshCount { get; }",
|
|
"public AetheriaRuntimeExponentialCurve BodyRadiusCurve { get; }",
|
|
"public AetheriaRuntimeExponentialCurve LightRadiusCurve { get; }",
|
|
"public AetheriaRuntimeExponentialCurve GravityWaveFrequencyCurve { get; }",
|
|
"public int ResolveDefaultMinimapZoomIndex()",
|
|
"public int ResolveNextMinimapZoomIndex(int currentIndex)",
|
|
"public double ResolveMinimapDistance(int zoomIndex)",
|
|
"public double ResolveDefaultMinimapDistance()",
|
|
"public double ResolveMinimapIconSize(double minimapDistance)",
|
|
"public double ResolveBodyIconSize(double mass)",
|
|
"public double ResolveBodyRadius(double mass)",
|
|
"public double ResolveLightRadius(double mass)",
|
|
"public double ResolveGravityWaveFrequency(double mass)",
|
|
"public double NormalizeThermalRisk(double temperature)",
|
|
"public double NormalizeHeatstrokePost(double heatstroke)",
|
|
"public double NormalizeSevereHeatstrokePost(double heatstroke)",
|
|
"public double ResolveSevereHeatstrokePostWeight(double heatstroke, double timeSeconds)",
|
|
"public double NormalizeDetectionProgress(double infoGathered)",
|
|
"public bool ResolveTargetSpottedFillEnabled(double infoGathered, double timeSeconds)",
|
|
"public double NormalizeTargetVisibilityFill(double infoGathered)",
|
|
"public double NormalizeVisibilityToTargetFill(double infoGathered)",
|
|
"public double NormalizeTargetStatusFill(double normalizedValue)",
|
|
"public double ResolveLockIndicatorNoiseAmplitude(double lockProgress)",
|
|
"public double ResolveLockIndicatorNoiseFrequency(double lockProgress)",
|
|
"public double ResolveLockSpinSpeed(double lockProgress)",
|
|
"public AetheriaRuntimeExponentialCurve TemperatureEmissionCurve { get; }",
|
|
"public AetheriaRuntimeExponentialLerp LockIndicatorFrequency { get; }",
|
|
"public AetheriaRuntimeExponentialLerp LockSpinSpeed { get; }",
|
|
"public static AetheriaRuntimeGravityInfluenceBrush[] QueryGravityInfluences(",
|
|
"AetheriaRuntimeZoneSnapshotCommit? zone",
|
|
"public static int QueryGravityInfluences(",
|
|
"List<AetheriaRuntimeGravityInfluenceBrush> brushes",
|
|
"public static AetheriaRuntimeDaemonRenderGroupDocument[] QueryRenderGroups(",
|
|
"AetheriaRuntimeDaemonSoaViewIndex? index",
|
|
"AetheriaRuntimeXzRect",
|
|
"IntersectsCircle(viewport, center.x, center.z, radius)",
|
|
"IntersectsBounds(group, minX, minY, minZ, maxX, maxY, maxZ)",
|
|
"TryResolveBodyCenter(body, orbitPositions, out var center)",
|
|
"public static double EvaluateGravityTerrainHeight(",
|
|
"public static double ResolveZoneRenderRadius(",
|
|
"public static AetheriaRuntimeGravityTerrainBand QueryGravityTerrainBand(",
|
|
"zone.GravityTerrainRadius",
|
|
"brush.WaveFrequency",
|
|
"public static AetheriaRuntimeDaemonBodyPose[] QueryBodyPoses(",
|
|
"public static int QueryBodyPoses(",
|
|
"new AetheriaRuntimeDaemonBodyPose(",
|
|
"AetheriaRuntimeDaemonBodyView",
|
|
"public static AetheriaRuntimeDaemonBodyView[] QueryBodyViews(",
|
|
"AetheriaRuntimeXzRect viewport)",
|
|
"public static int QueryBodyViews(",
|
|
"IntersectsCircle(",
|
|
"Math.Max(ResolveGravityRadius(body), ResolveWaveRadius(body))",
|
|
"AetheriaRuntimeDaemonAsteroidBeltPose",
|
|
"public static AetheriaRuntimeDaemonAsteroidBeltPose[] QueryAsteroidBeltPoses(",
|
|
"public static int QueryAsteroidBeltPoses(",
|
|
"AetheriaRuntimeDaemonAsteroidInstancePose",
|
|
"public static AetheriaRuntimeDaemonAsteroidInstancePose[] QueryAsteroidInstancePoses(",
|
|
"public static int QueryAsteroidInstancePoses(",
|
|
"TryResolveAsteroidBeltCenter(body, orbitPositions, orbits, out var center)",
|
|
"ResolveAsteroidBeltRadius(body)",
|
|
"AetheriaRuntimeDaemonCompassMarker",
|
|
"public static AetheriaRuntimeDaemonCompassMarker[] QueryCompassMarkers(",
|
|
"public static int QueryCompassMarkers(",
|
|
"public static int[] QueryVisibleEntityIndices(",
|
|
"public static int QueryVisibleEntityIndices(",
|
|
"public static int[] QueryPresentationEntityIndices(",
|
|
"public static int QueryPresentationEntityIndices(",
|
|
"TryParseEntityIndex(run?.CurrentEntityKey)",
|
|
"ContainsPoint(viewport, entity.PositionX, entity.PositionZ)",
|
|
"AetheriaRuntimeDaemonEntityContact",
|
|
"public static bool TryQueryEntityContact(",
|
|
"public static AetheriaRuntimeDaemonEntityContact[] QueryEntityContacts(",
|
|
"public static int QueryEntityContacts(",
|
|
"AetheriaRuntimeDaemonEntityTarget",
|
|
"public static bool TryQueryEntityTarget(",
|
|
"AetheriaRuntimeDaemonWormholeExit",
|
|
"public static AetheriaRuntimeDaemonWormholeExit[] QueryWormholeExits(",
|
|
"public static int QueryWormholeExits(",
|
|
"BuildZoneMap(run)",
|
|
"BuildEntityMap(zone)"
|
|
};
|
|
|
|
var missingQuerySymbols = requiredQuerySymbols
|
|
.Where(symbol => !querySource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingQuerySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon render query API must expose gravity-in-rect and render-group bounds queries over daemon snapshot/SoA data: " +
|
|
string.Join(", ", missingQuerySymbols));
|
|
}
|
|
|
|
var requiredSnapshotSymbols = new[]
|
|
{
|
|
"public double GravityInfluenceCenterX",
|
|
"public double GravityInfluenceCenterZ",
|
|
"public double GravityInfluenceRadius",
|
|
"public double GravityWellDepth",
|
|
"public double GravityWaveRadius",
|
|
"public double GravityWaveDepth",
|
|
"public double GravityWaveSpeed",
|
|
"public double GravityTerrainRadius",
|
|
"public double GravityTerrainDepth",
|
|
"public double GravityTerrainDepthExponent",
|
|
"public double GravityTerrainBoundaryFog",
|
|
"public double GravityTerrainWaveFrequency",
|
|
"public double SimulationTimeSeconds"
|
|
};
|
|
|
|
var missingPackageSnapshotSymbols = requiredSnapshotSymbols
|
|
.Where(symbol => !packageSnapshot.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs: missing {symbol}");
|
|
var missingCanonicalSnapshotSymbols = requiredSnapshotSymbols
|
|
.Where(symbol => !canonicalSnapshot.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs: missing {symbol}");
|
|
var missingSnapshotSymbols = missingPackageSnapshotSymbols.Concat(missingCanonicalSnapshotSymbols).ToArray();
|
|
if (missingSnapshotSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon body snapshots must publish resolved gravity influence fields so render clients do not reconstruct Unity gravity state: " +
|
|
string.Join("; ", missingSnapshotSymbols));
|
|
}
|
|
|
|
var requiredRendererSymbols = new[]
|
|
{
|
|
"private readonly List<AetheriaRuntimeDaemonRenderGroupDocument> _visibleRenderGroups",
|
|
"AetheriaRuntimeDaemonRenderQueries.QueryRenderGroups(",
|
|
"TryGetCameraQueryBounds(out var min, out var max)",
|
|
"return index.RenderGroups;"
|
|
};
|
|
var missingRendererSymbols = requiredRendererSymbols
|
|
.Where(symbol => !indirectRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingRendererSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity's daemon indirect renderer must query daemon SoA render groups by camera bounds before lowering draw calls: " +
|
|
string.Join(", ", missingRendererSymbols));
|
|
}
|
|
|
|
if (zoneRenderer.Contains("Zone.GetHeight(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must evaluate gravity terrain from daemon render queries instead of the mirrored Unity Zone height evaluator.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("public Zone Zone", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("public void LoadZone(", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("public Dictionary<string, PlanetObject> Planets", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("public ItemManager ItemManager", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("ZoneRenderer.ItemManager", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must expose daemon-view rendering concepts instead of public mirrored Zone/ItemManager ownership.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("ItemManager.Log(", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("ItemManager.Get", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must not depend on ItemManager for renderer diagnostics or item projection.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("Zone.PowerPulse(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must query daemon gravity terrain bands instead of calling mirrored Unity Zone gravity math.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("Settings.PlanetSettings.GravityRadius.Evaluate(", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("Settings.PlanetSettings.GravityDepth.Evaluate(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must lower daemon-authored gravity influence radius/depth instead of reconstructing gravity brushes from Unity settings.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("Settings.GameplaySettings.TargetDetectionInfoThreshold", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must query daemon visibility through shared render settings instead of Unity GameplaySettings.");
|
|
}
|
|
|
|
if (ContainsUnitySettingsMember(zoneRenderer, "MinimapZoomLevels") ||
|
|
ContainsUnitySettingsMember(zoneRenderer, "DefaultMinimapZoom") ||
|
|
ContainsUnitySettingsMember(zoneRenderer, "MinimapIconSize") ||
|
|
ContainsUnitySettingsMember(zoneRenderer, "DefaultViewDistance") ||
|
|
ContainsUnitySettingsMember(zoneRenderer, "MinimapAsteroidSize") ||
|
|
ContainsUnitySettingsMember(zoneRenderer, "IconSize") ||
|
|
ContainsUnitySettingsMember(zoneRenderer, "MinimapZoneGravityRange") ||
|
|
ContainsUnitySettingsMember(zoneRenderer, "PlanetRotationSpeed") ||
|
|
ContainsUnitySettingsMember(zoneRenderer, "AsteroidMeshCount"))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must initialize view/minimap presentation through shared daemon render settings instead of Unity GameSettings.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains(" Settings.WormholeDistanceRatio", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("(Settings.WormholeDistanceRatio", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains(", Settings.WormholeDistanceRatio", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must query daemon wormhole exits through shared render settings instead of Unity GameSettings.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("Zone.GetOrbitPosition(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must position bodies from daemon render body poses instead of the mirrored Unity Zone orbit evaluator.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("Zone.Time", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("Zone?.Time", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must sample animation and terrain from daemon simulation time instead of mirrored Unity Zone time.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("Zone.PlanetInstances[planet.Key]", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("zone.PlanetInstances.TryGetValue(pose.BodyKey, out var planet)", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("LoadPlanet(planet)", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("zone.AsteroidBelts.TryGetValue(body.BodyKey", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("LoadAsteroidBelt(belt)", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("new Wormhole", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("zone.Galaxy", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer frame updates must read body render state from daemon body poses instead of indexed mirrored Unity planets.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("foreach (var orbit in zone.Orbits.Values)", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("FirstOrDefault(body => body.OrbitKey == orbit.OrbitKey)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must instantiate body views from daemon body poses keyed by body id instead of rebuilding a Unity orbit hierarchy.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("belt.OrbitPosition", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("belt.Radius", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("belt.Transforms", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("foreach (var (key, belt) in Zone.AsteroidBelts)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer frame updates must read asteroid belt and instance poses from daemon render queries instead of mirrored Unity asteroid belts.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("_zoneSubscriptions", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("zone.Entities.ObserveAdd()", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("zone.Entities.ObserveRemove()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must reconcile rendered entities from daemon frame snapshots instead of subscribing to mirrored Unity entity collection mutations.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("foreach (var entity in legacyEntityFacadeZone.Entities)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must use the daemon-indexed observed entity projection supplied by gameplay instead of enumerating Unity Zone.Entities.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("_legacyEntityFacadeZone", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("_legacyEntityFacadeZone?.Radius", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("_legacyLootFacadeZone", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("Zone legacyLootFacadeZone", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("gridObject.Zone = ", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must not keep Unity Zone facade handles; daemon snapshots and observed entity projections own renderer input.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("PerspectiveEntity.EntityInfoGathered", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must query daemon compass markers instead of reading mirrored Unity entity visibility state.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("VisibleEntities", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must query daemon visible entity indices instead of subscribing to mirrored Unity visibility collections.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("AdjacentZones", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must query daemon wormhole exits instead of enumerating mirrored GalaxyZone adjacency.");
|
|
}
|
|
|
|
var requiredZoneRendererSymbols = new[]
|
|
{
|
|
"public AetheriaRuntimeDaemonRenderSettings RenderSettings { get; set; }",
|
|
"private string _daemonCurrentEntityKey = \"\";",
|
|
"private double _daemonSimulationTimeSeconds;",
|
|
"private readonly List<AetheriaRuntimeDaemonBodyView> _daemonBodyViews",
|
|
"private IReadOnlyList<AetheriaRuntimeBodySnapshotCommit> _zoneRenderBodies",
|
|
"private readonly List<AetheriaRuntimeZoneRenderBodyPose> _daemonBodyPoses",
|
|
"private readonly Dictionary<string, AetheriaRuntimeZoneRenderBodyPose> _daemonBodyPosesByBodyKey",
|
|
"private readonly Dictionary<string, PlanetObject> _bodyViewsByBodyKey",
|
|
"public bool TryGetBodyView(string bodyKey, out PlanetObject bodyView)",
|
|
"private readonly List<AetheriaRuntimeZoneRenderAsteroidBeltPose> _daemonAsteroidBeltPoses",
|
|
"private readonly List<AetheriaRuntimeZoneRenderAsteroidInstancePose> _visibleAsteroidInstancePoses",
|
|
"private readonly List<AetheriaRuntimeDaemonCompassMarker> _daemonCompassMarkers",
|
|
"private readonly Dictionary<int, AetheriaRuntimeDaemonCompassMarker> _daemonCompassMarkersByEntityIndex",
|
|
"private readonly Dictionary<int, AetheriaRuntimeZoneTargetRow> _daemonTargetRowsByEntityIndex",
|
|
"private readonly List<AetheriaRuntimeZoneContactRow> _daemonContactRows",
|
|
"private IReadOnlyDictionary<int, Entity> _observedEntitySnapshotsByDaemonIndex",
|
|
"private readonly List<int> _daemonPresentationEntityIndices",
|
|
"private readonly HashSet<int> _daemonPresentationEntityIndicesSet",
|
|
"private readonly List<int> _daemonVisibleEntityIndices",
|
|
"private readonly HashSet<int> _daemonVisibleEntityIndicesSet",
|
|
"private readonly HashSet<int> _visibleDaemonEntityIndices",
|
|
"private AetheriaDaemonObserver _daemonObserver;",
|
|
"private readonly List<AetheriaRuntimeDaemonWormholeExit> _daemonWormholeExits",
|
|
"render?.WormholeExits ?? Array.Empty<AetheriaRuntimeZoneRenderWormholeExit>()",
|
|
"_daemonWormholeExits.Add(new AetheriaRuntimeDaemonWormholeExit(",
|
|
"public Dictionary<int, (GameObject gravity, CompassIcon icon)> WormholeInstances",
|
|
"public void LoadDaemonZoneView(",
|
|
"IReadOnlyDictionary<int, Entity> observedEntitySnapshotsByDaemonIndex,",
|
|
"AetheriaRuntimeZoneRenderDocument render)",
|
|
"public BodySettingsCollection[] BodySettingsCollections;",
|
|
"public void ApplyZoneRender(AetheriaRuntimeZoneRenderDocument render)",
|
|
"_daemonCurrentEntityKey = render?.CurrentEntityKey ?? \"\";",
|
|
"_daemonSimulationTimeSeconds = render?.SimulationTimeSeconds ?? 0;",
|
|
"_zoneRenderBodies = render?.Bodies ?? Array.Empty<AetheriaRuntimeBodySnapshotCommit>();",
|
|
"_observedEntitySnapshotsByDaemonIndex = observedEntitySnapshotsByDaemonIndex;",
|
|
"render?.ZoneRenderRadius ?? 2000",
|
|
"private readonly HashSet<string> _daemonVisibleBodyKeys",
|
|
"private void SyncDaemonBodyViews()",
|
|
"foreach (var bodyView in _daemonBodyViews)",
|
|
"if (bodyView.IsAsteroidBelt)",
|
|
"foreach (var bodyPose in _zoneRenderBodyPoses ?? Array.Empty<AetheriaRuntimeZoneRenderBodyPose>())",
|
|
"ResolveDaemonRenderViewport()",
|
|
"private AetheriaRuntimeXzRect ResolveDaemonRenderViewport()",
|
|
"private static AetheriaRuntimeViewportBounds ToViewportBounds(AetheriaRuntimeXzRect viewport)",
|
|
"private void UnloadBodyView(string bodyKey)",
|
|
"foreach (var pose in _daemonBodyPoses)",
|
|
"LoadPlanet(body)",
|
|
"void LoadPlanet(AetheriaRuntimeBodySnapshotCommit body)",
|
|
"beltPosesByBodyKey.TryGetValue(bodyKey, out var beltPose)",
|
|
"LoadAsteroidBelt(beltPose)",
|
|
"void LoadAsteroidBelt(AetheriaRuntimeZoneRenderAsteroidBeltPose beltPose)",
|
|
"private void SyncDaemonEntityInstances()",
|
|
"TryCollectDaemonPresentationEntityIndicesFromSoa(viewport)",
|
|
"private bool TryCollectDaemonPresentationEntityIndicesFromSoa(AetheriaRuntimeXzRect viewport)",
|
|
"observer.LastRenderNativeView",
|
|
"!view.IsCreated || !view.HasEntityIndex",
|
|
"view.HasRenderVisibility && view.RenderVisibility[i] == 0",
|
|
"var position = view.Position[i];",
|
|
"var entityIndex = view.EntityIndex[i];",
|
|
"private AetheriaDaemonObserver ResolveDaemonObserver()",
|
|
"FindAnyObjectByType<AetheriaDaemonObserver>()",
|
|
"ResolveObjectsViewport(viewport)",
|
|
"private AetheriaRuntimeObjectsViewportDocument ResolveObjectsViewport(AetheriaRuntimeXzRect viewport)",
|
|
".ZonePresentationDocuments(viewportBounds, \"unity-zone-renderer\")",
|
|
"_zonePresentationDocuments.CurrentObjectsViewport",
|
|
"foreach (var entity in objects?.Objects ?? Array.Empty<AetheriaRuntimeViewportObject>())",
|
|
"_observedEntitySnapshotsByDaemonIndex.TryGetValue(entityIndex, out var entity)",
|
|
"Loading entity {entity.Name} from daemon presentation query",
|
|
"UnloadEntity(pair.Value.Entity)",
|
|
"private readonly Dictionary<int, EntityInstance> _entityInstancesByDaemonIndex",
|
|
"public IReadOnlyDictionary<int, EntityInstance> DaemonEntityInstances => _entityInstancesByDaemonIndex;",
|
|
"public bool TryGetEntityInstance(int daemonEntityIndex, out EntityInstance instance)",
|
|
"public bool TryGetEntityInstance(Entity entity, out EntityInstance instance)",
|
|
"public bool TryGetDaemonTargetDistance(int daemonEntityIndex, out float distance)",
|
|
"_daemonTargetRowsByEntityIndex.TryGetValue(daemonEntityIndex, out var target)",
|
|
"foreach (var entity in EntityInstances.Keys.ToArray())",
|
|
"_daemonBodyPoses.AddRange(_zoneRenderBodyPoses ?? Array.Empty<AetheriaRuntimeZoneRenderBodyPose>());",
|
|
"_daemonAsteroidBeltPoses.AddRange(_zoneRenderAsteroidBeltPoses ?? Array.Empty<AetheriaRuntimeZoneRenderAsteroidBeltPose>());",
|
|
"foreach (var beltPose in _daemonAsteroidBeltPoses)",
|
|
"beltPose.InstancePoses ?? Array.Empty<AetheriaRuntimeZoneRenderAsteroidInstancePose>()",
|
|
"private void RefreshDaemonContactRows()",
|
|
"_zonePresentationDocuments.CurrentContacts",
|
|
"private void RefreshDaemonCompassMarkers()",
|
|
"private void RefreshDaemonVisibleEntityInstances()",
|
|
"PowerPulse(",
|
|
"RadialWaves(",
|
|
"RenderSettings.TargetDetectionInfoThreshold",
|
|
"RenderSettings.DefaultViewDistance",
|
|
"RenderSettings.MinimapAsteroidSize",
|
|
"RenderSettings.ResolveDefaultMinimapDistance()",
|
|
"RenderSettings.ResolveMinimapIconSize(value)",
|
|
"RenderSettings.ResolveBodyIconSize(mass)",
|
|
"RenderSettings.MinimapZoneGravityRange",
|
|
"RenderSettings.AsteroidVerticalOffset",
|
|
"RenderSettings.PlanetRotationSpeed",
|
|
"RenderSettings.ZoneBoundaryPower",
|
|
"RenderSettings.ZoneBoundaryDepth",
|
|
"RenderSettings.AsteroidMeshCount",
|
|
"RenderSettings.ResolveBodyRadius(mass)",
|
|
"RenderSettings.ResolveLightRadius(mass)",
|
|
"RenderSettings.ResolveGravityWaveFrequency(mass)",
|
|
"AddWormhole(exit)",
|
|
"public void AddWormhole(AetheriaRuntimeDaemonWormholeExit exit)",
|
|
"private double DaemonSimulationTimeSeconds => _daemonSimulationTimeSeconds;",
|
|
"_daemonCompassMarkersByEntityIndex.TryGetValue(entityInstance.DaemonEntityIndex",
|
|
"_daemonBodyPosesByBodyKey.TryGetValue(planet.Key, out var pose)",
|
|
"pose.GravityWaveSpeed"
|
|
};
|
|
var missingZoneRendererSymbols = requiredZoneRendererSymbols
|
|
.Where(symbol => !zoneRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingZoneRendererSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must lower daemon-authored gravity terrain snapshots instead of recomputing render height through Zone: " +
|
|
string.Join(", ", missingZoneRendererSymbols));
|
|
}
|
|
|
|
if (zoneRenderer.Contains("public GameSettings Settings", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer still accepts the whole Unity GameSettings object instead of explicit renderer asset/tuning inputs.");
|
|
}
|
|
|
|
if (zoneRenderer.Contains("foreach (var entitySnapshot in daemonZone?.Entities", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("Loading entity {entity.Name} from daemon entity snapshot", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer must query daemon presentation entities instead of loading every daemon entity snapshot into a mirrored Unity level.");
|
|
}
|
|
|
|
var entityInstancePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "EntityInstance.cs");
|
|
var entityInstance = File.Exists(entityInstancePath)
|
|
? File.ReadAllText(entityInstancePath)
|
|
: throw new InvalidOperationException("Cannot verify EntityInstance convergence authority; source file is missing.");
|
|
if (entityInstance.Contains("Entity.Target.Value != null ? Mathf.Max(Entity.TargetRange", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"EntityInstance convergence distance must read daemon target projection through ZoneRenderer instead of facade Entity.Target.");
|
|
}
|
|
|
|
if (entityInstance.Contains("Entity.ItemManager.GameplaySettings", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"EntityInstance render code must use daemon render settings instead of drilling through the observed Entity ItemManager facade.");
|
|
}
|
|
|
|
var requiredEntityInstanceSymbols = new[]
|
|
{
|
|
"ZoneRenderer.RenderSettings.TemperatureEmissionCurve.Evaluate(",
|
|
"ZoneRenderer.TryGetDaemonTargetDistance(DaemonEntityIndex, out var daemonTargetDistance)",
|
|
"Mathf.Max(daemonTargetDistance, (float)ZoneRenderer.RenderSettings.ConvergenceMinimumDistance)",
|
|
"LookAtPoint.position = transform.position + entityLookDirection * lookAtDistance;"
|
|
};
|
|
|
|
var missingEntityInstanceSymbols = requiredEntityInstanceSymbols
|
|
.Where(symbol => !entityInstance.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingEntityInstanceSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"EntityInstance no longer resolves convergence from daemon target projection: " +
|
|
string.Join(", ", missingEntityInstanceSymbols));
|
|
}
|
|
|
|
var schematicDisplayPath = Path.Combine(root, "Assets", "Scripts", "UI", "HUD", "SchematicDisplay.cs");
|
|
var schematicDisplay = File.Exists(schematicDisplayPath)
|
|
? File.ReadAllText(schematicDisplayPath)
|
|
: throw new InvalidOperationException("Cannot verify HUD thermal presentation authority; SchematicDisplay.cs is missing.");
|
|
if (schematicDisplay.Contains("Settings.GameplaySettings.HypothermiaTemperature", StringComparison.Ordinal) ||
|
|
schematicDisplay.Contains("Settings.GameplaySettings.HeatstrokeTemperature", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SchematicDisplay must normalize cockpit thermal risk through shared daemon render settings instead of Unity GameSettings.");
|
|
}
|
|
|
|
if (schematicDisplay.Contains("ActionGameManager.Instance.ZoneRenderer", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SchematicDisplay must not reach through ActionGameManager.Instance.ZoneRenderer for HUD presentation settings.");
|
|
}
|
|
|
|
var requiredSchematicHudSymbols = new[]
|
|
{
|
|
"CatalogSnapshot()",
|
|
"CurrentEntityHudStatus()",
|
|
"private AetheriaClientState _runtimeState;",
|
|
"private AetheriaClientState RuntimeState",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-schematic-display\")",
|
|
"RuntimeState.Catalog.Latest()",
|
|
"RuntimeState.PlayerSettings.Latest()",
|
|
"RuntimeState.CurrentEntity.Latest()?.Hud",
|
|
"?.Hud ?? new AetheriaRuntimeCurrentEntityHudStatus();",
|
|
"hud.OverrideShutdown",
|
|
"hud.HeatsinksEnabled",
|
|
"hud.Heatstroke",
|
|
"hud.Hypothermia",
|
|
"hud.Visibility",
|
|
"hud.HullDurabilityRatio"
|
|
};
|
|
var missingSchematicHudSymbols = requiredSchematicHudSymbols
|
|
.Where(symbol => !schematicDisplay.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSchematicHudSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SchematicDisplay no longer renders player HUD facts from the typed current-entity document: " +
|
|
string.Join(", ", missingSchematicHudSymbols));
|
|
}
|
|
|
|
var forbiddenSchematicHudSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot>",
|
|
"CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument>",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeCurrentEntityDocument>",
|
|
".ReactiveCatalogSnapshot(\"unity-schematic-display\")",
|
|
".ReactivePlayerSettingsDocument(\"unity-schematic-display\")",
|
|
".ReactiveCurrentEntity(\"unity-schematic-display\")",
|
|
"_catalog?.Dispose()",
|
|
"_playerSettings?.Dispose()",
|
|
"_currentEntity?.Dispose()",
|
|
"_entity.OverrideShutdown",
|
|
"_entity.HeatsinksEnabled",
|
|
"_entity.Heatstroke",
|
|
"_entity.Hypothermia"
|
|
};
|
|
var presentForbiddenSchematicHudSymbols = forbiddenSchematicHudSymbols
|
|
.Where(symbol => schematicDisplay.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (presentForbiddenSchematicHudSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SchematicDisplay player HUD facts must not come from the Unity entity facade: " +
|
|
string.Join(", ", presentForbiddenSchematicHudSymbols));
|
|
}
|
|
|
|
if (!schematicDisplay.Contains("public void SetRenderSettings(AetheriaRuntimeDaemonRenderSettings renderSettings)", StringComparison.Ordinal) ||
|
|
!schematicDisplay.Contains("_renderSettings?.NormalizeThermalRisk(", StringComparison.Ordinal) ||
|
|
!gameplayBootShell.Contains("CockpitHudShell.SetRenderSettings(ZoneRenderer.RenderSettings)", StringComparison.Ordinal) ||
|
|
!gameplayBootShell.Contains("runtimeState?.DaemonFrame?.Latest()", StringComparison.Ordinal) ||
|
|
!gameplayBootShell.Contains("render/game-feel settings are daemon authority", StringComparison.Ordinal) ||
|
|
!cockpitHudShell.Contains("public sealed class AetheriaUnityCockpitHudShell", StringComparison.Ordinal) ||
|
|
!cockpitHudShell.Contains("public void SetRenderSettings(AetheriaRuntimeDaemonRenderSettings renderSettings)", StringComparison.Ordinal) ||
|
|
!cockpitHudShell.Contains("SchematicDisplay?.SetRenderSettings(renderSettings)", StringComparison.Ordinal) ||
|
|
!cockpitHudShell.Contains("TargetSchematicDisplay?.SetRenderSettings(renderSettings)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"HUD thermal presentation must flow through daemon-frame render settings, not Unity-local render tuning.");
|
|
}
|
|
if (File.Exists(renderSettingsBridgePath) ||
|
|
unityRenderPresentation.Contains("AetheriaUnityRenderSettingsBridge", StringComparison.Ordinal) ||
|
|
unityRenderPresentation.Contains("local Unity Settings fallback", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("TargetSpottedBlinkFrequency", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("TargetSpottedBlinkOffset", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity render/game-feel boot must not preserve the local render-settings bridge or fallback; daemon frame settings are authoritative.");
|
|
}
|
|
|
|
var requiredActionGameManagerRenderSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityCockpitHudShell",
|
|
"public sealed class AetheriaUnityGameplayBootShell",
|
|
"runtimeState?.DaemonFrame?.Latest()",
|
|
"CockpitHudShell.SetRenderSettings(ZoneRenderer.RenderSettings)",
|
|
"renderSettings.NormalizeDetectionProgress(",
|
|
"renderSettings.ResolveTargetSpottedFillEnabled(",
|
|
"renderSettings.NormalizeHeatstrokePost(",
|
|
"renderSettings.ResolveSevereHeatstrokePostWeight(",
|
|
"renderSettings.NormalizeTargetVisibilityFill(",
|
|
"renderSettings.NormalizeVisibilityToTargetFill(",
|
|
"renderSettings.NormalizeTargetStatusFill(",
|
|
"renderSettings.ResolveLockIndicatorNoiseAmplitude(",
|
|
"renderSettings.ResolveLockIndicatorNoiseFrequency(",
|
|
"renderSettings.ResolveLockSpinSpeed(",
|
|
"ZoneRenderer.RenderSettings.ResolveDefaultMinimapZoomIndex()",
|
|
"ZoneRenderer.RenderSettings.ResolveNextMinimapZoomIndex(_zoomLevelIndex)",
|
|
"ZoneRenderer.RenderSettings.ResolveMinimapDistance(_zoomLevelIndex)",
|
|
"ZoneRenderer.BodySettingsCollections = Settings.BodySettingsCollections;"
|
|
};
|
|
|
|
var missingActionGameManagerRenderSymbols = requiredActionGameManagerRenderSymbols
|
|
.Where(symbol => !unityRenderPresentation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingActionGameManagerRenderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity target/render presentation no longer consumes daemon-owned render settings: " +
|
|
string.Join(", ", missingActionGameManagerRenderSymbols));
|
|
}
|
|
|
|
var zoneRendererPresentationSettings = new Dictionary<string, string>
|
|
{
|
|
["ActionGameManager.Instance.Settings.PlanetSettings.AsteroidVerticalOffset"] = "ActionGameManager.Instance.Settings.PlanetSettings.AsteroidVerticalOffset",
|
|
["PlanetRotationSpeed"] = "Settings.PlanetRotationSpeed",
|
|
["Settings.PlanetSettings.ZoneDepthExponent"] = "Settings.PlanetSettings.ZoneDepthExponent",
|
|
["Settings.PlanetSettings.ZoneDepth + Settings.PlanetSettings.ZoneBoundaryFog"] = "Settings.PlanetSettings.ZoneDepth + Settings.PlanetSettings.ZoneBoundaryFog",
|
|
["AsteroidMeshCount"] = "Settings.AsteroidMeshCount",
|
|
["Settings.PlanetSettings.BodyRadius"] = "Settings.PlanetSettings.BodyRadius",
|
|
["Settings.PlanetSettings.LightRadius"] = "Settings.PlanetSettings.LightRadius",
|
|
["Settings.PlanetSettings.WaveFrequency"] = "Settings.PlanetSettings.WaveFrequency"
|
|
};
|
|
var survivingZoneRendererPresentationSettings = zoneRendererPresentationSettings
|
|
.Where(symbol =>
|
|
symbol.Key == "PlanetRotationSpeed" || symbol.Key == "AsteroidMeshCount"
|
|
? ContainsUnitySettingsMember(zoneRenderer, symbol.Key)
|
|
: zoneRenderer.Contains(symbol.Key, StringComparison.Ordinal))
|
|
.Select(symbol => symbol.Value)
|
|
.ToArray();
|
|
if (survivingZoneRendererPresentationSettings.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer still reads zone presentation tuning from Unity GameSettings instead of shared daemon render settings: " +
|
|
string.Join(", ", survivingZoneRendererPresentationSettings));
|
|
}
|
|
|
|
var forbiddenRenderLoopSymbols = new[]
|
|
{
|
|
"Settings.GameplaySettings.TargetDetectionInfoThreshold",
|
|
"Settings.GameplaySettings.SevereHeatstrokeRiskThreshold",
|
|
"Settings.GameplaySettings.LockIndicatorNoiseAmplitude",
|
|
"Settings.GameplaySettings.LockIndicatorFrequency",
|
|
"Settings.GameplaySettings.LockSpinSpeed",
|
|
"Settings.HeatstrokePhasingFloor",
|
|
"Settings.HeatstrokePhasingFrequency",
|
|
"TargetSpottedBlinkFrequency",
|
|
"TargetSpottedBlinkOffset",
|
|
"Mathf.Lerp(.25f, .75f,"
|
|
};
|
|
var renderLoopHits = FindMethodScopedLineHits(actionGameManager, forbiddenRenderLoopSymbols)
|
|
.Where(hit => hit.MethodName is "Update" or "UpdateTargetIndicators")
|
|
.ToArray();
|
|
if (renderLoopHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager render loops must interpret observed state through shared daemon render settings instead of Unity GameSettings: " +
|
|
string.Join(", ", renderLoopHits.Select(hit => $"{hit.MethodName}:{hit.LineNumber}:{hit.Line.Trim()}")));
|
|
}
|
|
|
|
var minimapBootHits = FindMethodScopedLineHits(
|
|
actionGameManager,
|
|
new[] { "Settings.MinimapZoomLevels", "Settings.DefaultMinimapZoom" })
|
|
.Where(hit => hit.MethodName == "Start")
|
|
.ToArray();
|
|
if (minimapBootHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must bootstrap minimap zoom through shared daemon render settings instead of indexing Unity GameSettings directly: " +
|
|
string.Join(", ", minimapBootHits.Select(hit => $"{hit.MethodName}:{hit.LineNumber}:{hit.Line.Trim()}")));
|
|
}
|
|
}
|
|
|
|
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);",
|
|
"public Guid ID = Guid.NewGuid();",
|
|
"public Guid Orbit;",
|
|
"public Guid Parent;",
|
|
"data.OrbitKey = Zone.OrbitKey(data.ID);",
|
|
"planetData.BodyKey = Zone.BodyKey(planetData.ID);",
|
|
"lagrangeOrbit.OrbitKey = Zone.OrbitKey(lagrangeOrbit.ID);",
|
|
"turretOrbit.OrbitKey = Zone.OrbitKey(turretOrbit.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[]
|
|
{
|
|
"OrbitKey = Zone.OrbitKey(Guid.NewGuid()),",
|
|
"data.ParentOrbitKey = orbitInverseMap[data].Parent != null",
|
|
"planetData.BodyKey = Zone.BodyKey(Guid.NewGuid());",
|
|
"planetData.OrbitKey = orbitMap[planet].OrbitKey;",
|
|
"planetData.Name = bodyName.Substring(0, Math.Min(8, bodyName.Length));",
|
|
"var bodyName = planetData.BodyKey.Substring(planetData.BodyKey.LastIndexOf(':') + 1);",
|
|
"OrbitKey = Zone.OrbitKey(Guid.NewGuid()),",
|
|
"ParentOrbitKey = baseOrbit.ParentOrbitKey,",
|
|
"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, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.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, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs")] = new[]
|
|
{
|
|
"public sealed class AetheriaRuntimeZoneSnapshotCommit",
|
|
"public sealed class AetheriaRuntimeOrbitSnapshotCommit",
|
|
"public sealed class AetheriaRuntimeBodySnapshotCommit",
|
|
"public string OrbitKey { get; set; } = \"\";",
|
|
"public string ParentOrbitKey { get; set; } = \"\";",
|
|
"public string BodyKey { get; set; } = \"\";",
|
|
"public double GravityInfluenceCenterX { get; set; }",
|
|
"public double GravityInfluenceCenterZ { get; set; }",
|
|
"public double GravityInfluenceRadius { get; set; }",
|
|
"public double GravityWellDepth { get; set; }",
|
|
"public double GravityWaveRadius { get; set; }",
|
|
"public double GravityWaveDepth { get; set; }",
|
|
"public double GravityWaveSpeed { get; set; }",
|
|
"public double GravityTerrainRadius { get; set; }",
|
|
"public double GravityTerrainDepth { get; set; }",
|
|
"public double GravityTerrainDepthExponent { get; set; }",
|
|
"public double GravityTerrainBoundaryFog { get; set; }",
|
|
"public double GravityTerrainWaveFrequency { get; set; }"
|
|
}
|
|
};
|
|
|
|
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 managed runtime snapshot commits 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 RequireTypedRuntimeBehaviorCoverage(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var observedEntityRestorerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedEntityRestorer.cs");
|
|
var behaviorStateProjectorPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeBehaviorStateProjector.cs");
|
|
var daemonProgramPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs");
|
|
var unityProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj");
|
|
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify typed runtime behavior coverage; ActionGameManager.cs is missing.");
|
|
var observedEntityRestorer = File.Exists(observedEntityRestorerPath)
|
|
? File.ReadAllText(observedEntityRestorerPath)
|
|
: throw new InvalidOperationException("Cannot verify typed runtime behavior coverage; AetheriaUnityObservedEntityRestorer.cs is missing.");
|
|
var behaviorStateProjector = File.Exists(behaviorStateProjectorPath)
|
|
? File.ReadAllText(behaviorStateProjectorPath)
|
|
: throw new InvalidOperationException("Cannot verify typed runtime behavior coverage; behavior-state projector is missing.");
|
|
var daemonProgram = File.Exists(daemonProgramPath)
|
|
? File.ReadAllText(daemonProgramPath)
|
|
: throw new InvalidOperationException("Cannot verify typed runtime behavior coverage; daemon program is missing.");
|
|
var unityProject = File.Exists(unityProjectPath)
|
|
? File.ReadAllText(unityProjectPath)
|
|
: throw new InvalidOperationException("Cannot verify typed runtime behavior coverage; Unity project is missing.");
|
|
var runtimeRestoreSource = actionGameManager + "\n" + observedEntityRestorer;
|
|
|
|
var requiredBehaviorStateProjection = new[]
|
|
{
|
|
"public static class AetheriaRuntimeBehaviorStateProjector",
|
|
"public static void EnsureEquipmentBehaviorStates(",
|
|
"public static AetheriaRuntimeBehaviorStateCommit[] CreateEquipmentBehaviorStates(",
|
|
"AetheriaRuntimeCatalogSnapshot? catalog",
|
|
"catalog.FindItem(item.ItemKey ?? \"\")",
|
|
"typedItem?.BehaviorPayloads",
|
|
"OwnerKind = EquipmentOwnerKind",
|
|
"BehaviorKind = payload.Kind"
|
|
};
|
|
|
|
var missingBehaviorStateProjection = requiredBehaviorStateProjection
|
|
.Where(symbol => !behaviorStateProjector.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingBehaviorStateProjection.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed runtime behavior authority must derive daemon behavior-state rows from catalog payloads instead of Unity behavior objects: " +
|
|
string.Join(", ", missingBehaviorStateProjection));
|
|
}
|
|
|
|
if (!daemonProgram.Contains("node.RuntimeCatalog().Latest() ?? new AetheriaRuntimeCatalogSnapshot()", StringComparison.Ordinal) ||
|
|
!daemonProgram.Contains("AetheriaRuntimeBehaviorStateProjector.CreateEquipmentBehaviorStates(equipment, catalog)", StringComparison.Ordinal) ||
|
|
!daemonProgram.Contains("AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(runtimeEntity, catalog)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon runtime checkpoint projection must publish typed behavior-state rows from managed catalog payloads.");
|
|
}
|
|
|
|
if (!unityProject.Contains("AetheriaRuntimeBehaviorStateProjector.cs", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity-compatible shared runtime builds must include the typed behavior-state projector.");
|
|
}
|
|
|
|
var requiredWeaponStateCoverage = new[]
|
|
{
|
|
"if (weapon is LockWeapon lockWeapon)",
|
|
"else if (weapon is ChargedWeapon chargedWeapon)",
|
|
"else if (weapon is ConstantWeapon constantWeapon)",
|
|
"else if (weapon is InstantWeapon instantWeapon)"
|
|
};
|
|
|
|
var missingWeaponStateCoverage = requiredWeaponStateCoverage
|
|
.Where(symbol => !runtimeRestoreSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingWeaponStateCoverage.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed weapon snapshot coverage no longer restores the live articulated weapon families: " +
|
|
string.Join(", ", missingWeaponStateCoverage));
|
|
}
|
|
|
|
var requiredBehaviorStateCoverage = new[]
|
|
{
|
|
"case Sensor sensor:",
|
|
"case Radiator radiator:",
|
|
"case Reactor reactor:",
|
|
"case Capacitor capacitor:",
|
|
"case AetherDrive drive:",
|
|
"case ResourceScanner resourceScanner:",
|
|
"case MiningTool miningTool:",
|
|
"case Thruster thruster:",
|
|
"case Shield shield:",
|
|
"case VelocityLimit velocityLimit:",
|
|
"case Thermotoggle thermotoggle:",
|
|
"case Switch switchBehavior:",
|
|
"case Trigger trigger:",
|
|
"case StatModifier statModifier:",
|
|
"case TurretController turretController:"
|
|
};
|
|
|
|
var missingBehaviorStateCoverage = requiredBehaviorStateCoverage
|
|
.Where(symbol => !runtimeRestoreSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingBehaviorStateCoverage.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed runtime behavior snapshot coverage no longer owns the mutable non-weapon behavior families: " +
|
|
string.Join(", ", missingBehaviorStateCoverage));
|
|
}
|
|
}
|
|
|
|
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");
|
|
var daemonSurfaceCommandsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeDaemonSurfaceCommands.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.");
|
|
}
|
|
|
|
if (!File.Exists(daemonSurfaceCommandsPath))
|
|
{
|
|
throw new InvalidOperationException("Aetheria daemon surface command router is missing.");
|
|
}
|
|
|
|
var bootstrap = File.ReadAllText(bootstrapPath);
|
|
if (!bootstrap.Contains("RuntimeInitializeOnLoadMethod", StringComparison.Ordinal) ||
|
|
!bootstrap.Contains("DefaultSurfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId", StringComparison.Ordinal) ||
|
|
!bootstrap.Contains("AetheriaEveSurfacePresenter", StringComparison.Ordinal) ||
|
|
!bootstrap.Contains("UIDocument", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve runtime bootstrap no longer mounts the daemon-published game surface through the UI Toolkit presenter.");
|
|
}
|
|
|
|
var presenter = File.ReadAllText(presenterPath);
|
|
var daemonSurfaceCommands = File.ReadAllText(daemonSurfaceCommandsPath);
|
|
var requiredPresenterSymbols = new[]
|
|
{
|
|
"private string surfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId",
|
|
"ReadDaemonSurface(stateBoot)",
|
|
".EveSurfaceDocument(surfaceId)",
|
|
"?.Reactive()",
|
|
"AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver(",
|
|
"CultMeshReactiveDocument<EveSurfaceDocument>",
|
|
"ResolveReactiveDaemonSurfaceState(",
|
|
"DisposeReactiveSurfaceState()",
|
|
"AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(",
|
|
"private bool ShouldMountSurface(",
|
|
"_mountedSurfaceVersion != surface.Version",
|
|
"private static readonly AetheriaEveUnitySurfaceChrome RootOnlyChrome",
|
|
"UseShell = false",
|
|
"MountSurface(AetheriaRuntimeStateBootReport stateBoot, EveSurfaceDocument surface)",
|
|
"AetheriaEveUnitySurfaceHost.Render(",
|
|
"control.TrySubmitSurfaceCommand(request, out var daemonEnvelope)",
|
|
".SurfaceCommandAsync("
|
|
};
|
|
var missingPresenterSymbols = requiredPresenterSymbols
|
|
.Where(symbol => !presenter.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingPresenterSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve presenter no longer lowers refreshed daemon/CultMesh surfaces through typed Eve commands: " +
|
|
string.Join(", ", missingPresenterSymbols));
|
|
}
|
|
|
|
if (presenter.Contains("AetheriaRuntimeStateReader.ReadEveSurface", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve presenter still mounts daemon surfaces through file-reader lookup instead of the shared AetheriaClient facade.");
|
|
}
|
|
|
|
if (presenter.Contains(".DaemonGameSurfaceAsync()", StringComparison.Ordinal) ||
|
|
presenter.Contains(".DaemonGameTuiSurfaceAsync()", StringComparison.Ordinal) ||
|
|
presenter.Contains(".DaemonEditorSurfaceAsync()", StringComparison.Ordinal) ||
|
|
presenter.Contains(".DaemonEditorTuiSurfaceAsync()", StringComparison.Ordinal) ||
|
|
presenter.Contains("client.State.Daemon.ReactiveGameSurface()", StringComparison.Ordinal) ||
|
|
presenter.Contains("client.State.Daemon.ReactiveGameTuiSurface()", StringComparison.Ordinal) ||
|
|
presenter.Contains("client.State.Daemon.ReactiveEditorSurface()", StringComparison.Ordinal) ||
|
|
presenter.Contains("client.State.Daemon.ReactiveEditorTuiSurface()", StringComparison.Ordinal) ||
|
|
presenter.Contains("client.State.Daemon.GameSurface.LatestAsync()", StringComparison.Ordinal) ||
|
|
presenter.Contains("client.State.Daemon.GameTuiSurface.LatestAsync()", StringComparison.Ordinal) ||
|
|
presenter.Contains("client.State.Daemon.EditorSurface.LatestAsync()", StringComparison.Ordinal) ||
|
|
presenter.Contains("client.State.Daemon.EditorTuiSurface.LatestAsync()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve presenter still reads daemon surfaces through one-shot compatibility helpers instead of managed reactive document handles.");
|
|
}
|
|
|
|
if (presenter.Contains("new EveUiToolkitSurfaceLowerer", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve presenter still lowers daemon-published surfaces directly instead of delegating to the shared Unity Eve surface host.");
|
|
}
|
|
|
|
if (presenter.Contains("ResolveClient(", StringComparison.Ordinal) ||
|
|
presenter.Contains("AetheriaRuntimeDaemonSurfaceCommands.TrySubmit(", StringComparison.Ordinal) ||
|
|
presenter.Contains("AetheriaClientEveSurface.", StringComparison.Ordinal) ||
|
|
presenter.Contains("client.State.", StringComparison.Ordinal) ||
|
|
presenter.Contains("client.Ui.", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve presenter must use provider-native typed state/control/UI access instead of raw client plumbing.");
|
|
}
|
|
|
|
var daemonSubmitIndex = presenter.IndexOf(
|
|
"control.TrySubmitSurfaceCommand(request, out var daemonEnvelope)",
|
|
StringComparison.Ordinal);
|
|
var eveSubmitIndex = presenter.IndexOf(
|
|
".SurfaceCommandAsync(",
|
|
StringComparison.Ordinal);
|
|
if (daemonSubmitIndex < 0 || eveSubmitIndex < 0 || daemonSubmitIndex > eveSubmitIndex)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve presenter must route daemon-published surface commands to the typed daemon boundary before falling back to Eve requests.");
|
|
}
|
|
|
|
var requiredDaemonSurfaceCommandSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeDaemonSurfaceCommands",
|
|
"EveSurfaceCommandRequest request",
|
|
"AetheriaClient",
|
|
"client.Control.TrySubmitSurfaceCommand(request, out envelope)"
|
|
};
|
|
var missingDaemonSurfaceCommandSymbols = requiredDaemonSurfaceCommandSymbols
|
|
.Where(symbol => !daemonSurfaceCommands.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonSurfaceCommandSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surface command routing no longer submits typed daemon operations: " +
|
|
string.Join(", ", missingDaemonSurfaceCommandSymbols));
|
|
}
|
|
|
|
if (daemonSurfaceCommands.Contains("AetheriaRuntimeStateReader.TryReadDaemonRenderView", StringComparison.Ordinal) ||
|
|
daemonSurfaceCommands.Contains("TryReactiveDaemonSoaView(client)", StringComparison.Ordinal) ||
|
|
daemonSurfaceCommands.Contains("client.CurrentObservedDaemon()", StringComparison.Ordinal) ||
|
|
daemonSurfaceCommands.Contains("client.State.CurrentObservedDaemon()", StringComparison.Ordinal) ||
|
|
daemonSurfaceCommands.Contains("client.State.ReactiveObservedDaemon()", StringComparison.Ordinal) ||
|
|
daemonSurfaceCommands.Contains("observedState.TryCurrent(out var current)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surface command routing still reads observed daemon state through an aggregate reader instead of direct managed typed documents.");
|
|
}
|
|
|
|
if (daemonSurfaceCommands.Contains(".ReadAsync(client.State)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surface command routing still samples observed daemon state through a one-shot compatibility helper instead of a managed reactive document.");
|
|
}
|
|
|
|
if (daemonSurfaceCommands.Contains("ObserveAsync()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surface command routing still reads observed daemon state through the AetheriaClient compatibility helper.");
|
|
}
|
|
|
|
if (daemonSurfaceCommands.Contains(".OpenAsync(", StringComparison.Ordinal) ||
|
|
daemonSurfaceCommands.Contains("string stateFilePath", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surface command routing still hides the managed client boundary behind a state-file-path shortcut.");
|
|
}
|
|
|
|
var forbiddenDaemonSurfaceCommandSymbols = new[]
|
|
{
|
|
"request.Payload.TryGetValue",
|
|
"ApplyPayload(",
|
|
"ReadInt(",
|
|
"ReadDouble(",
|
|
"ReadString(",
|
|
"\"commandKind\""
|
|
};
|
|
var daemonSurfaceCommandHits = forbiddenDaemonSurfaceCommandSymbols
|
|
.Where(symbol => daemonSurfaceCommands.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (daemonSurfaceCommandHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surface command routing must lower command ids to typed daemon documents, not decode string payload maps: " +
|
|
string.Join(", ", daemonSurfaceCommandHits));
|
|
}
|
|
|
|
if (daemonSurfaceCommands.Contains("AetheriaRuntimeCommandSubmitter.TrySubmitDaemonCommand(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surface command routing still calls the generic daemon submitter instead of the typed daemon operation client.");
|
|
}
|
|
|
|
if (daemonSurfaceCommands.Contains("TrySendCommandKind(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surface command routing still uses a generic command-kind submission back door instead of explicit typed operations.");
|
|
}
|
|
}
|
|
|
|
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 RequireMainMenuSettingsCommands(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 command 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 forbiddenSymbols = new[]
|
|
{
|
|
"ActionGameManager.RuntimePlayerSettings.Name =",
|
|
"ActionGameManager.RuntimePlayerSettings.GameplaySettings.TemperatureUnit =",
|
|
"ActionGameManager.RuntimePlayerSettings.GameplaySettings.SignificantDigits =",
|
|
"ActionGameManager.RuntimePlayerSettings.GraphicsSettings.NebulaQuality =",
|
|
"ActionGameManager.RuntimePlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap =",
|
|
"ActionGameManager.RequestRuntimePlayerSettingsCommand",
|
|
"ActionGameManager.RequestRuntimePlayerName",
|
|
"ActionGameManager.RequestRuntimeTemperatureUnit",
|
|
"ActionGameManager.RequestRuntimeSignificantDigits",
|
|
"ActionGameManager.RequestRuntimeNebulaQuality",
|
|
"ActionGameManager.RequestRuntimeShowAsteroidsInMinimap"
|
|
};
|
|
|
|
var actionGameManagerForbiddenSymbols = new[]
|
|
{
|
|
"public static RuntimePlayerSettings RuntimePlayerSettings",
|
|
"RequestRuntimePlayerSettingsCommand",
|
|
"RequestRuntimePlayerName",
|
|
"RequestRuntimeTemperatureUnit",
|
|
"RequestRuntimeSignificantDigits",
|
|
"RequestRuntimeNebulaQuality",
|
|
"RequestRuntimeShowAsteroidsInMinimap"
|
|
};
|
|
|
|
var hits = File.ReadLines(mainMenuPath)
|
|
.Select((line, index) => new { Path = mainMenuPath, LineNumber = index + 1, Line = line })
|
|
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Concat(File.ReadLines(actionGameManagerPath)
|
|
.Select((line, index) => new { Path = actionGameManagerPath, LineNumber = index + 1, Line = line })
|
|
.Where(line => actionGameManagerForbiddenSymbols.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 still owns direct player-settings mutation instead of sending Eve commands: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var requiredMainMenuAuthoritySymbols = new[]
|
|
{
|
|
"SendKnownAetheriaEveCommand(request, \"player-settings\")",
|
|
"AetheriaRuntimeMainMenuCommandKind.PlayerSettingsCommand",
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
".Ui(stateBoot, \"unity-main-menu\")",
|
|
".SurfaceCommandAsync(request, \"unity-main-menu\")",
|
|
"stateBoot.StateFilePath",
|
|
"\"unity-main-menu\""
|
|
};
|
|
|
|
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 the Aetheria Eve command boundary: " +
|
|
string.Join(", ", missingUiCalls));
|
|
}
|
|
|
|
var forbiddenSubmitAcceptanceSymbols = new[]
|
|
{
|
|
"private static bool TrySendKnownAetheriaEveCommand(",
|
|
"if (!TrySendKnownAetheriaEveCommand(request, \"player-settings\"))"
|
|
};
|
|
var submitAcceptanceHits = forbiddenSubmitAcceptanceSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (submitAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu player-settings command lowering still treats Eve submission as local acceptance state: " +
|
|
string.Join(", ", submitAcceptanceHits));
|
|
}
|
|
|
|
if (source.Contains("TrySendPlayerSettingsCommand(", StringComparison.Ordinal) ||
|
|
source.Contains("CommandKindForSurface(request)", StringComparison.Ordinal) ||
|
|
source.Contains("new AetheriaRuntimePlayerSettingsCommandBody", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still decodes player-settings Eve payloads locally instead of delegating to the typed command client.");
|
|
}
|
|
|
|
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[]
|
|
{
|
|
"ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId)",
|
|
".RuntimeState(stateBoot, \"unity-main-menu\")",
|
|
".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaRuntimeMainMenuCommandKind.PlayerSettingsCommand",
|
|
"SendKnownAetheriaEveCommand(request, \"player-settings\")"
|
|
};
|
|
|
|
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.RequestRuntimePlayerName(evt.newValue)",
|
|
"TryQueuePlayerSettingsCommand(",
|
|
"TryQueueAetheriaEveCommand(",
|
|
"AetheriaRuntimeEveCommandLog.QueueCommand(",
|
|
"BuildPlayerSettingsSurfaceDefinition()",
|
|
"ProjectPlayerSettingsSurfaceState(",
|
|
"new AetheriaRuntimePlayerSettingsSurfaceState(",
|
|
"ResolveMenuSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer",
|
|
"host.AddComponent<UIDocument>",
|
|
"new EveSurfaceCommandRequest("
|
|
};
|
|
|
|
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 RequireMainMenuSettingsShellUsesEveSurface(string root)
|
|
{
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
var mainMenuSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeMainMenuSurfaceBuilder.cs");
|
|
if (!File.Exists(mainMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify main-menu settings shell; MainMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(mainMenuSurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify main-menu settings shell; AetheriaRuntimeMainMenuSurfaceBuilder.cs is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(mainMenuPath);
|
|
var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RenderMenuSurface(",
|
|
"ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.SettingsSurfaceId)",
|
|
"ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId)",
|
|
"ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId)",
|
|
"ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId)",
|
|
".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)",
|
|
"HandleSettingsSurfaceCommand(",
|
|
"HandleVerseSettingsSurfaceCommand(",
|
|
"HandleInputSettingsSurfaceCommand(",
|
|
"AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeMainMenuCommandKind.ShowPlayerSettings",
|
|
"AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings",
|
|
"AetheriaRuntimeMainMenuCommandKind.ShowInputSettings",
|
|
"AetheriaRuntimeMainMenuCommandKind.BackToMain",
|
|
"AetheriaRuntimeMainMenuCommandKind.BackToSettings",
|
|
"AetheriaRuntimeMainMenuCommandKind.OpenRuntimeInputScreen",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_menuSurfaceDocument)"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu no longer lowers the settings shell through Eve surfaces: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"BuildSettingsSurfaceDefinition(",
|
|
"BuildInputSettingsSurfaceDefinition(",
|
|
"BuildPlayerSettingsSurfaceDefinition(",
|
|
"BuildVerseSettingsSurfaceDefinition(",
|
|
"private static EveSurfaceComponent Button(",
|
|
"private static EveSurfaceComponent Card(",
|
|
"_nextMenu.panel.AddButton(\"Player Settings\"",
|
|
"_nextMenu.panel.AddButton(\"Verse\"",
|
|
"_nextMenu.panel.AddButton(\"Input\"",
|
|
"_nextMenu.panel.AddButton(\"Audio\"",
|
|
"BuildAudioSettingsSurfaceDefinition(",
|
|
"HandleAudioSettingsSurfaceCommand(",
|
|
"ShowAudioSettingsCommand",
|
|
"ResolveMenuSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer",
|
|
"host.AddComponent<UIDocument>",
|
|
"switch (request.Command)",
|
|
"string.Equals(request.Command, AetheriaRuntimeMainMenuCommands.BackToSettings",
|
|
"string.Equals(request.Command, AetheriaRuntimeMainMenuCommands.OpenRuntimeInputScreen",
|
|
"TrySendVerseHostCommand(request.Command)",
|
|
"CommandKindForSurface(request)",
|
|
"AetheriaRuntimeClientTargetCommands.IsKnown(request.Command",
|
|
"ProjectPlayerSettingsSurfaceState(",
|
|
"ProjectVerseSettingsSurfaceState(",
|
|
"ProjectMainMenuSurfaceState(",
|
|
"new AetheriaRuntimePlayerSettingsSurfaceState(",
|
|
"new AetheriaRuntimeClientTargetSurfaceState(",
|
|
"_nextMenu.panel.Title.text = \"settings\"",
|
|
"_nextMenu.panel.Title.text = TitleSubtitle(\"input\", \"settings\")",
|
|
"_nextMenu.panel.Title.text = TitleSubtitle(\"audio\", \"settings\")"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still keeps a fake audio/settings shell or old PropertiesPanel settings shell alive: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeMainMenuSurfaceBuilder",
|
|
"public static class AetheriaRuntimeMainMenuCommands",
|
|
"BuildSettings(",
|
|
"BuildInputSettings(",
|
|
"BuildPlayerSettings(",
|
|
"BuildVerseSettings(",
|
|
"private static AetheriaRuntimeSurfaceDocument BuildRoot(",
|
|
"private static AetheriaRuntimeSurfaceDocument BuildInputSettings(",
|
|
"\"STARBRIDGE\"",
|
|
"AetheriaRuntimePlayerSettingsSurfaceBuilder.Build(playerSettings, updatedAtUtc, version)",
|
|
"AetheriaRuntimeSurfaceDocument document",
|
|
"WithBackAction(",
|
|
"AetheriaRuntimeMainMenuCommands.BackToSettings",
|
|
"public enum AetheriaRuntimeMainMenuCommandKind",
|
|
"public readonly struct AetheriaRuntimeMainMenuCommand",
|
|
"public static class AetheriaRuntimeMainMenuSurfaceCommands",
|
|
"public static bool TryRead(",
|
|
"AetheriaRuntimeMainMenuCommands.ShowPlayerSettings",
|
|
"AetheriaRuntimeMainMenuCommands.ShowVerseSettings",
|
|
"AetheriaRuntimeMainMenuCommands.ShowInputSettings",
|
|
"AetheriaRuntimeMainMenuCommands.BackToSettings"
|
|
};
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !mainMenuSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared main-menu surface builder no longer owns the settings/input shell contract: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
|
|
if (mainMenuSurfaceBuilder.Contains("AetheriaRuntimeMainMenuSurfaceState", StringComparison.Ordinal) ||
|
|
mainMenuSurfaceBuilder.Contains("ComposeRootState(", StringComparison.Ordinal) ||
|
|
mainMenuSurfaceBuilder.Contains("ComposePlayerSettingsState(", StringComparison.Ordinal) ||
|
|
mainMenuSurfaceBuilder.Contains("new AetheriaRuntimePlayerSettingsSurfaceState(", StringComparison.Ordinal) ||
|
|
mainMenuSurfaceBuilder.Contains("public static AetheriaRuntimeMainMenuSurfaceState Project", StringComparison.Ordinal) ||
|
|
mainMenuSurfaceBuilder.Contains("public static AetheriaRuntimePlayerSettingsSurfaceState Project", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared main-menu surface builder must accept typed documents and return finished Eve documents; do not rebuild a shadow main-menu SurfaceState projection layer.");
|
|
}
|
|
}
|
|
|
|
static void RequireMainMenuRootUsesEveSurface(string root)
|
|
{
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
var mainMenuSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeMainMenuSurfaceBuilder.cs");
|
|
if (!File.Exists(mainMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify main-menu root shell; MainMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(mainMenuSurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify main-menu root shell; AetheriaRuntimeMainMenuSurfaceBuilder.cs is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(mainMenuPath);
|
|
var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.RootSurfaceId)",
|
|
".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)",
|
|
"AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeMainMenuCommandKind.ContinueRun",
|
|
"AetheriaRuntimeMainMenuCommandKind.NewGame",
|
|
"AetheriaRuntimeMainMenuCommandKind.ShowSettings",
|
|
"AetheriaRuntimeMainMenuCommandKind.Quit",
|
|
"HandleMainSurfaceCommand(",
|
|
"ResolveSectorMap(stateBoot)",
|
|
"HideMenuSurface();"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu no longer lowers the root shell through Eve surfaces: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
if (!mainMenuSurfaceBuilder.Contains("AetheriaRuntimeMainMenuSurfaceBuilder", StringComparison.Ordinal) ||
|
|
!mainMenuSurfaceBuilder.Contains("public static AetheriaRuntimeSurfaceDocument BuildRoot(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime main-menu surface builder no longer owns the root shell composition.");
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"PanelPrototype",
|
|
"FadeTime",
|
|
"FadeDistance",
|
|
"FadeAlphaExponent",
|
|
"FadePositionExponent",
|
|
"_currentMenu",
|
|
"_nextMenu",
|
|
"_fadeFromRight",
|
|
"_fadeLerp",
|
|
"_fading",
|
|
"_panelPosition",
|
|
"TitleSubtitle(",
|
|
"IsMenuSurfaceVisible(",
|
|
"_nextMenu.panel.AddButton(\"Continue\"",
|
|
"_nextMenu.panel.AddButton(\"New Game\"",
|
|
"_nextMenu.panel.AddButton(\"Settings\"",
|
|
"_nextMenu.panel.AddButton(\"Quit\"",
|
|
"BuildMainSurfaceDefinition(",
|
|
"private static EveSurfaceDocument BuildMenuSurfaceDocument(",
|
|
"switch (request.Command)",
|
|
"Fade(true)",
|
|
"Fade(false)"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still owns root navigation through the legacy PropertiesPanel/fade shell: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var prefabPath = Path.Combine(root, "Assets", "Prefabs", "UI", "Main Menu Canvas.prefab");
|
|
if (!File.Exists(prefabPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify main-menu prefab shell; Main Menu Canvas.prefab is missing.");
|
|
}
|
|
|
|
var prefab = File.ReadAllText(prefabPath);
|
|
var forbiddenPrefabSymbols = new[]
|
|
{
|
|
"PanelPrototype:",
|
|
"FadeTime:",
|
|
"FadeDistance:",
|
|
"FadeAlphaExponent:",
|
|
"FadePositionExponent:"
|
|
};
|
|
|
|
var prefabHits = forbiddenPrefabSymbols
|
|
.Where(symbol => prefab.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (prefabHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Main-menu prefab still serializes the dead root shell: " +
|
|
string.Join(", ", prefabHits));
|
|
}
|
|
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"BuildRoot(",
|
|
"AetheriaRuntimeMainMenuCommands.RootSurfaceId",
|
|
"AetheriaRuntimeMainMenuCommands.ContinueRun",
|
|
"AetheriaRuntimeMainMenuCommands.NewGame",
|
|
"AetheriaRuntimeMainMenuCommands.ShowSettings",
|
|
"AetheriaRuntimeMainMenuCommands.Quit",
|
|
"private static AetheriaRuntimeSurfaceDocument BuildRoot(",
|
|
"\"STARBRIDGE\"",
|
|
"\"Aetheria Starbridge\"",
|
|
"GravitySurface($",
|
|
"public enum AetheriaRuntimeMainMenuCommandKind",
|
|
"public readonly struct AetheriaRuntimeMainMenuCommand",
|
|
"public static class AetheriaRuntimeMainMenuSurfaceCommands",
|
|
"public static bool TryRead("
|
|
};
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !mainMenuSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared main-menu surface builder no longer owns the root shell contract: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireConfirmationDialogOwnsMinimalPromptShell(string root)
|
|
{
|
|
var dialogPath = Path.Combine(root, "Assets", "Scripts", "UI", "ConfirmationDialog.cs");
|
|
if (!File.Exists(dialogPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify confirmation dialog shell; ConfirmationDialog.cs is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(dialogPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"public class ConfirmationDialog : MonoBehaviour",
|
|
"public TextMeshProUGUI Title;",
|
|
"public RectTransform Content;",
|
|
"public Property Property;",
|
|
"public InputField InputField;",
|
|
"public void Clear()",
|
|
"public Property AddProperty(Func<string> read)",
|
|
"public void AddField(string name, Func<string> read, Action<string> write)",
|
|
"public void AddField(string name, Func<int> read, Action<int> write)",
|
|
"public void SetInputGate(Action enableGlobalInput, Action disableGlobalInput)"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ConfirmationDialog no longer owns the minimal prompt shell contract: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
": PropertiesPanel",
|
|
"OnPropertyAdded",
|
|
"RefreshPropertyValues",
|
|
"PropertyLabel",
|
|
"Dropdown",
|
|
"RangedFloatField",
|
|
"BoolField",
|
|
"EnumField",
|
|
"StatSheet",
|
|
"ActionGameManager.Instance"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ConfirmationDialog still drags generic PropertiesPanel machinery into the prompt shell: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var prefabBlock = ReadSerializedMonoBehaviourBlock(
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Main Menu Canvas.prefab"),
|
|
"83488dc4b5e58cd40b4d1c4ee98207b5");
|
|
var sceneBlock = ReadSerializedMonoBehaviourBlock(
|
|
Path.Combine(root, "Assets", "Scenes", "ARPG.unity"),
|
|
"83488dc4b5e58cd40b4d1c4ee98207b5");
|
|
|
|
var forbiddenSerializedFields = new[]
|
|
{
|
|
"Spacer:",
|
|
"Dropdown:",
|
|
"Section:",
|
|
"List:",
|
|
"PropertyLabel:",
|
|
"Attribute:",
|
|
"RangedFloatField:",
|
|
"ProgressField:",
|
|
"EnumField:",
|
|
"BoolField:",
|
|
"PropertyButton:",
|
|
"ButtonField:",
|
|
"IncrementField:",
|
|
"StatSheet:",
|
|
"CurveField:",
|
|
"SelectedChild:",
|
|
"GameManager:"
|
|
};
|
|
|
|
foreach (var (label, block) in new[] { ("Main Menu Canvas.prefab", prefabBlock), ("ARPG.unity", sceneBlock) })
|
|
{
|
|
var blockHits = forbiddenSerializedFields
|
|
.Where(symbol => block.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (blockHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"ConfirmationDialog still serializes dead PropertiesPanel fields in {label}: " +
|
|
string.Join(", ", blockHits));
|
|
}
|
|
}
|
|
}
|
|
|
|
static string ReadSerializedMonoBehaviourBlock(string path, string scriptGuid)
|
|
{
|
|
if (!File.Exists(path))
|
|
{
|
|
throw new InvalidOperationException($"Cannot inspect serialized MonoBehaviour block; file is missing: {path}");
|
|
}
|
|
|
|
var lines = File.ReadAllLines(path);
|
|
var scriptLine = $"guid: {scriptGuid}";
|
|
for (var index = 0; index < lines.Length; index++)
|
|
{
|
|
if (!lines[index].Contains(scriptLine, StringComparison.Ordinal))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var start = index;
|
|
while (start >= 0 && !lines[start].StartsWith("--- !u!114 ", StringComparison.Ordinal))
|
|
{
|
|
start--;
|
|
}
|
|
|
|
if (start < 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
var end = start + 1;
|
|
while (end < lines.Length && !lines[end].StartsWith("--- !u!", StringComparison.Ordinal))
|
|
{
|
|
end++;
|
|
}
|
|
|
|
return string.Join(Environment.NewLine, lines.Skip(start).Take(end - start));
|
|
}
|
|
|
|
throw new InvalidOperationException(
|
|
$"Cannot inspect serialized MonoBehaviour block for script guid {scriptGuid} in {path}.");
|
|
}
|
|
|
|
static void RequireMainMenuInputSettingsDelegateToRuntimeScreen(string root)
|
|
{
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
var mainMenuSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeMainMenuSurfaceBuilder.cs");
|
|
if (!File.Exists(mainMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify main-menu input delegation; MainMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(mainMenuSurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify main-menu input delegation; AetheriaRuntimeMainMenuSurfaceBuilder.cs is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(mainMenuPath);
|
|
var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeMainMenuCommandKind.OpenRuntimeInputScreen",
|
|
"SetRuntimeInputScreenShell(Func<bool> canOpenRuntimeInputScreen, Action openRuntimeInputScreen)",
|
|
"CanOpenRuntimeInputScreen()",
|
|
"TryOpenRuntimeInputScreen()",
|
|
"ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId)",
|
|
".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)",
|
|
"_canOpenRuntimeInputScreen?.Invoke() == true",
|
|
"_openRuntimeInputScreen?.Invoke();"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu input page no longer delegates to the live runtime remap screen: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"Typed input rebinding controls are not lowered through Eve yet.",
|
|
"The live remapping screen still owns drag/drop rebinding and low-level InputSystem edits.",
|
|
"ActionGameManager.Instance.ShowInputScreenFromMenu();",
|
|
"ActionGameManager.Instance.CanShowInputScreenFromMenu()"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu input page still reports the live remap owner as future work instead of delegating to it: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
if (!mainMenuSurfaceBuilder.Contains("AetheriaRuntimeMainMenuCommands.OpenRuntimeInputScreen", StringComparison.Ordinal) ||
|
|
!mainMenuSurfaceBuilder.Contains("bool canOpenRuntimeInputScreen", StringComparison.Ordinal) ||
|
|
!mainMenuSurfaceBuilder.Contains("if (canOpenRuntimeInputScreen)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared main-menu input surface no longer exposes the runtime input-screen handoff.");
|
|
}
|
|
}
|
|
|
|
static void RequireRuntimeInputScreenUsesEveSurface(string root)
|
|
{
|
|
var inputScreenPath = Path.Combine(root, "Assets", "Scripts", "UI", "InputScreen", "InputDisplayLayout.cs");
|
|
var commandsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeInputSettingsCommands.cs");
|
|
var builderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeInputSettingsSurfaceBuilder.cs");
|
|
|
|
if (!File.Exists(inputScreenPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify runtime input-screen Eve lowering; InputDisplayLayout.cs is missing.");
|
|
}
|
|
|
|
if (!File.Exists(commandsPath) || !File.Exists(builderPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot verify runtime input-screen Eve lowering; the shared input-settings Eve contract is missing.");
|
|
}
|
|
|
|
var inputScreen = File.ReadAllText(inputScreenPath);
|
|
var commands = File.ReadAllText(commandsPath);
|
|
var builder = File.ReadAllText(builderPath);
|
|
|
|
var requiredInputScreenSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeInputSettingsSurfaceBuilder.Build(",
|
|
"AetheriaRuntimeInputSettingsSurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeInputSettingsCommandKind.BeginCapture",
|
|
"AetheriaRuntimeInputSettingsCommandKind.ToggleActionBar",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_surfaceDocument)",
|
|
"UIDocument",
|
|
".Ui(RequireLocalStateBoot(), \"unity-input-screen\")",
|
|
".InputSettingsAsync(command, body, \"unity-input-screen\")",
|
|
"AetheriaRuntimeInputSettingsCommandBody",
|
|
"AetheriaRuntimeEveCommandKind.SetBindingOverride",
|
|
"AetheriaRuntimeEveCommandKind.SetActionBarEnabled",
|
|
"RequireLocalStateBoot()",
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(",
|
|
"_runtimeState.PlayerSettings.Latest()",
|
|
"action.ApplyBindingOverride",
|
|
"new InputAction(\"Aetheria Input Capture\")",
|
|
"AetheriaRuntimeInputSettingsSurfaceBuilder.IsSupportedCapturePath(",
|
|
"new AetheriaRuntimeObservedInputBinding(",
|
|
"HideLegacyChildren()"
|
|
};
|
|
|
|
var missingInputScreenSymbols = requiredInputScreenSymbols
|
|
.Where(symbol => !inputScreen.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingInputScreenSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime input screen no longer lowers through the Eve UI Toolkit surface contract: " +
|
|
string.Join(", ", missingInputScreenSymbols));
|
|
}
|
|
|
|
var forbiddenInputScreenSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument>",
|
|
".ReactivePlayerSettingsDocument(",
|
|
"AetheriaRuntimePlayerSettingsSession",
|
|
".ObservePlayer()",
|
|
"ClearClientCaches()",
|
|
"OnPointerClickAsObservable",
|
|
"BeginDragTrigger",
|
|
"EndDragTrigger",
|
|
"VerticalLayoutGroup",
|
|
"LayoutRebuilder",
|
|
"UILineRenderer",
|
|
"Observable.NextFrame()",
|
|
"DisplayLayout(_inputLayout)",
|
|
"EnsureSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer()",
|
|
"host.AddComponent<UIDocument>",
|
|
"AetheriaRuntimeStateCommitLog.QueuePlayerSettings",
|
|
"PlayerSettingsAsync()",
|
|
"VerseHostSettingsAsync()",
|
|
"ActionGameManager.CommitRuntimeInputBindingOverride",
|
|
"ActionGameManager.CommitRuntimeActionBarInput",
|
|
"BeginCapture(request.Payload)",
|
|
"ToggleActionBarInput(request.Payload)",
|
|
"private void BeginCapture(IReadOnlyDictionary<string, string> payload)",
|
|
"private void ToggleActionBarInput(IReadOnlyDictionary<string, string> payload)",
|
|
"request.Payload",
|
|
"private static readonly string[] DefaultActionBarCandidatePaths",
|
|
"private static bool IsSupportedCapturePath(string path)",
|
|
"ProjectSurfaceState(",
|
|
"AetheriaRuntimeInputSettingsSurfaceBuilder.Project(",
|
|
"new AetheriaRuntimeInputSettingsSurfaceState(",
|
|
"new AetheriaRuntimeInputBindingSurfaceState(",
|
|
"new AetheriaRuntimeActionBarInputSurfaceState(",
|
|
"public sealed class AetheriaRuntimeInputSettingsSurfaceState",
|
|
"public sealed class AetheriaRuntimeInputBindingSurfaceState",
|
|
"public sealed class AetheriaRuntimeActionBarInputSurfaceState",
|
|
"AetheriaRuntimeInputSettingsSurfaceBuilder.ProjectActionBarInputs(",
|
|
"AetheriaRuntimeInputSettingsSurfaceBuilder.ProjectActionBarCandidates(",
|
|
"new SortedDictionary<string, string>(StringComparer.Ordinal)",
|
|
"private IReadOnlyList<AetheriaRuntimeInputPathSurfaceLabel> ProjectActionBarCandidates("
|
|
};
|
|
|
|
var survivingLegacySymbols = forbiddenInputScreenSymbols
|
|
.Where(symbol => inputScreen.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (survivingLegacySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime input screen still carries the old drag/drop uGUI authority path: " +
|
|
string.Join(", ", survivingLegacySymbols));
|
|
}
|
|
|
|
if (!commands.Contains("SurfaceId = \"aetheria.input_settings\"", StringComparison.Ordinal) ||
|
|
!commands.Contains("BeginCapture", StringComparison.Ordinal) ||
|
|
!commands.Contains("ToggleActionBar", StringComparison.Ordinal) ||
|
|
!commands.Contains("SetBindingOverride", StringComparison.Ordinal) ||
|
|
!commands.Contains("SetActionBarEnabled", StringComparison.Ordinal) ||
|
|
!commands.Contains("public static bool IsKnown", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared input-settings command contract is incomplete.");
|
|
}
|
|
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaRuntimeInputPathSurfaceLabel",
|
|
"public sealed class AetheriaRuntimeObservedInputBinding",
|
|
"public static readonly IReadOnlyList<string> DefaultActionBarCandidatePaths",
|
|
"public static readonly IReadOnlyList<AetheriaRuntimeInputPathSurfaceLabel> DefaultActionBarCandidateInputPaths",
|
|
"public static bool IsSupportedCapturePath(string path)",
|
|
"public static AetheriaRuntimeSurfaceDocument Build(",
|
|
"private static AetheriaRuntimeSurfaceDocument Build(",
|
|
"private static IReadOnlyList<InputBindingRow> ComposeBindingInputs(",
|
|
"private static IReadOnlyList<AetheriaRuntimeInputPathSurfaceLabel> ComposeActionBarCandidates(",
|
|
"private static IReadOnlyList<ActionBarInputRow> ComposeActionBarInputs(",
|
|
"private sealed class InputBindingRow",
|
|
"private sealed class ActionBarInputRow",
|
|
"new SortedDictionary<string, string>(StringComparer.Ordinal)",
|
|
"public enum AetheriaRuntimeInputSettingsCommandKind",
|
|
"public readonly struct AetheriaRuntimeInputSettingsSurfaceCommand",
|
|
"public static class AetheriaRuntimeInputSettingsSurfaceCommands",
|
|
"public static bool TryRead(",
|
|
"AetheriaRuntimeInputSettingsCommands.BeginCapture",
|
|
"AetheriaRuntimeInputSettingsCommands.ToggleActionBar",
|
|
"ReadString(request, \"actionName\")",
|
|
"ReadInt(request, \"bindingIndex\", -1)",
|
|
"ReadString(request, \"inputPath\")",
|
|
"ReadBool(request, \"enabled\", false)",
|
|
"\"Low-level InputSystem edits flow through this Eve surface as typed input-setting requests.\""
|
|
};
|
|
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !builder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared input-settings Eve surface builder is incomplete: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
|
|
if (builder.Contains("AetheriaRuntimeInputSettingsSurfaceState", StringComparison.Ordinal) ||
|
|
builder.Contains("AetheriaRuntimeInputBindingSurfaceState", StringComparison.Ordinal) ||
|
|
builder.Contains("AetheriaRuntimeActionBarInputSurfaceState", StringComparison.Ordinal) ||
|
|
builder.Contains("ComposeState(", StringComparison.Ordinal) ||
|
|
builder.Contains("public static AetheriaRuntimeInputSettingsSurfaceState Project(", StringComparison.Ordinal) ||
|
|
builder.Contains("public static IReadOnlyList<AetheriaRuntimeInputBindingSurfaceState> ProjectBindingInputs(", StringComparison.Ordinal) ||
|
|
builder.Contains("public static IReadOnlyList<AetheriaRuntimeInputPathSurfaceLabel> ProjectActionBarCandidates(", StringComparison.Ordinal) ||
|
|
builder.Contains("public static IReadOnlyList<AetheriaRuntimeActionBarInputSurfaceState> ProjectActionBarInputs(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Input-settings surface builder must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer.");
|
|
}
|
|
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify runtime input-screen authority; ActionGameManager.cs is missing.");
|
|
|
|
var requiredInputSettingsSubmitSymbols = new[]
|
|
{
|
|
"SendInputSettingsCommand(",
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
".Ui(RequireLocalStateBoot(), \"unity-input-screen\")",
|
|
".InputSettingsAsync(",
|
|
"AetheriaRuntimeEveCommandKind.SetBindingOverride",
|
|
"AetheriaRuntimeEveCommandKind.SetActionBarEnabled"
|
|
};
|
|
|
|
var missingInputSettingsSubmitSymbols = requiredInputSettingsSubmitSymbols
|
|
.Where(symbol => !inputScreen.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingInputSettingsSubmitSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime input writes no longer send explicit Eve input-setting commands: " +
|
|
string.Join(", ", missingInputSettingsSubmitSymbols));
|
|
}
|
|
|
|
var forbiddenActionGameManagerSymbols = new[]
|
|
{
|
|
"RequestRuntimeInputBindingOverride",
|
|
"RequestRuntimeActionBarInput",
|
|
"SendRuntimeInputSettingsCommand(",
|
|
"public static void CommitRuntimeInputBindingOverride",
|
|
"public static void CommitRuntimeActionBarInput",
|
|
"TrySendRuntimeInputSettingsCommand(",
|
|
"private static bool TrySendRuntimeInputSettingsCommand(",
|
|
"RuntimePlayerSettings.InputSettings.SetBindingOverride(actionName, bindingIndex, inputSystemPath)",
|
|
"RuntimePlayerSettings.InputSettings.SetActionBarInputEnabled(inputSystemPath, enabled)"
|
|
};
|
|
|
|
var survivingActionGameManagerSymbols = forbiddenActionGameManagerSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (survivingActionGameManagerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime input settings are still using Unity-local mutation authority instead of typed Eve requests: " +
|
|
string.Join(", ", survivingActionGameManagerSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireActionGameManagerInputScreenUsesSharedFullscreenPrimitive(string root)
|
|
{
|
|
var managerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var sceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs");
|
|
var menuShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityMenuShell.cs");
|
|
var gameplayInputShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayInputShell.cs");
|
|
if (!File.Exists(managerPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify ActionGameManager input-screen delegation; ActionGameManager.cs is missing.");
|
|
}
|
|
if (!File.Exists(sceneWiringPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify Unity scene wiring input-screen delegation; AetheriaUnityGameplaySceneWiring.cs is missing.");
|
|
}
|
|
if (!File.Exists(menuShellPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify Unity menu shell input-screen delegation; AetheriaUnityMenuShell.cs is missing.");
|
|
}
|
|
if (!File.Exists(gameplayInputShellPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify Unity gameplay input shell delegation; AetheriaUnityGameplayInputShell.cs is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(managerPath);
|
|
var sceneWiring = File.ReadAllText(sceneWiringPath);
|
|
var menuShell = File.ReadAllText(menuShellPath);
|
|
var gameplayInputShell = File.ReadAllText(gameplayInputShellPath);
|
|
var requiredManagerSymbols = new[]
|
|
{
|
|
"SceneWiring.ConfigureRuntimeInputScreenShell(MenuShell)",
|
|
"GameplayInputShell.Bootstrap();"
|
|
};
|
|
|
|
var missingManagerSymbols = requiredManagerSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingManagerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager no longer wires the Unity menu shell as the live input-screen owner: " +
|
|
string.Join(", ", missingManagerSymbols));
|
|
}
|
|
|
|
var requiredSceneWiringSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityGameplaySceneWiring",
|
|
"public void ConfigureRuntimeInputScreenShell(AetheriaUnityMenuShell menuShell)",
|
|
"MainMenu?.SetRuntimeInputScreenShell(menuShell.CanOpenRuntimeInputScreen, menuShell.ShowRuntimeInputScreen)"
|
|
};
|
|
|
|
var missingSceneWiringSymbols = requiredSceneWiringSymbols
|
|
.Where(symbol => !sceneWiring.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSceneWiringSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityGameplaySceneWiring no longer wires the Unity menu shell as the live input-screen owner: " +
|
|
string.Join(", ", missingSceneWiringSymbols));
|
|
}
|
|
|
|
var requiredShellSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityMenuShell",
|
|
"public bool CanOpenRuntimeInputScreen()",
|
|
"public void ShowRuntimeInputScreen()",
|
|
"public void ToggleFullscreenMenu(GameObject menu)",
|
|
"private void ShowFullscreenMenu(GameObject menu)",
|
|
"private void HideFullscreenMenu(GameObject menu)"
|
|
};
|
|
|
|
var missingShellSymbols = requiredShellSymbols
|
|
.Where(symbol => !menuShell.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingShellSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityMenuShell no longer owns the shared fullscreen primitive for the live input-screen owner: " +
|
|
string.Join(", ", missingShellSymbols));
|
|
}
|
|
|
|
var requiredGameplayInputShellSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityGameplayInputShell",
|
|
"public AetheriaUnityMenuShell MenuShell { get; set; }",
|
|
"Input.Global.InputScreen.performed += context => MenuShell.ToggleFullscreenMenu(MenuShell.HelpScreen);",
|
|
"Input.Global.ZoneMap.performed += context =>",
|
|
"Input.Player.TargetNearest.performed += context => PilotOperationController.RequestTargetNearest();",
|
|
"private void RegisterActionBarInput()",
|
|
"DragSession.RegisterTarget(dragAction => ActionBarPresentation.RequestBinding(slot, dragAction));"
|
|
};
|
|
|
|
var missingGameplayInputShellSymbols = requiredGameplayInputShellSymbols
|
|
.Where(symbol => !gameplayInputShell.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingGameplayInputShellSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityGameplayInputShell no longer owns Unity input callbacks and routes them through typed shell dependencies: " +
|
|
string.Join(", ", missingGameplayInputShellSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"public bool CanShowInputScreenFromMenu()",
|
|
"public void ShowInputScreenFromMenu()",
|
|
"private bool CanOpenRuntimeInputScreen()",
|
|
"private void ShowRuntimeInputScreen()",
|
|
"private void ShowFullscreenMenu(GameObject menu)",
|
|
"private void HideFullscreenMenu(GameObject menu)",
|
|
"Input.Global.InputScreen.performed += context",
|
|
"Input.Global.ZoneMap.performed += context",
|
|
"Input.Player.TargetNearest.performed += context",
|
|
"new InputAction(binding: controlPath)",
|
|
"OnPointerEnterAsObservable()"
|
|
};
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still exposes runtime input-screen menu handoff as public manager API: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireSectorMapZoneDetailsUseEveSurface(string root)
|
|
{
|
|
var sectorRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs");
|
|
var zoneDetailsSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeZoneDetailsSurfaceBuilder.cs");
|
|
if (!File.Exists(sectorRendererPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify sector-map zone details shell; SectorRenderer.cs is missing.");
|
|
}
|
|
if (!File.Exists(zoneDetailsSurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify sector-map zone details shell; shared runtime zone details surface builder is missing.");
|
|
}
|
|
|
|
var clientStatePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaClientState.cs");
|
|
var verseClientPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeVerseClient.cs");
|
|
if (!File.Exists(clientStatePath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify sector-map zone details shell; AetheriaClientState.cs is missing.");
|
|
}
|
|
if (!File.Exists(verseClientPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify sector-map zone details shell; AetheriaRuntimeVerseClient.cs is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(sectorRendererPath);
|
|
var zoneDetailsSurfaceBuilder = File.ReadAllText(zoneDetailsSurfaceBuilderPath);
|
|
var clientState = File.ReadAllText(clientStatePath);
|
|
var verseClient = File.ReadAllText(verseClientPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RenderZoneDetailsSurface(",
|
|
"HandleZoneDetailsSurfaceCommand(",
|
|
"HideZoneDetailsSurface(",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_zoneDetailsSurfaceDocument)",
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-renderer\")",
|
|
"RuntimeState.ZoneDetailsSurface(zoneIndex).Latest()",
|
|
"RuntimeState.CurrentZone.Latest()",
|
|
"AetheriaRuntimeZoneDetailsSurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeZoneDetailsCommandKind.Close"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SectorRenderer no longer lowers zone details through an Eve surface shell: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var requiredSharedSymbols = new[]
|
|
{
|
|
"BuildFromDocuments(",
|
|
"AetheriaRuntimeZoneDetailsDocument zoneDetails",
|
|
"AetheriaRuntimeSectorMapDocument sectorMap",
|
|
"AetheriaRuntimeCatalogSnapshot catalog",
|
|
"AetheriaRuntimePlayerSettingsDocument playerSettings"
|
|
};
|
|
var missingSharedSymbols = requiredSharedSymbols
|
|
.Where(symbol => !zoneDetailsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSharedSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime no longer owns sector-map zone-details surface composition: " +
|
|
string.Join(", ", missingSharedSymbols));
|
|
}
|
|
|
|
var requiredManagedHandleSymbols = new[]
|
|
{
|
|
"ZoneDetailsSurface(int zoneIndex)"
|
|
};
|
|
var missingManagedHandleSymbols = requiredManagedHandleSymbols
|
|
.Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingManagedHandleSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState no longer exposes managed zone-details surface document handles: " +
|
|
string.Join(", ", missingManagedHandleSymbols));
|
|
}
|
|
|
|
var requiredVerseSurfaceSymbols = new[]
|
|
{
|
|
"IndexedDocumentId(\"aetheria.zone.details.surface\", zoneIndex)",
|
|
"ZoneDetailsSurfaceAsync(frame, zoneIndex)",
|
|
"AetheriaRuntimeZoneDetailsSurfaceBuilder.BuildFromDocuments(",
|
|
"RequireManagedCatalog()",
|
|
"RequireManagedPlayerSettings()"
|
|
};
|
|
var missingVerseSurfaceSymbols = requiredVerseSurfaceSymbols
|
|
.Where(symbol => !verseClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingVerseSurfaceSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient no longer provides zone-details surfaces as managed CultMesh documents: " +
|
|
string.Join(", ", missingVerseSurfaceSymbols));
|
|
}
|
|
|
|
var forbiddenAccessSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeZoneDetailsSurfaceBuilder.Build(",
|
|
"AetheriaRuntimeZoneDetailsSurfaceBuilder.Facts(",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentSectorMap(\"unity-sector-renderer\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentZoneDetails(zoneIndex, \"unity-sector-renderer\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-sector-renderer\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-sector-renderer\")",
|
|
"CultMeshReactiveDocument<",
|
|
".ReactiveSectorMap(\"unity-sector-renderer\")",
|
|
".ReactiveCurrentZone(\"unity-sector-renderer\")",
|
|
".ReactiveZoneDetails(zoneIndex, \"unity-sector-renderer\")",
|
|
".ReactiveCatalogSnapshot(\"unity-sector-renderer\")",
|
|
".ReactivePlayerSettingsDocument(\"unity-sector-renderer\")",
|
|
"AetheriaRuntimeSectorMapSession",
|
|
"AetheriaRuntimeCurrentZoneSession",
|
|
"AetheriaRuntimeZoneDetailsSession",
|
|
"AetheriaRuntimeCatalogSession",
|
|
"AetheriaRuntimePlayerSettingsSession",
|
|
".ObserveSectorMap()",
|
|
".ObserveZone()",
|
|
".ObserveZone(zoneIndex)",
|
|
"ResolveClient().State.ObserveCatalog()",
|
|
".ObservePlayer()",
|
|
"ClearClientCaches()"
|
|
}
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenAccessSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SectorRenderer should sample sector UI documents through named current typed state instead of owning reactive/session handles: " +
|
|
string.Join(", ", forbiddenAccessSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"BuildZoneDetailsSurfaceDefinition(",
|
|
"ZoneDetailsSurfaceId",
|
|
"CloseZoneDetailsCommand",
|
|
"private const string ZoneDetailsSurfaceType",
|
|
"ResolveZoneDetailsSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer()",
|
|
"Properties.Clear();",
|
|
"Properties.Title.text = zone.Name;",
|
|
"Properties.AddProperty(\"Owner\"",
|
|
"Properties.AddProperty(\"Mass\"",
|
|
"Properties.AddProperty(\"Radius\"",
|
|
"Properties.AddProperty(\"Planets\"",
|
|
"Properties.AddProperty(\"Asteroid Belts\"",
|
|
"Properties.AddProperty(\"Gas Giants\"",
|
|
"Properties.AddProperty(\"Stars\"",
|
|
"Properties.AddProperty(\"Stations\"",
|
|
"Properties.AddProperty(\"Turrets\"",
|
|
"Properties.AddProperty(\"Ships\"",
|
|
"GameManager.Settings.ZoneSettings.ZoneRadius.Evaluate(",
|
|
"GameManager.Settings.ZoneSettings.ZoneMass.Evaluate(",
|
|
"OpenRuntimeCatalog()",
|
|
"ProjectZoneBodies(",
|
|
"ProjectZoneEntities(",
|
|
"ComposeZoneDetailsSurfaceState(",
|
|
"ProjectZoneDetailsSurfaceState(",
|
|
"ProjectDaemonZone(",
|
|
"AetheriaRuntimeZoneDetailsSurfaceBuilder.Compose(",
|
|
"AetheriaRuntimeZoneDetailsSurfaceBuilder.Project(",
|
|
"AetheriaRuntimeZoneDetailsDaemonProjection",
|
|
"new AetheriaRuntimeZoneDetailsBodyProjection(",
|
|
"new AetheriaRuntimeZoneDetailsEntityProjection(",
|
|
"runtimeZone.PlanetInstances",
|
|
"runtimeZone.AsteroidBelts",
|
|
"runtimeZone.Entities",
|
|
"private static bool IsBodyKind(",
|
|
"private static bool IsPlanetBody(",
|
|
"private static bool HasHullType(",
|
|
"string.Equals(request.Command, AetheriaRuntimeZoneDetailsSurfaceBuilder.Close"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SectorRenderer still owns zone details through the old PropertiesPanel path: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeZoneDetailsSurfaceBuilder",
|
|
"public sealed class AetheriaRuntimeZoneDetailsFacts",
|
|
"public sealed class AetheriaRuntimeZoneDetailsBodyFacts",
|
|
"public sealed class AetheriaRuntimeZoneDetailsEntityFacts",
|
|
"public static AetheriaRuntimeZoneDetailsFacts Facts(",
|
|
"Math.Max(0, zone.GravityTerrainRadius)",
|
|
".Sum(body => body.Mass)",
|
|
"public static AetheriaRuntimeSurfaceDocument Build(",
|
|
"var bodyList = (bodies ?? Array.Empty<AetheriaRuntimeZoneDetailsBodyFacts>())",
|
|
"var sortedFactions = (otherFactions ?? Array.Empty<string>())",
|
|
"var planets = bodyList.Count(IsPlanetBody).ToString()",
|
|
"var stations = entityList.Count(entity => HasHullType(entity, \"Station\")).ToString()",
|
|
"private static bool IsBodyKind(",
|
|
"private static bool IsPlanetBody(",
|
|
"private static bool HasHullType(",
|
|
"public const string SurfaceId = \"aetheria.sector_map.zone_details\"",
|
|
"public const string Close = \"aetheria.sector_map.zone_details.close\"",
|
|
"public enum AetheriaRuntimeZoneDetailsCommandKind",
|
|
"public readonly struct AetheriaRuntimeZoneDetailsCommand",
|
|
"public static class AetheriaRuntimeZoneDetailsSurfaceCommands",
|
|
"public static bool TryRead(",
|
|
"public static AetheriaRuntimeSurfaceDocument Build(",
|
|
"providerKind: \"sector.map\"",
|
|
"Factions Present",
|
|
"Has not been visited.",
|
|
"Asteroid Belts"
|
|
};
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !zoneDetailsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime zone details surface builder no longer owns the sector-map zone shell contract: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
|
|
if (zoneDetailsSurfaceBuilder.Contains("AetheriaRuntimeZoneDetailsSurfaceState", StringComparison.Ordinal) ||
|
|
zoneDetailsSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) ||
|
|
zoneDetailsSurfaceBuilder.Contains("public static AetheriaRuntimeZoneDetailsSurfaceState Compose(", StringComparison.Ordinal) ||
|
|
zoneDetailsSurfaceBuilder.Contains("public static AetheriaRuntimeZoneDetailsSurfaceState Project(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime zone details must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer.");
|
|
}
|
|
}
|
|
|
|
static void RequireRuntimeMenuTabsUseEveSurface(string root)
|
|
{
|
|
var menuPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuPanel.cs");
|
|
var localMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "LocalMenu.cs");
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var legacyMenuTabsSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeMenuTabsSurfaceBuilder.cs");
|
|
var localStorySurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeLocalStorySurfaceBuilder.cs");
|
|
if (!File.Exists(menuPanelPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify runtime menu tab shell; MenuPanel.cs is missing.");
|
|
}
|
|
if (!File.Exists(localMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify runtime local-menu shell; LocalMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(actionGameManagerPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify runtime menu tab shell; ActionGameManager.cs is missing.");
|
|
}
|
|
if (!File.Exists(localStorySurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify runtime local story shell; AetheriaRuntimeLocalStorySurfaceBuilder.cs is missing.");
|
|
}
|
|
|
|
var legacyTabButtonPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuTabButton.cs");
|
|
if (File.Exists(legacyTabButtonPath))
|
|
{
|
|
throw new InvalidOperationException("MenuPanel tab metadata still has a surviving MenuTabButton component shell.");
|
|
}
|
|
|
|
var source = File.ReadAllText(menuPanelPath);
|
|
var localMenu = File.ReadAllText(localMenuPath);
|
|
var actionGameManager = File.ReadAllText(actionGameManagerPath);
|
|
var surfaceHostPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveUnitySurfaceHost.cs");
|
|
if (!File.Exists(surfaceHostPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify runtime menu tab shell; AetheriaEveUnitySurfaceHost.cs is missing.");
|
|
}
|
|
|
|
var localStorySurfaceBuilder = File.ReadAllText(localStorySurfaceBuilderPath);
|
|
var surfaceHost = File.ReadAllText(surfaceHostPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"MenuTabBinding",
|
|
"TabBindings = Array.Empty<MenuTabBinding>();",
|
|
"RenderTabSurface(",
|
|
"HandleTabSurfaceCommand(",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_tabSurfaceDocument)",
|
|
"BuildTabSurfaceDocument(",
|
|
"new AetheriaRuntimeSurfaceDocument(",
|
|
"new AetheriaRuntimeSurfaceTree(",
|
|
"new AetheriaRuntimeSurfaceCommandTemplate(",
|
|
"ResolveVisibleTabs(",
|
|
"TryResolveCurrentDocking(out var docking)",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-runtime-menu-tabs\")",
|
|
"_runtimeState.CurrentDocking.Latest()",
|
|
"docking.IsDocked",
|
|
"GetTabLabel(",
|
|
"ToRuntimeTabKey(",
|
|
"NormalizeTabKey(tab.ToString())",
|
|
"TryReadTabSurfaceCommand(request, out var tabKey)",
|
|
"string.Equals(tabKey, ToRuntimeTabKey(tab), StringComparison.Ordinal)",
|
|
"TabButtons.gameObject.SetActive(false)"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MenuPanel no longer lowers the runtime tab shell through Eve surfaces: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var requiredHostSymbols = new[]
|
|
{
|
|
"public static class AetheriaEveUnitySurfaceHost",
|
|
"public static UIDocument Render(",
|
|
"new EveUiToolkitSurfaceLowerer(new EveUiToolkitSurfaceOptions(embeddedDocumentResolver))",
|
|
"lowerer.Lower(surface, commandHandler)",
|
|
"shell.Add(lowered)",
|
|
"root.Add(lowered)"
|
|
};
|
|
|
|
var missingHostSymbols = requiredHostSymbols
|
|
.Where(symbol => !surfaceHost.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingHostSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaEveUnitySurfaceHost no longer owns the shared Unity Eve lowering path: " +
|
|
string.Join(", ", missingHostSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"MenuTabButton",
|
|
"tabButton.Button.onClick.AddListener(",
|
|
"tabButton.gameObject.SetActive(!tabButton.RequireDock || GameManager.DockedEntity != null);",
|
|
"_tabs[MenuTab.Local].gameObject.SetActive(",
|
|
"BuildTabSurfaceDefinition(",
|
|
"private static EveSurfaceComponent Button(",
|
|
"private static EveSurfaceComponent Text(",
|
|
"private static EveSurfaceComponent Node(",
|
|
"ResolveTabSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer()",
|
|
"string.Equals(request.Command, AetheriaRuntimeMenuTabsSurfaceBuilder.CommandFor(",
|
|
"AetheriaRuntimeMenuTabsSurfaceBuilder",
|
|
"AetheriaRuntimeMenuTabsSurfaceCommands",
|
|
"AetheriaRuntimeMenuTabModelOption",
|
|
"AetheriaRuntimeMenuTabCommandKind",
|
|
"ResolveClient().State.CurrentDocking()",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeCurrentDockingDocument> _currentDocking",
|
|
".ReactiveCurrentDocking(\"unity-runtime-menu-tabs\")",
|
|
"docking = _currentDocking.Current",
|
|
"AetheriaRuntimeObservedDockingState",
|
|
"SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)",
|
|
"private AetheriaUnityPresentationEntityIndex _presentationEntityIndex;",
|
|
"private AetheriaUnityObservedDockingIndex _observedDockingIndex;",
|
|
"TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)",
|
|
"new AetheriaUnityObservedDockingIndex(_presentationEntityIndex)",
|
|
"ComposeTabSurface(",
|
|
"ProjectTabSurfaceState(",
|
|
"ProjectTabSurface(",
|
|
"AetheriaRuntimeMenuTabsSurfaceBuilder.Compose(",
|
|
"AetheriaRuntimeMenuTabsSurfaceBuilder.Project(",
|
|
"new AetheriaRuntimeMenuTabProjectionOption(",
|
|
"private static string TabKey("
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MenuPanel still owns tab-shell behavior through the old MenuTabButton path: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
if (source.Contains("GameManager.IsObservedDocked", StringComparison.Ordinal) ||
|
|
source.Contains("GameManager.TryGetObservedDockedLocalStory", StringComparison.Ordinal) ||
|
|
source.Contains("AetheriaClientReactiveDockingState _dockingState", StringComparison.Ordinal) ||
|
|
source.Contains("private AetheriaClientDockingSnapshot ResolveCurrentDocking()", StringComparison.Ordinal) ||
|
|
source.Contains("AetheriaUnityRuntimeClientProvider.CurrentDockingState(\"unity-runtime-menu-tabs\")", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MenuPanel tab visibility must read current typed docking state directly instead of direct docking caches, ActionGameManager observed adapters, or Unity observed docking index bridges.");
|
|
}
|
|
|
|
if (File.Exists(legacyMenuTabsSurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime menu tabs are client-local UI; do not keep a shared MenuTabsSurfaceBuilder or projection shell for them.");
|
|
}
|
|
|
|
var requiredDockedStoryObserverSymbols = new[]
|
|
{
|
|
"TryResolveDockedLocalInkStory(out _currentLocalInkLocation)",
|
|
"private bool TryResolveDockedLocalInkStory(out LocationStory story)",
|
|
"SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)",
|
|
"CurrentDockingSnapshot()",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-local-menu\")",
|
|
"_runtimeState.CurrentDocking.Latest()",
|
|
"docking.IsDocked",
|
|
"Ink story execution is still a Unity-local island.",
|
|
"local story state becomes a daemon-owned CultMesh document.",
|
|
"_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey(",
|
|
"docking.DockParentEntityKey",
|
|
"docking.DockingBayIndex",
|
|
"dockingBay?.Entity is not OrbitalEntity { Story: { } dockedStory }",
|
|
"AetheriaRuntimeLocalStorySurfaceBuilder.Build(",
|
|
"AetheriaRuntimeLocalStorySurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeLocalStoryCommandKind.Continue",
|
|
"AetheriaRuntimeLocalStoryCommandKind.Choose",
|
|
"new AetheriaRuntimeLocalStoryChoiceState("
|
|
};
|
|
var dockedStoryObserverCorpus = localMenu;
|
|
var missingDockedStoryObserverSymbols = requiredDockedStoryObserverSymbols
|
|
.Where(symbol => !dockedStoryObserverCorpus.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDockedStoryObserverSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime menu/local story visibility no longer flows through typed docking state plus explicit Unity presentation lookup: " +
|
|
string.Join(", ", missingDockedStoryObserverSymbols));
|
|
}
|
|
|
|
if (source.Contains("GameManager.DockedEntity != null", StringComparison.Ordinal) ||
|
|
source.Contains("GameManager.DockedEntity as OrbitalEntity", StringComparison.Ordinal) ||
|
|
localMenu.Contains("AetheriaClientReactiveDockingState _dockingState", StringComparison.Ordinal) ||
|
|
localMenu.Contains("private bool TryResolveDockingState(out AetheriaClientDockingSnapshot dockingState)", StringComparison.Ordinal) ||
|
|
localMenu.Contains("TryResolveDockedLocalStory(", StringComparison.Ordinal) ||
|
|
localMenu.Contains("_currentLocation", StringComparison.Ordinal) ||
|
|
localMenu.Contains("private AetheriaUnityObservedDockingIndex _observedDockingIndex", StringComparison.Ordinal) ||
|
|
localMenu.Contains("TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)", StringComparison.Ordinal) ||
|
|
localMenu.Contains("TryResolveObservedDockingIndex(out var dockingIndex)", StringComparison.Ordinal) ||
|
|
localMenu.Contains("new AetheriaUnityObservedDockingIndex(_presentationEntityIndex)", StringComparison.Ordinal) ||
|
|
localMenu.Contains("dockingIndex.TryResolveCurrentDockingBay", StringComparison.Ordinal) ||
|
|
localMenu.Contains("ActionGameManager.Instance.DockedEntity", StringComparison.Ordinal) ||
|
|
localMenu.Contains("TryGetObservedDockedLocalStory", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("TryGetObservedDockedLocalStory", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("IsObservedDocked", StringComparison.Ordinal) ||
|
|
localMenu.Contains("ProjectStorySurface(", StringComparison.Ordinal) ||
|
|
localMenu.Contains("AetheriaRuntimeLocalStorySurfaceBuilder.Project(", StringComparison.Ordinal) ||
|
|
localMenu.Contains("Instantiate(ChoicePrefab", StringComparison.Ordinal) ||
|
|
localMenu.Contains("choiceInstance.Button.onClick.AddListener", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime menu/local UI must not inspect DockedEntity, rebuild legacy prefab buttons, or carry a local story projection helper; use typed docking and build the Eve local-story surface directly.");
|
|
}
|
|
|
|
var requiredLocalStoryBuilderSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeLocalStorySurfaceBuilder",
|
|
"public const string SurfaceId = \"aetheria.runtime_menu.local_story\"",
|
|
"public const string Continue = \"aetheria.runtime_menu.local_story.continue\"",
|
|
"ChoiceCommandFor(int choiceIndex)",
|
|
"public static AetheriaRuntimeSurfaceDocument Build(",
|
|
"var storyBody = string.IsNullOrWhiteSpace(body) ? \"...\" : body.Trim()",
|
|
"var orderedChoices = (choices ?? Array.Empty<AetheriaRuntimeLocalStoryChoiceState>())",
|
|
"public enum AetheriaRuntimeLocalStoryCommandKind",
|
|
"public readonly struct AetheriaRuntimeLocalStoryCommand",
|
|
"public static class AetheriaRuntimeLocalStorySurfaceCommands",
|
|
"AetheriaRuntimeLocalStoryChoiceState"
|
|
};
|
|
var missingLocalStoryBuilderSymbols = requiredLocalStoryBuilderSymbols
|
|
.Where(symbol => !localStorySurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingLocalStoryBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime local story surface builder no longer owns the local story shell contract: " +
|
|
string.Join(", ", missingLocalStoryBuilderSymbols));
|
|
}
|
|
|
|
if (localStorySurfaceBuilder.Contains("AetheriaRuntimeLocalStorySurfaceState", StringComparison.Ordinal) ||
|
|
localStorySurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) ||
|
|
localStorySurfaceBuilder.Contains("public static AetheriaRuntimeLocalStorySurfaceState Project(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime local story composition must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer.");
|
|
}
|
|
|
|
}
|
|
|
|
static void RequireInventoryShipSettingsUseEveSurface(string root)
|
|
{
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var shipSettingsSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeShipSettingsSurfaceBuilder.cs");
|
|
if (!File.Exists(inventoryMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify inventory ship-settings shell; InventoryMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(shipSettingsSurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify inventory ship-settings shell; shared runtime ship settings surface builder is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(inventoryMenuPath);
|
|
var shipSettingsSurfaceBuilder = File.ReadAllText(shipSettingsSurfaceBuilderPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RenderCurrentShipSettingsSurface(",
|
|
"HandleCurrentShipSettingsSurfaceCommand(",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_shipSettingsSurfaceDocument)",
|
|
"AetheriaRuntimeShipSettingsSurfaceBuilder.Build(",
|
|
"currentEntity?.Entity?.DisplayName",
|
|
"(float)(currentEntity?.ShutdownPerformance ?? 0)",
|
|
"AetheriaRuntimeShipSettingsSurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeShipSettingsCommandKind.DecrementShutdownThreshold",
|
|
"AetheriaRuntimeShipSettingsCommandKind.IncrementShutdownThreshold",
|
|
"AetheriaRuntimeShipSettingsCommandKind.ResetShutdownThreshold",
|
|
"AetheriaRuntimeShipSettingsCommandKind.Close",
|
|
"AetheriaRuntimeShipSettingsSurfaceCommands.ResolveShutdownPerformance(",
|
|
"ResolveDefaultShutdownPerformance()",
|
|
"PlayerSettingsSnapshot()?.DefaultShutdownPerformance",
|
|
"TryReadCurrentEntity(out var currentEntity)",
|
|
"SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)",
|
|
"private AetheriaRuntimeCurrentEntityDocument _shipSettingsCurrentEntity;",
|
|
"!TryReadCurrentEntity(out var latestCurrentEntity)",
|
|
"RequestEntityShutdownPerformance(",
|
|
"latestCurrentEntity.EntityKey",
|
|
"(float)latestCurrentEntity.ShutdownPerformance",
|
|
"RuntimeState.CurrentEntity.Latest()",
|
|
"CurrentEntitySnapshot()",
|
|
"currentEntity = CurrentEntitySnapshot();"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu no longer lowers current ship settings through an Eve surface shell: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"BuildCurrentShipSettingsSurfaceDefinition(",
|
|
"ShipSettingsSurfaceId",
|
|
"DecrementShutdownThresholdCommand",
|
|
"IncrementShutdownThresholdCommand",
|
|
"ResetShutdownThresholdCommand",
|
|
"CloseShipSettingsCommand",
|
|
"private const string ShipSettingsSurfaceType",
|
|
"ResolveShipSettingsSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer()",
|
|
"host.AddComponent<UIDocument>",
|
|
"PropertiesPanel.AddField(\"Shutdown Threshold\"",
|
|
"() => GameManager.CurrentEntity.Settings.ShutdownPerformance",
|
|
"f => GameManager.RequestEntityShutdownPerformance(GameManager.CurrentEntity, f)",
|
|
"switch (request.Command)",
|
|
"ShutdownThresholdStep",
|
|
"Mathf.Clamp01(entity.Settings.ShutdownPerformance",
|
|
"Mathf.Clamp01(GameManager.Settings.GameplaySettings.DefaultShutdownPerformance",
|
|
"GameManager.Settings.GameplaySettings.DefaultShutdownPerformance",
|
|
"var entity = GameManager.CurrentEntity",
|
|
"GameManager.CurrentEntity == null",
|
|
"GameManager.TryGetObservedCurrentEntity(",
|
|
"RenderCurrentShipSettingsSurface(GameManager.CurrentEntity)",
|
|
"private Entity _shipSettingsEntity",
|
|
"var entity = _shipSettingsEntity;",
|
|
"entity == null || !IsCurrentEntity(entity)",
|
|
"entity.Settings.ShutdownPerformance",
|
|
"new AetheriaRuntimeShipSettingsSurfaceState(",
|
|
"ComposeCurrentShipSettingsSurface(",
|
|
"ProjectCurrentShipSettingsSurface(",
|
|
"AetheriaRuntimeShipSettingsSurfaceBuilder.Compose(",
|
|
"AetheriaRuntimeShipSettingsSurfaceBuilder.Project("
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still owns ship settings through the old PropertiesPanel field path: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeShipSettingsSurfaceBuilder",
|
|
"public const string SurfaceId = \"aetheria.inventory.current_ship_settings\"",
|
|
"public const string DecrementShutdownThreshold",
|
|
"public const string IncrementShutdownThreshold",
|
|
"public const string ResetShutdownThreshold",
|
|
"public const string Close",
|
|
"public enum AetheriaRuntimeShipSettingsCommandKind",
|
|
"public readonly struct AetheriaRuntimeShipSettingsCommand",
|
|
"public static class AetheriaRuntimeShipSettingsSurfaceCommands",
|
|
"public static bool TryRead(",
|
|
"public static AetheriaRuntimeSurfaceDocument Build(",
|
|
"var formattedShutdownPerformance = formatShutdownPerformance == null",
|
|
"updatedAtUtc.ToString(\"O\", CultureInfo.InvariantCulture)",
|
|
"public static float ResolveShutdownPerformance(",
|
|
"public static float ClampShutdownPerformance("
|
|
};
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !shipSettingsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime ship settings surface builder no longer owns the ship-settings shell contract: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
|
|
if (shipSettingsSurfaceBuilder.Contains("public static AetheriaRuntimeShipSettingsSurfaceState Compose(", StringComparison.Ordinal) ||
|
|
shipSettingsSurfaceBuilder.Contains("public static AetheriaRuntimeShipSettingsSurfaceState Project(", StringComparison.Ordinal) ||
|
|
shipSettingsSurfaceBuilder.Contains("AetheriaRuntimeShipSettingsSurfaceState", StringComparison.Ordinal) ||
|
|
shipSettingsSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime ship settings composition must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer.");
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryCargoItemDetailsUseEveSurface(string root)
|
|
{
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var surfaceDocumentPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeSurfaceDocuments.cs");
|
|
var daemonItemStatQueriesPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeDaemonItemStatQueries.cs");
|
|
var eveUnitySurfaceHostPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.eve-runtime",
|
|
"Runtime",
|
|
"AetheriaEveUnitySurfaceHost.cs");
|
|
var runtimeSurfaceDocumentsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeSurfaceDocuments.cs");
|
|
var cargoItemSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs");
|
|
var equippedItemSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs");
|
|
var unityProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj");
|
|
var requiredFiles = new[]
|
|
{
|
|
inventoryMenuPath,
|
|
actionGameManagerPath,
|
|
surfaceDocumentPath,
|
|
daemonItemStatQueriesPath,
|
|
eveUnitySurfaceHostPath,
|
|
runtimeSurfaceDocumentsPath,
|
|
cargoItemSurfaceBuilderPath,
|
|
equippedItemSurfaceBuilderPath,
|
|
unityProjectPath
|
|
};
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot verify inventory cargo-item shell; required files are missing: " +
|
|
string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var source = File.ReadAllText(inventoryMenuPath);
|
|
var actionGameManager = File.ReadAllText(actionGameManagerPath);
|
|
var surfaceDocument = File.ReadAllText(surfaceDocumentPath);
|
|
var daemonItemStatQueries = File.ReadAllText(daemonItemStatQueriesPath);
|
|
var unityProject = File.ReadAllText(unityProjectPath);
|
|
var eveUnitySurfaceHost = File.ReadAllText(eveUnitySurfaceHostPath);
|
|
var runtimeSurfaceDocuments = File.ReadAllText(runtimeSurfaceDocumentsPath);
|
|
var cargoItemSurfaceBuilder = File.ReadAllText(cargoItemSurfaceBuilderPath);
|
|
var equippedItemSurfaceBuilder = File.ReadAllText(equippedItemSurfaceBuilderPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RenderCargoItemDetailsSurface(",
|
|
"HandleCargoItemDetailsSurfaceCommand(",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_cargoItemDetailsSurfaceDocument)",
|
|
"AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Build(",
|
|
"ResolveManufacturerName(typedItem)",
|
|
"FormatItemTier(typedItem, equippableItem)",
|
|
"ComposeCargoItemObservation(",
|
|
"AetheriaRuntimeCargoItemDetailsSurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeCargoItemDetailsCommandKind.Close"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu no longer lowers cargo-item inspection through an Eve surface shell: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
if (source.Contains("InventoryCargoEventData", StringComparison.Ordinal) ||
|
|
source.Contains("Occupancy[", StringComparison.Ordinal) ||
|
|
source.Contains("RenderCargoItemDetailsSurface(item);", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu must not open cargo-item inspection by decoding Unity inventory cells; item selection belongs to the daemon-presented inventory surface.");
|
|
}
|
|
|
|
var runtimeStateRefResolverPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeStateRefResolver.cs");
|
|
var runtimeStateRefResolver = File.Exists(runtimeStateRefResolverPath)
|
|
? File.ReadAllText(runtimeStateRefResolverPath)
|
|
: throw new InvalidOperationException("Cannot verify item stat state-ref authority; AetheriaRuntimeStateRefResolver.cs is missing.");
|
|
|
|
if (!runtimeStateRefResolver.Contains("TryResolveDaemonItemStatRef(", StringComparison.Ordinal) ||
|
|
!runtimeStateRefResolver.Contains("AetheriaRuntimeDaemonItemStatQueries.TryReadItemStatRef(", StringComparison.Ordinal) ||
|
|
!runtimeStateRefResolver.Contains("AetheriaRuntimeDaemonItemStatQueries.EvaluatePerformanceStat(", StringComparison.Ordinal) ||
|
|
!runtimeStateRefResolver.Contains("item.Temperature", StringComparison.Ordinal) ||
|
|
!runtimeStateRefResolver.Contains("FindDaemonItem(", StringComparison.Ordinal) ||
|
|
!cargoItemSurfaceBuilder.Contains("AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(", StringComparison.Ordinal) ||
|
|
source.Contains("ResolveInventorySurfaceStateRef", StringComparison.Ordinal) ||
|
|
source.Contains("TryResolveInventorySurfaceStateRef", StringComparison.Ordinal) ||
|
|
source.Contains("private static bool TryReadItemStatRef(", StringComparison.Ordinal) ||
|
|
source.Contains("DecodeRefToken", StringComparison.Ordinal) ||
|
|
source.Contains("GameManager.ItemManager.GetTier", StringComparison.Ordinal) ||
|
|
source.Contains("GameManager.ItemManager.Evaluate", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("ObservedItemStat(", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("ItemManager.Evaluate(stat", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu must project current item stats through shared daemon item-stat queries instead of direct Unity ItemManager stat authority.");
|
|
}
|
|
|
|
var stateRefRequiredSymbols = new[]
|
|
{
|
|
("AetheriaRuntimeSurfaceDocuments.cs", surfaceDocument, "public static class AetheriaRuntimeSurfaceStateRefs"),
|
|
("AetheriaRuntimeSurfaceDocuments.cs", surfaceDocument, "public const string Source = \"stateRef\""),
|
|
("AetheriaRuntimeSurfaceDocuments.cs", surfaceDocument, "public const string Value = \"valueRef\""),
|
|
("AetheriaRuntimeDaemonItemStatQueries.cs", daemonItemStatQueries, "public const string StateRefPrefix = \"aetheria.state/items\""),
|
|
("AetheriaRuntimeDaemonItemStatQueries.cs", daemonItemStatQueries, "public static string ItemStatRef("),
|
|
("AetheriaRuntimeDaemonItemStatQueries.cs", daemonItemStatQueries, "public static bool TryReadItemStatRef("),
|
|
("AetheriaRuntimeDaemonItemStatQueries.cs", daemonItemStatQueries, "public string ValueRef =>"),
|
|
("GameCult.Aetheria.State.Unity.csproj", unityProject, "AetheriaRuntimeDaemonItemStatQueries.cs"),
|
|
("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "CultMeshStateRefResolver stateRefResolver"),
|
|
("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "ContainsStateRefs(surface)"),
|
|
("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "prop.Key.EndsWith(\"Ref\", StringComparison.Ordinal)"),
|
|
("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "CreateDefaultStateRefResolver()"),
|
|
("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "AetheriaEveRuntimeUnityHooks"),
|
|
("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "TryCreateDefaultStateRefResolver()"),
|
|
("AetheriaRuntimeSurfaceDocuments.cs", runtimeSurfaceDocuments, "public static EveUiSurfaceDocument ResolveStateRefs("),
|
|
("AetheriaRuntimeSurfaceDocuments.cs", runtimeSurfaceDocuments, "ResolvePropRefs(props, resolveStateRef)"),
|
|
("AetheriaRuntimeSurfaceDocuments.cs", runtimeSurfaceDocuments, "ResolvePropRef(props, AetheriaRuntimeSurfaceStateRefs.Source, \"value\", resolveStateRef)"),
|
|
("AetheriaRuntimeSurfaceDocuments.cs", runtimeSurfaceDocuments, "IsStatePointerProp(prop.Key)"),
|
|
("AetheriaRuntimeSurfaceDocuments.cs", runtimeSurfaceDocuments, "ResolvePointerValueKey(refProp.Key)"),
|
|
("AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs", cargoItemSurfaceBuilder, "public string ValueRef { get; }"),
|
|
("AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs", cargoItemSurfaceBuilder, "props.Add(AetheriaRuntimeSurfaceStateRefs.ValueRef(valueRef))"),
|
|
("AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs", equippedItemSurfaceBuilder, "public string ValueRef { get; }"),
|
|
("AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs", equippedItemSurfaceBuilder, "props.Add(AetheriaRuntimeSurfaceStateRefs.ValueRef(valueRef))")
|
|
};
|
|
var missingStateRefSymbols = stateRefRequiredSymbols
|
|
.Where(symbol => !symbol.Item2.Contains(symbol.Item3, StringComparison.Ordinal))
|
|
.Select(symbol => $"{symbol.Item1}: {symbol.Item3}")
|
|
.ToArray();
|
|
if (missingStateRefSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve/CultUI item stat surfaces must expose typed daemon state refs that the UI runtime can resolve: " +
|
|
string.Join(", ", missingStateRefSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"BuildCargoItemDetailsSurfaceDefinition(",
|
|
"BuildCargoItemBehaviorCards(",
|
|
"BuildCargoItemBehaviorMetric(",
|
|
"CargoItemDetailsSurfaceId",
|
|
"CloseCargoItemDetailsCommand",
|
|
"ResolveCargoItemDetailsSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer()",
|
|
"host.AddComponent<UIDocument>",
|
|
"string.Equals(request.Command, AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Close",
|
|
"ComposeCargoItemDetailsSurface(",
|
|
"ProjectCargoItemDetailsSurfaceState(",
|
|
"ProjectCargoItemBehaviorSections(",
|
|
"ProjectCargoItemBehaviorMetric(",
|
|
"ProjectCargoItemDetailsSurface(",
|
|
"ProjectCargoItemObservation(",
|
|
"AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Project("
|
|
};
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still owns cargo-item inspection surface construction instead of projecting shared runtime state: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeCargoItemDetailsSurfaceBuilder",
|
|
"public const string SurfaceId = \"aetheria.inventory.cargo_item_details\"",
|
|
"public const string Close = \"aetheria.inventory.cargo_item_details.close\"",
|
|
"public enum AetheriaRuntimeCargoItemDetailsCommandKind",
|
|
"public readonly struct AetheriaRuntimeCargoItemDetailsCommand",
|
|
"public static class AetheriaRuntimeCargoItemDetailsSurfaceCommands",
|
|
"public static bool TryRead(",
|
|
"AetheriaRuntimeCargoItemObservation",
|
|
"AetheriaRuntimeCargoItemSection",
|
|
"AetheriaRuntimeCargoItemMetric",
|
|
"var itemName = typedItem?.Name ?? \"\"",
|
|
"var behaviorSections = Array.Empty<AetheriaRuntimeCargoItemSection>()",
|
|
"var hasEquipmentStatus = !string.IsNullOrWhiteSpace(tier)",
|
|
"ProjectBehaviorSections(",
|
|
"ProjectBehaviorMetric(",
|
|
"public static AetheriaRuntimeSurfaceDocument Build("
|
|
};
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !cargoItemSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared cargo-item detail surface builder no longer owns the cargo inspection contract: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
|
|
if (cargoItemSurfaceBuilder.Contains("AetheriaRuntimeCargoItemDetailsSurfaceState", StringComparison.Ordinal) ||
|
|
cargoItemSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) ||
|
|
cargoItemSurfaceBuilder.Contains("public static AetheriaRuntimeCargoItemDetailsSurfaceState Compose(", StringComparison.Ordinal) ||
|
|
cargoItemSurfaceBuilder.Contains("public static AetheriaRuntimeCargoItemDetailsSurfaceState Project(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime cargo item details must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer.");
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryEquippedItemDetailsUseEveSurface(string root)
|
|
{
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var equippedItemSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs");
|
|
var stateProjectPath = Path.Combine(root, "Aetheria.State", "Aetheria.State.csproj");
|
|
var unityProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj");
|
|
var requiredFiles = new[]
|
|
{
|
|
inventoryMenuPath,
|
|
actionGameManagerPath,
|
|
equippedItemSurfaceBuilderPath,
|
|
stateProjectPath,
|
|
unityProjectPath
|
|
};
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot verify inventory equipped-item shell; required files are missing: " +
|
|
string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var source = File.ReadAllText(inventoryMenuPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RenderEquippedItemDetailsSurface(",
|
|
"HandleEquippedItemDetailsSurfaceCommand(",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_equippedItemDetailsSurfaceDocument)",
|
|
"AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Build(",
|
|
"BuildEquippedItemTitle(item, typedItem)",
|
|
"ResolveManufacturerName(typedItem)",
|
|
"ComposeEquippedItemObservation(",
|
|
"ComposeEquippedItemTemperatureControls(",
|
|
"ComposeEquippedItemWeaponGroupControls(",
|
|
"AetheriaRuntimeEquippedItemDetailsSurfaceCommands.TryRead(request, out var command)",
|
|
"switch (command.Kind)",
|
|
"AetheriaRuntimeEquippedItemDetailsCommandKind.Close",
|
|
"AetheriaRuntimeEquippedItemDetailsCommandKind.ToggleOverrideShutdown",
|
|
"AetheriaRuntimeEquippedItemDetailsCommandKind.SetTargetTemperature",
|
|
"AetheriaRuntimeEquippedItemDetailsCommandKind.ToggleWeaponGroup"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu no longer lowers equipped-item inspection through an Eve surface shell: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"EquippedItemDetailsSurfaceId",
|
|
"BuildEquippedItemDetailsSurfaceDefinition(",
|
|
"BuildEquippedItemControlCard(",
|
|
"BuildEquippedItemWeaponGroupCard(",
|
|
"BuildEquippedItemActionBarCards(",
|
|
"ProjectEquippedItemActionBarSlots(",
|
|
"BuildItemBehaviorCards(",
|
|
"BuildItemBehaviorMetric(",
|
|
"CloseEquippedItemDetailsCommand",
|
|
"ToggleEquippedItemOverrideShutdownCommand",
|
|
"SetEquippedItemTargetTemperatureCommand",
|
|
"ToggleEquippedItemWeaponGroupCommand",
|
|
"BindEquippedItemWeaponGroupCommand",
|
|
"ClearEquippedItemActionBarBindingCommand",
|
|
"AetheriaRuntimeEquippedItemDetailsCommandKind.BindWeaponGroup",
|
|
"AetheriaRuntimeEquippedItemDetailsCommandKind.ClearActionBarBinding",
|
|
"RequestWeaponGroupActionBarBinding(",
|
|
"RequestClearActionBarBinding(",
|
|
"ResolveEquippedItemDetailsSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer()",
|
|
"host.AddComponent<UIDocument>",
|
|
"new AetheriaRuntimeEquippedItemDetailsSurfaceState(",
|
|
"ComposeEquippedItemDetailsSurface(",
|
|
"ProjectEquippedItemDetailsSurfaceState(",
|
|
"ProjectEquippedItemBehaviorSections(",
|
|
"ProjectEquippedItemBehaviorMetric(",
|
|
"new AetheriaRuntimeEquippedItemSection(",
|
|
"new AetheriaRuntimeEquippedItemMetric(",
|
|
"private static EveSurfaceComponent Card(",
|
|
"private static EveSurfaceComponent Metric(",
|
|
"private static EveSurfaceComponent Text(",
|
|
"private static EveSurfaceComponent Button(",
|
|
"private static EveSurfaceComponent CommandButton(",
|
|
"private static EveSurfaceComponent TextField(",
|
|
"private static EveSurfaceComponent ButtonRow(",
|
|
"private static EveSurfaceComponent Node(",
|
|
"TryReadPayloadInt(",
|
|
"TryReadPayloadFloat(",
|
|
"request.Payload",
|
|
"ProjectEquippedItemDetailsSurface(",
|
|
"ProjectEquippedItemObservation(",
|
|
"ProjectEquippedItemTemperatureControls(",
|
|
"ProjectEquippedItemWeaponGroupControls(",
|
|
"AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Compose(",
|
|
"AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Project("
|
|
};
|
|
var forbiddenHits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still owns the equipped-item surface contract locally: " +
|
|
string.Join(", ", forbiddenHits));
|
|
}
|
|
|
|
if (!source.Contains("RenderEquippedItemDetailsSurface(item);", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("InventoryMenu equipped-item click path no longer routes inspection through the Eve surface.");
|
|
}
|
|
|
|
var legacyShellSymbols = new[]
|
|
{
|
|
"public PropertiesPanel PropertiesPanel;",
|
|
"PropertiesPanel.GameManager = GameManager;",
|
|
"PropertiesPanel.gameObject.SetActive(true);",
|
|
"PropertiesPanel.Inspect(item);"
|
|
};
|
|
|
|
var hits = legacyShellSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still owns equipped-item inspection through the old PropertiesPanel shell: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var equippedItemSurfaceBuilder = File.ReadAllText(equippedItemSurfaceBuilderPath);
|
|
var stateProject = File.ReadAllText(stateProjectPath);
|
|
var unityProject = File.ReadAllText(unityProjectPath);
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaRuntimeEquippedItemObservation",
|
|
"public sealed class AetheriaRuntimeEquippedItemSection",
|
|
"public sealed class AetheriaRuntimeEquippedItemMetric",
|
|
"public sealed class AetheriaRuntimeEquippedItemControl",
|
|
"public sealed class AetheriaRuntimeEquippedItemTemperatureControl",
|
|
"public enum AetheriaRuntimeEquippedItemDetailsCommandKind",
|
|
"public readonly struct AetheriaRuntimeEquippedItemDetailsCommand",
|
|
"public static class AetheriaRuntimeEquippedItemDetailsSurfaceCommands",
|
|
"public static bool TryRead(",
|
|
"public const string SurfaceId = \"aetheria.inventory.equipped_item_details\"",
|
|
"public const string Close = \"aetheria.inventory.equipped_item_details.close\"",
|
|
"public const string ToggleOverrideShutdown = \"aetheria.inventory.equipped_item_details.override_shutdown.toggle\"",
|
|
"public const string SetTargetTemperature = \"aetheria.inventory.equipped_item_details.target_temperature.set\"",
|
|
"public const string ToggleWeaponGroup = \"aetheria.inventory.equipped_item_details.weapon_group.toggle\"",
|
|
"var behaviorSections = typedItem == null",
|
|
"BuildControlsCard(overrideShutdown, overrideShutdownLabel, temperatureControls)",
|
|
"weaponGroupControls.Count > 0",
|
|
"ProjectBehaviorSections(",
|
|
"ProjectBehaviorMetric(",
|
|
"AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(",
|
|
"public static AetheriaRuntimeSurfaceDocument Build(",
|
|
"new AetheriaRuntimeSurfaceCommandTemplate(Close",
|
|
"new AetheriaRuntimeSurfaceCommandTemplate(ToggleOverrideShutdown",
|
|
"new AetheriaRuntimeSurfaceCommandTemplate(SetTargetTemperature",
|
|
"new AetheriaRuntimeSurfaceCommandTemplate(ToggleWeaponGroup",
|
|
"ReadInt(request, \"behaviorIndex\", -1)",
|
|
"ReadFloat(request, \"value\", 0f)",
|
|
"ReadInt(request, \"group\", -1)",
|
|
"\"control.text\"",
|
|
"\"inventory.menu\""
|
|
};
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !equippedItemSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared equipped-item detail surface builder no longer owns the equipped-item inspection contract: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
|
|
if (equippedItemSurfaceBuilder.Contains("AetheriaRuntimeEquippedItemDetailsSurfaceState", StringComparison.Ordinal) ||
|
|
equippedItemSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) ||
|
|
equippedItemSurfaceBuilder.Contains("public static AetheriaRuntimeEquippedItemDetailsSurfaceState Compose(", StringComparison.Ordinal) ||
|
|
equippedItemSurfaceBuilder.Contains("public static AetheriaRuntimeEquippedItemDetailsSurfaceState Project(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime equipped item details must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer.");
|
|
}
|
|
|
|
var forbiddenBuilderActionBarSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeEquippedItemActionBarSlot",
|
|
"ActionBarSlots",
|
|
"BindWeaponGroup",
|
|
"ClearActionBarBinding",
|
|
"ReadInt(request, \"slot\", -1)",
|
|
"action_bar"
|
|
};
|
|
var builderActionBarHits = forbiddenBuilderActionBarSymbols
|
|
.Where(symbol => equippedItemSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (builderActionBarHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared equipped-item detail surface still exposes Unity-local action-bar binding concepts: " +
|
|
string.Join(", ", builderActionBarHits));
|
|
}
|
|
|
|
var stateProjectIncludesRuntimePackage = stateProject.Contains(@"Runtime\*.cs", StringComparison.Ordinal);
|
|
if ((!stateProjectIncludesRuntimePackage &&
|
|
!stateProject.Contains("AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs", StringComparison.Ordinal)) ||
|
|
!unityProject.Contains("AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared equipped-item surface builder is not included in both runtime project surfaces.");
|
|
}
|
|
|
|
var actionGameManagerSource = File.ReadAllText(actionGameManagerPath);
|
|
var gameplaySceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs");
|
|
var gameplaySceneWiring = File.Exists(gameplaySceneWiringPath)
|
|
? File.ReadAllText(gameplaySceneWiringPath)
|
|
: throw new InvalidOperationException("Cannot verify action-bar scene wiring; AetheriaUnityGameplaySceneWiring.cs is missing.");
|
|
var actionBarPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityActionBarPresentation.cs");
|
|
var actionBarPresentation = File.Exists(actionBarPresentationPath)
|
|
? File.ReadAllText(actionBarPresentationPath)
|
|
: throw new InvalidOperationException("Cannot verify equipped-item action-bar presentation; AetheriaUnityActionBarPresentation.cs is missing.");
|
|
|
|
var requiredActionBarPresentationSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityActionBarPresentation",
|
|
"public int SlotCount",
|
|
"public string SlotLabel(int slotIndex)",
|
|
"public string BindingLabel(int slotIndex)",
|
|
"public bool TryResolveControlPath(int slotIndex, out string controlPath)",
|
|
"public void RequestBinding(ActionBarSlot slot, DragObject dragAction)",
|
|
"public bool RequestWeaponGroupBinding(int slotIndex, int groupIndex)",
|
|
"public bool ClearBinding(int slotIndex)",
|
|
"public void ApplyLocalBindings()",
|
|
"public void ApplyBindings(",
|
|
"private AetheriaUnityActionBarBinding CreateBindingCommit(",
|
|
"private void SetLocalBinding(AetheriaUnityActionBarBinding binding)"
|
|
};
|
|
|
|
var missingActionBarPresentationSymbols = requiredActionBarPresentationSymbols
|
|
.Where(symbol => !actionBarPresentation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingActionBarPresentationSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Equipped-item action-bar presentation reads must live outside ActionGameManager: " +
|
|
string.Join(", ", missingActionBarPresentationSymbols));
|
|
}
|
|
|
|
var requiredActionBarInjectionSymbols = new[]
|
|
{
|
|
"private readonly AetheriaUnityActionBarPresentation _actionBarPresentation",
|
|
"SceneWiring.ConfigureActionBarPresentation(",
|
|
"_actionBarPresentation,"
|
|
};
|
|
var missingActionBarInjectionSymbols = requiredActionBarInjectionSymbols
|
|
.Where(symbol => !actionGameManagerSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingActionBarInjectionSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must inject action-bar presentation into the inventory shell instead of exposing manager read APIs: " +
|
|
string.Join(", ", missingActionBarInjectionSymbols));
|
|
}
|
|
|
|
var requiredActionBarSceneWiringSymbols = new[]
|
|
{
|
|
"public void ConfigureActionBarPresentation(",
|
|
"actionBarPresentation?.Bind(",
|
|
"resolveActionBarControl);",
|
|
"Inventory?.SetActionBarPresentation(actionBarPresentation)"
|
|
};
|
|
var missingActionBarSceneWiringSymbols = requiredActionBarSceneWiringSymbols
|
|
.Where(symbol => !gameplaySceneWiring.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingActionBarSceneWiringSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityGameplaySceneWiring must bind action-bar presentation into the inventory shell instead of leaving ActionGameManager as the owner: " +
|
|
string.Join(", ", missingActionBarSceneWiringSymbols));
|
|
}
|
|
|
|
var forbiddenActionBarManagerReadSymbols = new[]
|
|
{
|
|
"public int GetActionBarSlotCount(",
|
|
"public string GetActionBarSlotLabel(",
|
|
"public string GetActionBarBindingLabel(",
|
|
"public bool TryResolveActionBarSlotControlPath("
|
|
};
|
|
var actionBarManagerReadHits = forbiddenActionBarManagerReadSymbols
|
|
.Where(symbol => actionGameManagerSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (actionBarManagerReadHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still exposes action-bar presentation reads: " +
|
|
string.Join(", ", actionBarManagerReadHits));
|
|
}
|
|
|
|
var requiredActionBarInventorySymbols = new[]
|
|
{
|
|
"SetActionBarPresentation(AetheriaUnityActionBarPresentation actionBarPresentation)",
|
|
"AetheriaUnityRuntimeClientProvider.Control(\"unity-inventory-menu\")"
|
|
};
|
|
|
|
var missingActionBarInventorySymbols = requiredActionBarInventorySymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingActionBarInventorySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu no longer submits equipped-item action-bar bindings through native Aetheria control access: " +
|
|
string.Join(", ", missingActionBarInventorySymbols));
|
|
}
|
|
|
|
var forbiddenInventoryActionBarDaemonSymbols = new[]
|
|
{
|
|
"operations => operations.SetActionBarBinding(",
|
|
"operations => operations.ClearActionBarBinding(controlPath)",
|
|
"SetActionBarBinding(",
|
|
"ClearActionBarBinding(controlPath)"
|
|
};
|
|
var inventoryActionBarDaemonHits = forbiddenInventoryActionBarDaemonSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (inventoryActionBarDaemonHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still treats local action-bar mapping as a daemon equipped-item command: " +
|
|
string.Join(", ", inventoryActionBarDaemonHits));
|
|
}
|
|
|
|
var forbiddenInventoryActionBarManagerReadSymbols = new[]
|
|
{
|
|
"GameManager.GetActionBarSlotCount(",
|
|
"GameManager.GetActionBarSlotLabel(",
|
|
"GameManager.GetActionBarBindingLabel(",
|
|
"GameManager.TryResolveActionBarSlotControlPath("
|
|
};
|
|
var inventoryActionBarManagerReadHits = forbiddenInventoryActionBarManagerReadSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (inventoryActionBarManagerReadHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still reads action-bar presentation through ActionGameManager: " +
|
|
string.Join(", ", inventoryActionBarManagerReadHits));
|
|
}
|
|
|
|
var forbiddenActionBarManagerSymbols = new[]
|
|
{
|
|
"RequestWeaponGroupActionBarBinding(",
|
|
"RequestClearActionBarBinding(",
|
|
"TryRequestDaemonActionBarBinding(",
|
|
"TryRequestDaemonActionBarBindingClear(",
|
|
"RequestActionBarBinding(",
|
|
"CreateActionBarBinding(",
|
|
"CreateActionBarBindingCommit(",
|
|
"ResolveActionBarClient()?.Operations.SetActionBarBinding("
|
|
};
|
|
var survivingActionBarManagerSymbols = forbiddenActionBarManagerSymbols
|
|
.Where(symbol => actionGameManagerSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingActionBarManagerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns equipped-item action-bar binding authority instead of InventoryMenu's typed facade path: " +
|
|
string.Join(", ", survivingActionBarManagerSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireTradeCargoSelectorUseEveSurface(string root)
|
|
{
|
|
var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs");
|
|
var tradeCargoSelectorSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.cs");
|
|
if (!File.Exists(tradeMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify trade cargo-selector shell; TradeMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(tradeCargoSelectorSurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify trade cargo-selector shell; shared runtime trade cargo selector surface builder is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(tradeMenuPath);
|
|
var tradeCargoSelectorSurfaceBuilder = File.ReadAllText(tradeCargoSelectorSurfaceBuilderPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RenderCargoSelectorSurface(",
|
|
"_cargoSelectorSurfaceModel = BuildTradeCargoSelectorSurfaceModel();",
|
|
"HandleCargoSelectorSurfaceCommand(",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_cargoSelectorSurfaceDocument)",
|
|
"_cargoSelectorSurfaceModel.Document",
|
|
"BuildTradeCargoSelectorSurfaceModel(",
|
|
"AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Build(",
|
|
"_cargoSelectorSurfaceModel?.TryResolve(command.Command, out var selection) == true",
|
|
"ApplyCargoSelection(",
|
|
"new AetheriaRuntimeTradeCargoModelOption(",
|
|
"AetheriaRuntimeTradeCargoTargetKind.DockingBay",
|
|
"AetheriaRuntimeTradeCargoTargetKind.ShipBay",
|
|
"AetheriaRuntimeTradeCargoSelectorSurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeTradeCargoSelectorCommandKind.Close",
|
|
"AetheriaRuntimeTradeCargoSelectorCommandKind.Select",
|
|
"RuntimeState.StationRefit.Latest()",
|
|
"RuntimeState.CurrentDocking.Latest()",
|
|
"SetTargetCargo(",
|
|
"selection.EntityKey",
|
|
"OwnedQuantity",
|
|
"CargoTargets",
|
|
"AetheriaRuntimeStationCargoTargetRow"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu no longer lowers the cargo selector through an Eve surface shell: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"private const string CargoSelectorSurfaceType",
|
|
"private const string CargoSelectorSurfaceId",
|
|
"CloseCargoSelectorCommand",
|
|
"BuildCargoSelectorSurfaceDefinition(",
|
|
"ResolveCargoSelectorSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer()",
|
|
"host.AddComponent<UIDocument>",
|
|
"ContextMenu.AddOption(\"Docking Bay\"",
|
|
"ContextMenu.AddOption($\"{ship.Name} Bay {bay.index+1}\"",
|
|
"private static EveSurfaceComponent",
|
|
"new EveSurfaceComponent(",
|
|
"string.Equals(request.Command, AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Close",
|
|
"_cargoSelectionCommands.TryGetValue(request.Command",
|
|
"private readonly Dictionary<string, (EquippedCargoBay Cargo, string Label)> _cargoSelectionCommands",
|
|
"BuildCargoSelectionCommands(",
|
|
"ProjectTradeCargoSelectorSurfaceState(",
|
|
"ProjectTradeCargoSelectorSurface(",
|
|
"ComposeTradeCargoSelectorSurface(",
|
|
"_cargoSelectorSurfaceModel.State",
|
|
"AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Compose(",
|
|
"_cargoSelectorSurfaceProjection",
|
|
"AetheriaRuntimeObservedDockingState",
|
|
"SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)",
|
|
"private AetheriaUnityPresentationEntityIndex _presentationEntityIndex;",
|
|
"private AetheriaUnityObservedDockingIndex _observedDockingIndex;",
|
|
"TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)",
|
|
"new AetheriaUnityObservedDockingIndex(_presentationEntityIndex)",
|
|
"dockingIndex.StationRefitSnapshot()",
|
|
"dockingIndex.TryResolveCurrentDocking(out docking)",
|
|
"ClearClientCaches()",
|
|
"ResolveClient().State.CurrentDocking()",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeCurrentDockingDocument> _currentDocking",
|
|
".ReactiveCurrentDocking(\"unity-trade\")",
|
|
"docking = _currentDocking.Current",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeStationRefitDocument> _stationRefit",
|
|
".ReactiveStationRefit(\"unity-trade\")",
|
|
"return _stationRefit?.Current",
|
|
"docking.Refit",
|
|
"GameManager.DockedEntity.Children",
|
|
"GameManager.CurrentEntity.Parent.Children",
|
|
"GameManager.DockingBay",
|
|
"GameManager.AvailableEntities()",
|
|
"AetheriaClientReactiveDockingState _reactiveDockingState",
|
|
"AetheriaClientDockingSnapshot _dockingState",
|
|
"private AetheriaClientDockingSnapshot ResolveDockingState()",
|
|
"_cargoSelectorStationRefitEntities",
|
|
"stationRefit.AvailableEntities ?? Array.Empty<AetheriaRuntimeStationRefitEntityOption>()",
|
|
"stationRefit.StationStock ?? Array.Empty<AetheriaRuntimeStationStockItem>()"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu still owns cargo selection through the old context-menu option path: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var forbiddenTradeCountingSymbols = new[]
|
|
{
|
|
"CountAvailablePlayerShips(",
|
|
"CountTargetCargoItems(",
|
|
"ItemsOfType"
|
|
};
|
|
var tradeCountingHits = forbiddenTradeCountingSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (tradeCountingHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu still computes trade ownership locally instead of reading typed station stock facts: " +
|
|
string.Join(", ", tradeCountingHits));
|
|
}
|
|
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeTradeCargoSelectorSurfaceBuilder",
|
|
"public const string SurfaceId = \"aetheria.trade.target_cargo_selector\"",
|
|
"public const string Close = \"aetheria.trade.target_cargo_selector.close\"",
|
|
"public const string DockingBay = \"aetheria.trade.target_cargo_selector.docking_bay\"",
|
|
"public enum AetheriaRuntimeTradeCargoSelectorCommandKind",
|
|
"public readonly struct AetheriaRuntimeTradeCargoSelectorCommand",
|
|
"public static class AetheriaRuntimeTradeCargoSelectorSurfaceCommands",
|
|
"public static bool TryRead(",
|
|
"AetheriaRuntimeTradeCargoTargetOption",
|
|
"AetheriaRuntimeTradeCargoModelOption",
|
|
"AetheriaRuntimeTradeCargoTargetKind",
|
|
"AetheriaRuntimeTradeCargoSelection",
|
|
"AetheriaRuntimeTradeCargoSelectorSurfaceModel",
|
|
"public AetheriaRuntimeSurfaceDocument Document { get; }",
|
|
"public static string ShipBayCommand(",
|
|
"public static AetheriaRuntimeTradeCargoSelectorSurfaceModel Build(",
|
|
"public bool TryResolve(string command, out AetheriaRuntimeTradeCargoSelection selection)",
|
|
"private static AetheriaRuntimeSurfaceDocument Build(",
|
|
"providerKind: \"trade.menu\"",
|
|
"The observing client lists available cargo targets; the shared runtime surface owns the cargo selector contract."
|
|
};
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !tradeCargoSelectorSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime trade cargo selector surface builder no longer owns the trade cargo-selector shell contract: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
|
|
if (tradeCargoSelectorSurfaceBuilder.Contains("AetheriaRuntimeTradeCargoSelectorSurfaceState", StringComparison.Ordinal) ||
|
|
tradeCargoSelectorSurfaceBuilder.Contains("public AetheriaRuntimeTradeCargoSelectorSurfaceState State { get; }", StringComparison.Ordinal) ||
|
|
tradeCargoSelectorSurfaceBuilder.Contains("public static AetheriaRuntimeSurfaceDocument Build(", StringComparison.Ordinal) ||
|
|
tradeCargoSelectorSurfaceBuilder.Contains("public static AetheriaRuntimeTradeCargoSelectorSurfaceModel Compose(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime trade cargo selector builder must return a model with a finished surface document; do not rebuild a shadow SurfaceState projection layer.");
|
|
}
|
|
}
|
|
|
|
static void RequireTradeFilterAndRowActionsUseEveSurface(string root)
|
|
{
|
|
var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs");
|
|
var tradeInteractionSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeTradeInteractionSurfaceBuilder.cs");
|
|
if (!File.Exists(tradeMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify trade filter and row-action shells; TradeMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(tradeInteractionSurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify trade filter and row-action shells; shared runtime trade interaction surface builder is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(tradeMenuPath);
|
|
var tradeInteractionSurfaceBuilder = File.ReadAllText(tradeInteractionSurfaceBuilderPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RenderFilterSurface(",
|
|
"_filterSurfaceModel = BuildTradeFilterSurfaceModel();",
|
|
"HandleFilterSurfaceCommand(",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_filterSurfaceDocument)",
|
|
"_filterSurfaceModel.Document",
|
|
"BuildTradeFilterSurfaceModel(",
|
|
"AetheriaRuntimeTradeInteractionSurfaceBuilder.BuildFilters(",
|
|
"_filterSurfaceModel?.TryResolve(command.Command, out var selection) == true",
|
|
"ExecuteTradeFilterSelection(",
|
|
"new AetheriaRuntimeTradeFilterOption(",
|
|
"RenderRowActionSurface(",
|
|
"HandleRowActionSurfaceCommand(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_rowActionSurfaceDocument)",
|
|
"AetheriaRuntimeTradeInteractionSurfaceBuilder.BuildRowActions(",
|
|
"_rowActionSurfaceModel.Document",
|
|
"_rowActionSurfaceModel?.TryResolve(command.Command, out var selection) == true",
|
|
"new AetheriaRuntimeTradeRowActionOption(index, action.Label)",
|
|
"ShowBuyQuantityDialog(",
|
|
"AetheriaRuntimeTradeInteractionSurfaceCommands.TryReadFilter(request, out var command)",
|
|
"AetheriaRuntimeTradeInteractionSurfaceCommands.TryReadRowAction(request, out var command)",
|
|
"AetheriaRuntimeTradeInteractionCommandKind.Close",
|
|
"AetheriaRuntimeTradeInteractionCommandKind.Select"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu no longer lowers filter and row-action shells through Eve surfaces: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"private const string FilterSurfaceId",
|
|
"private const string RowActionSurfaceId",
|
|
"CloseFilterSurfaceCommand",
|
|
"CloseRowActionSurfaceCommand",
|
|
"BuildFilterSurfaceDefinition(",
|
|
"BuildRowActionSurfaceDefinition(",
|
|
"ResolveFilterSurfaceDocument(",
|
|
"ResolveRowActionSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer()",
|
|
"host.AddComponent<UIDocument>",
|
|
"public ContextMenu ContextMenu;",
|
|
"ContextMenu.Clear();",
|
|
"ContextMenu.AddDropdown(",
|
|
"ContextMenu.AddOption(",
|
|
"ContextMenu.Show();",
|
|
"private static EveSurfaceComponent",
|
|
"new EveSurfaceComponent(",
|
|
"string.Equals(request.Command, AetheriaRuntimeTradeInteractionSurfaceBuilder.CloseFilter",
|
|
"string.Equals(request.Command, AetheriaRuntimeTradeInteractionSurfaceBuilder.CloseRowAction",
|
|
"_filterSurfaceCommands.TryGetValue(request.Command",
|
|
"_filterSurfaceProjection",
|
|
"private readonly Dictionary<string, Action> _filterSurfaceCommands",
|
|
"BuildFilterSurfaceCommands(",
|
|
"ProjectTradeFilterSurfaceState(",
|
|
"ProjectTradeFilterSurface(",
|
|
"ComposeTradeFilterSurface(",
|
|
"AetheriaRuntimeTradeInteractionSurfaceBuilder.ComposeFilters(",
|
|
"_filterSurfaceModel.State",
|
|
"AddTradeFilterGroup(",
|
|
"_rowActionSurfaceCommands.TryGetValue(request.Command",
|
|
"_rowActionSurfaceProjection",
|
|
"private readonly Dictionary<string, Action> _rowActionSurfaceCommands",
|
|
"BuildRowActionSurfaceCommands(",
|
|
"ProjectTradeRowActionSurfaceState(",
|
|
"ProjectRowActions(",
|
|
"AetheriaRuntimeTradeInteractionSurfaceBuilder.ComposeRowActions(",
|
|
"_rowActionSurfaceModel.State"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu still owns filter or row-action behavior through the old context-menu path: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeTradeInteractionSurfaceBuilder",
|
|
"public const string FilterSurfaceId = \"aetheria.trade.filter_selector\"",
|
|
"public const string CloseFilter = \"aetheria.trade.filter_selector.close\"",
|
|
"public const string RowActionSurfaceId = \"aetheria.trade.row_actions\"",
|
|
"public const string CloseRowAction = \"aetheria.trade.row_actions.close\"",
|
|
"public enum AetheriaRuntimeTradeInteractionCommandKind",
|
|
"public readonly struct AetheriaRuntimeTradeInteractionCommand",
|
|
"public static class AetheriaRuntimeTradeInteractionSurfaceCommands",
|
|
"public static bool TryReadFilter(",
|
|
"public static bool TryReadRowAction(",
|
|
"AetheriaRuntimeTradeFilterOption",
|
|
"AetheriaRuntimeTradeFilterSelectionKind",
|
|
"AetheriaRuntimeTradeFilterSelection",
|
|
"AetheriaRuntimeTradeFilterSurfaceModel",
|
|
"AetheriaRuntimeTradeRowActionOption",
|
|
"AetheriaRuntimeTradeRowActionSelection",
|
|
"AetheriaRuntimeTradeRowActionSurfaceModel",
|
|
"AetheriaRuntimeTradeSurfaceGroup",
|
|
"AetheriaRuntimeTradeSurfaceOption",
|
|
"public AetheriaRuntimeSurfaceDocument Document { get; }",
|
|
"public static string HardpointFilterCommand(",
|
|
"public static string RowActionCommand(",
|
|
"public static AetheriaRuntimeTradeFilterSurfaceModel BuildFilters(",
|
|
"public bool TryResolve(string command, out AetheriaRuntimeTradeFilterSelection selection)",
|
|
"public static AetheriaRuntimeTradeRowActionSurfaceModel BuildRowActions(",
|
|
"public bool TryResolve(string command, out AetheriaRuntimeTradeRowActionSelection selection)",
|
|
"private static AetheriaRuntimeSurfaceDocument BuildFilter(",
|
|
"private static AetheriaRuntimeSurfaceDocument BuildRowActions(",
|
|
"providerKind: \"trade.menu\"",
|
|
"The observing client lists available trade filters; the shared runtime surface owns the filter selector contract.",
|
|
"The observing client lists available row actions; the shared runtime surface owns the row action contract."
|
|
};
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !tradeInteractionSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime trade interaction surface builder no longer owns the trade filter/row-action shell contracts: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
|
|
if (tradeInteractionSurfaceBuilder.Contains("AetheriaRuntimeTradeFilterSurfaceState", StringComparison.Ordinal) ||
|
|
tradeInteractionSurfaceBuilder.Contains("AetheriaRuntimeTradeRowActionSurfaceState", StringComparison.Ordinal) ||
|
|
tradeInteractionSurfaceBuilder.Contains("public AetheriaRuntimeTradeFilterSurfaceState State { get; }", StringComparison.Ordinal) ||
|
|
tradeInteractionSurfaceBuilder.Contains("public AetheriaRuntimeTradeRowActionSurfaceState State { get; }", StringComparison.Ordinal) ||
|
|
tradeInteractionSurfaceBuilder.Contains("public static AetheriaRuntimeSurfaceDocument BuildFilter(", StringComparison.Ordinal) ||
|
|
tradeInteractionSurfaceBuilder.Contains("public static AetheriaRuntimeSurfaceDocument BuildRowActions(", StringComparison.Ordinal) ||
|
|
tradeInteractionSurfaceBuilder.Contains("public static AetheriaRuntimeTradeFilterSurfaceModel ComposeFilters(", StringComparison.Ordinal) ||
|
|
tradeInteractionSurfaceBuilder.Contains("public static AetheriaRuntimeTradeRowActionSurfaceModel ComposeRowActions(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime trade interaction builder must return models with finished surface documents; do not rebuild shadow SurfaceState projection layers.");
|
|
}
|
|
}
|
|
|
|
static void RequireTradeItemDetailsUseEveSurface(string root)
|
|
{
|
|
var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs");
|
|
var tradeItemDetailsSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeTradeItemDetailsSurfaceBuilder.cs");
|
|
if (!File.Exists(tradeMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify trade item-details shell; TradeMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(tradeItemDetailsSurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify trade item-details shell; shared runtime trade item details surface builder is missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(tradeMenuPath);
|
|
var tradeItemDetailsSurfaceBuilder = File.ReadAllText(tradeItemDetailsSurfaceBuilderPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RenderTradeItemDetailsSurface(",
|
|
"HandleTradeItemDetailsSurfaceCommand(",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_tradeItemSurfaceDocument)",
|
|
"AetheriaRuntimeTradeItemDetailsSurfaceBuilder.Build(",
|
|
"ResolveManufacturerName(item)",
|
|
"FormatTemperature",
|
|
"AetheriaRuntimeTradeItemDetailsSurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeTradeItemDetailsCommandKind.Close"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu no longer lowers typed item inspection through an Eve surface: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"TradeItemSurfaceId",
|
|
"CloseTradeItemDetailsCommand",
|
|
"BuildTradeItemDetailsSurfaceDefinition(",
|
|
"BuildTradeItemBehaviorCards(",
|
|
"BuildTradeItemBehaviorMetric(",
|
|
"ResolveTradeItemDetailsSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer()",
|
|
"host.AddComponent<UIDocument>",
|
|
"public PropertiesPanel Properties;",
|
|
"OnClick = () => Properties.Inspect(i.TypedItem)",
|
|
"private static EveSurfaceComponent",
|
|
"new EveSurfaceComponent(",
|
|
"string.Equals(request.Command, AetheriaRuntimeTradeItemDetailsSurfaceBuilder.Close",
|
|
"ProjectTradeItemDetailsSurface(",
|
|
"ProjectTradeItemDetailsSurfaceState(",
|
|
"AetheriaRuntimeTradeItemDetailsSurfaceBuilder.Project(",
|
|
"ProjectTradeItemBehaviorSections(",
|
|
"ProjectTradeItemBehaviorMetric("
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu still owns typed item inspection through the old PropertiesPanel path: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeTradeItemDetailsSurfaceBuilder",
|
|
"public const string SurfaceId = \"aetheria.trade.item_details\"",
|
|
"public const string Close = \"aetheria.trade.item_details.close\"",
|
|
"public enum AetheriaRuntimeTradeItemDetailsCommandKind",
|
|
"public readonly struct AetheriaRuntimeTradeItemDetailsCommand",
|
|
"public static class AetheriaRuntimeTradeItemDetailsSurfaceCommands",
|
|
"public static bool TryRead(",
|
|
"AetheriaRuntimeTradeItemSection",
|
|
"AetheriaRuntimeTradeItemMetric",
|
|
"var itemName = item?.Name ?? \"\"",
|
|
"var behaviorSections = Array.Empty<AetheriaRuntimeTradeItemSection>()",
|
|
"var hasEquipmentStatus = !string.IsNullOrWhiteSpace(durability)",
|
|
"ProjectBehaviorSections(",
|
|
"ProjectBehaviorMetric(",
|
|
"public static AetheriaRuntimeSurfaceDocument Build(",
|
|
"providerKind: \"trade.menu\"",
|
|
"The observing client supplies the selected market row; the shared runtime surface owns trade item inspection layout."
|
|
};
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !tradeItemDetailsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime trade item details surface builder no longer owns the trade item-details shell contract: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
|
|
if (tradeItemDetailsSurfaceBuilder.Contains("AetheriaRuntimeTradeItemDetailsSurfaceState", StringComparison.Ordinal) ||
|
|
tradeItemDetailsSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) ||
|
|
tradeItemDetailsSurfaceBuilder.Contains("public static AetheriaRuntimeTradeItemDetailsSurfaceState Project(", StringComparison.Ordinal) ||
|
|
tradeItemDetailsSurfaceBuilder.Contains("public static AetheriaRuntimeTradeItemDetailsSurfaceState Compose(", StringComparison.Ordinal) ||
|
|
tradeItemDetailsSurfaceBuilder.Contains("private static AetheriaRuntimeTradeItemDetailsSurfaceState ProjectState(", StringComparison.Ordinal) ||
|
|
tradeItemDetailsSurfaceBuilder.Contains("Build(ProjectState(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime trade item details must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer.");
|
|
}
|
|
}
|
|
|
|
static void RequireTradeItemValuesUseRuntimeQueries(string root)
|
|
{
|
|
var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs");
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var catalogSnapshotPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeCatalogSnapshot.cs");
|
|
var tradeQueriesPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeDaemonTradeItemQueries.cs");
|
|
if (!File.Exists(tradeMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify trade item value; TradeMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(inventoryPanelPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify typed catalog item enum accessors; InventoryPanel.cs is missing.");
|
|
}
|
|
if (!File.Exists(inventoryMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify typed inventory document vocabulary; InventoryMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(catalogSnapshotPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify typed catalog item enum accessors; AetheriaRuntimeCatalogSnapshot.cs is missing.");
|
|
}
|
|
if (!File.Exists(tradeQueriesPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify trade item value; shared runtime trade item queries are missing.");
|
|
}
|
|
|
|
var tradeMenu = File.ReadAllText(tradeMenuPath);
|
|
var inventoryPanel = File.ReadAllText(inventoryPanelPath);
|
|
var inventoryMenu = File.ReadAllText(inventoryMenuPath);
|
|
var catalogSnapshot = File.ReadAllText(catalogSnapshotPath);
|
|
var tradeQueries = File.ReadAllText(tradeQueriesPath);
|
|
var requiredTradeMenuSymbols = new[]
|
|
{
|
|
"TradeItemValue(stock, typedItem)",
|
|
"TradeItemCommit(",
|
|
"CatalogSnapshot()?.TradeValueSettings",
|
|
"AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue(",
|
|
"AetheriaRuntimeTradeItemValue TradeValue",
|
|
"public int Price => TradeValue.Price",
|
|
"public string TierColorHex => TradeValue.TierColorHex",
|
|
"x.TypedItem.TryGetSimpleCommodityCategory(",
|
|
"x.TypedItem.TryGetCompoundCommodityCategory(",
|
|
"x.TypedItem.TryGetHardpointType("
|
|
};
|
|
var missingTradeMenuSymbols = requiredTradeMenuSymbols
|
|
.Where(symbol => !tradeMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTradeMenuSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu no longer reads trade item price/tier values through shared daemon runtime queries: " +
|
|
string.Join(", ", missingTradeMenuSymbols));
|
|
}
|
|
|
|
var forbiddenTradeMenuSymbols = new[]
|
|
{
|
|
"GameManager.ItemManager.GetTier",
|
|
"GameManager.ItemManager.GameplaySettings.QualityPriceModifier",
|
|
"_itemManager.GameplaySettings.QualityPriceModifier",
|
|
"GameManager.ObservedTradeValueSettings()",
|
|
"AetheriaUnityProjectionSettings.TradeValueSettings",
|
|
"private readonly ItemManager _itemManager",
|
|
"new TradeRow(item, FindTypedTradeItem(item), GameManager.ItemManager)",
|
|
"TryGetTypedSimpleCommodityCategory(",
|
|
"TryGetTypedCompoundCommodityCategory(",
|
|
"TryGetTypedHardpoint("
|
|
};
|
|
var tradeMenuHits = forbiddenTradeMenuSymbols
|
|
.Where(symbol => tradeMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (tradeMenuHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu still asks Unity ItemManager for trade item value: " +
|
|
string.Join(", ", tradeMenuHits));
|
|
}
|
|
|
|
var requiredCatalogItemEnumSymbols = new[]
|
|
{
|
|
"public bool TryGetHardpointType<TEnum>(out TEnum hardpointType) where TEnum : struct",
|
|
"public bool TryGetSimpleCommodityCategory<TEnum>(out TEnum category) where TEnum : struct",
|
|
"public bool TryGetCompoundCommodityCategory<TEnum>(out TEnum category) where TEnum : struct",
|
|
"public AetheriaRuntimeCatalogItem? FindItem<T>(T? item, Func<T, string?> itemKey) where T : class",
|
|
"Enum.TryParse(HardpointType, true, out hardpointType)",
|
|
"Enum.TryParse(SimpleCommodityCategory, true, out category)",
|
|
"Enum.TryParse(CompoundCommodityCategory, true, out category)"
|
|
};
|
|
var missingCatalogItemEnumSymbols = requiredCatalogItemEnumSymbols
|
|
.Where(symbol => !catalogSnapshot.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingCatalogItemEnumSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeCatalogItem must own typed enum accessors for catalog category/hardpoint fields: " +
|
|
string.Join(", ", missingCatalogItemEnumSymbols));
|
|
}
|
|
|
|
if (inventoryPanel.Contains("TryGetTypedHardpointType(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel must not parse hardpoint classification locally; daemon-owned inventory surfaces consume typed catalog state.");
|
|
}
|
|
|
|
if (inventoryPanel.Contains("inventory projection", StringComparison.OrdinalIgnoreCase) ||
|
|
inventoryMenu.Contains("inventory projection", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory UI must describe direct reactive inventory document access as typed documents, not inventory projection chaff.");
|
|
}
|
|
|
|
var requiredQuerySymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeDaemonTradeItemQueries",
|
|
"public readonly struct AetheriaRuntimeTradeItemValue",
|
|
"public sealed class AetheriaRuntimeTradeValueSettings",
|
|
"public readonly struct AetheriaRuntimeItemRarityTier",
|
|
"public readonly struct AetheriaRuntimeExponentialLerp",
|
|
"public static AetheriaRuntimeTradeItemValue TradeItemValue(",
|
|
"settings.QualityPriceModifier.Evaluate(quality.Value) * typedItem.Price",
|
|
"SelectTier(settings.Tiers, quality.Value)",
|
|
"AetheriaRuntimeDaemonItemStatQueries.ItemCommit("
|
|
};
|
|
var missingQuerySymbols = requiredQuerySymbols
|
|
.Where(symbol => !tradeQueries.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingQuerySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime trade item queries no longer own price/tier value: " +
|
|
string.Join(", ", missingQuerySymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireItemTierValuesUseRuntimeQueries(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var zoneRendererPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs");
|
|
var tradeQueriesPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeDaemonTradeItemQueries.cs");
|
|
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify item tier value; ActionGameManager.cs is missing.");
|
|
var inventoryMenu = File.Exists(inventoryMenuPath)
|
|
? File.ReadAllText(inventoryMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify item tier value; InventoryMenu.cs is missing.");
|
|
var zoneRenderer = File.Exists(zoneRendererPath)
|
|
? File.ReadAllText(zoneRendererPath)
|
|
: throw new InvalidOperationException("Cannot verify item tier value; ZoneRenderer.cs is missing.");
|
|
var tradeQueries = File.Exists(tradeQueriesPath)
|
|
? File.ReadAllText(tradeQueriesPath)
|
|
: throw new InvalidOperationException("Cannot verify item tier value; runtime trade item queries are missing.");
|
|
|
|
if (actionGameManager.Contains("ObservedItemTier(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still exposes item tier value as a Unity ItemManager bridge.");
|
|
}
|
|
|
|
var forbiddenUnityTierSymbols = new[]
|
|
{
|
|
"GameManager.ObservedItemTier",
|
|
"ItemManager.GetTier(",
|
|
"GameManager.ItemManager.GetTier"
|
|
};
|
|
var hits = new[] { inventoryMenuPath, zoneRendererPath }
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenUnityTierSymbols.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 UI/rendering still projects item tiers through ItemManager instead of shared runtime queries: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
if (!inventoryMenu.Contains("FormatItemTier(", StringComparison.Ordinal) ||
|
|
!inventoryMenu.Contains("AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue(", StringComparison.Ordinal) ||
|
|
!inventoryMenu.Contains("tradeValue.TierName", StringComparison.Ordinal) ||
|
|
!inventoryMenu.Contains("tradeValue.Upgrades", StringComparison.Ordinal) ||
|
|
!zoneRenderer.Contains("AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue(", StringComparison.Ordinal) ||
|
|
!zoneRenderer.Contains("tradeValue.TierColorHex", StringComparison.Ordinal) ||
|
|
!tradeQueries.Contains("public string TierName { get; }", StringComparison.Ordinal) ||
|
|
!tradeQueries.Contains("public string TierColorHex { get; }", StringComparison.Ordinal) ||
|
|
!tradeQueries.Contains("public int Upgrades { get; }", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Item tier labels and pickup colors must use shared runtime trade item query results.");
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryDropdownUseEveSurface(string root)
|
|
{
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var inventoryDropdownSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs");
|
|
var inventoryDropdownSurfaceDocumentsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs");
|
|
if (!File.Exists(inventoryPanelPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify inventory dropdown shell; InventoryPanel.cs is missing.");
|
|
}
|
|
if (!File.Exists(inventoryMenuPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify inventory dropdown shell; InventoryMenu.cs is missing.");
|
|
}
|
|
if (!File.Exists(inventoryDropdownSurfaceBuilderPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify inventory dropdown shell; shared runtime inventory dropdown surface builder is missing.");
|
|
}
|
|
if (!File.Exists(inventoryDropdownSurfaceDocumentsPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify inventory dropdown shell; shared runtime inventory dropdown surface documents are missing.");
|
|
}
|
|
|
|
var source = File.ReadAllText(inventoryPanelPath);
|
|
var inventoryMenu = File.ReadAllText(inventoryMenuPath);
|
|
var inventoryDropdownSurfaceBuilder = File.ReadAllText(inventoryDropdownSurfaceBuilderPath) + "\n" +
|
|
File.ReadAllText(inventoryDropdownSurfaceDocumentsPath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RenderDropdownSurface(",
|
|
"ResolveDropdownSurface(",
|
|
"_dropdownSurface = ResolveDropdownSurface();",
|
|
"BuildDropdownSurfaceRequest(",
|
|
"RuntimeState.InventoryDropdownSurface(BuildDropdownSurfaceRequest()).Latest()",
|
|
"HandleDropdownSurfaceCommand(",
|
|
"ExecuteDropdownSelection(",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"AetheriaEveUnitySurfaceHost.Hide(_dropdownSurfaceDocument)",
|
|
"_dropdownSurface,",
|
|
"AetheriaRuntimeInventoryDropdownSurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeInventoryDropdownCommandKind.Close",
|
|
"AetheriaRuntimeInventoryDropdownCommandKind.Select",
|
|
"TryResolveDropdownSelection(request, command.Command, out var selection)",
|
|
"PayloadValue(request, \"selectionKind\")",
|
|
"AetheriaRuntimeInventoryDropdownSelectionKind.EntityBay",
|
|
"AetheriaRuntimeInventoryDropdownSelectionKind.Loadout",
|
|
"selection.EntityKey",
|
|
"_displayedEntityKey",
|
|
"_displayedCargoEntityKey",
|
|
"TryResolveStationRefitEntity(string entityKey",
|
|
"SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)",
|
|
"_presentationEntityIndex.TryGetPresentationEntityByRecordKey",
|
|
"_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey",
|
|
"RenderInventoryPanelSurface()",
|
|
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
|
|
"HandleInventoryPanelSurfaceCommand",
|
|
"StationRefitSnapshot",
|
|
"LoadoutRestoreOptions"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel no longer lowers the dropdown shell through an Eve surface: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"DropdownSurfaceId",
|
|
"CloseDropdownSurfaceCommand",
|
|
"SaveLoadoutCommand",
|
|
"BuildDropdownSurfaceDefinition(",
|
|
"private const string DropdownSurfaceType",
|
|
"ResolveDropdownSurfaceDocument(",
|
|
"new EveUiToolkitSurfaceLowerer()",
|
|
"host.AddComponent<UIE.UIDocument>",
|
|
"ContextMenu.AddDropdown(entity.Name",
|
|
"ContextMenu.AddOption(GameManager.DockingBay.Name",
|
|
"ContextMenu.AddOption(\"Save Loadout\"",
|
|
"ContextMenu.AddDropdown(\"Restore Loadout\"",
|
|
"ContextMenu.Show();",
|
|
"GameManager.ObservedAvailableEntities()",
|
|
"GameManager.TryGetObservedDockingBay(",
|
|
"string.Equals(request.Command, AetheriaRuntimeInventoryDropdownSurfaceBuilder.Close",
|
|
"_dropdownCommands.TryGetValue(request.Command",
|
|
"private readonly Dictionary<string, Action> _dropdownCommands",
|
|
"BuildDropdownCommands(",
|
|
"ProjectDropdownSurfaceState(",
|
|
"ProjectDropdownSurface(",
|
|
"ComposeDropdownSurface(",
|
|
"_dropdownSurfaceModel.State",
|
|
"_dropdownSurfaceModel",
|
|
"AetheriaRuntimeInventoryDropdownSurfaceModel",
|
|
"AetheriaRuntimeInventoryDropdownSurfaceBuilder.Compose(",
|
|
"_dropdownSurfaceProjection",
|
|
"GameManager.DockingBay",
|
|
"GameManager.AvailableEntities()",
|
|
"GameManager.LoadoutTemplates"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still owns dropdown behavior through the old ContextMenu path: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
if (!inventoryDropdownSurfaceBuilder.Contains("public string EntityKey { get; }", StringComparison.Ordinal) ||
|
|
!inventoryDropdownSurfaceBuilder.Contains("BuildFromDocuments(", StringComparison.Ordinal) ||
|
|
!inventoryDropdownSurfaceBuilder.Contains("entityKey: entity.EntityKey", StringComparison.Ordinal) ||
|
|
!source.Contains("TryResolveStationRefitEntity(selection.EntityKey", StringComparison.Ordinal) ||
|
|
!source.Contains("TryResolveCurrentDockingBay(out var dockingBay)", StringComparison.Ordinal) ||
|
|
!source.Contains("_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey", StringComparison.Ordinal) ||
|
|
!source.Contains("StationRefitSnapshot", StringComparison.Ordinal) ||
|
|
!source.Contains("RuntimeState.InventoryDropdownSurface(BuildDropdownSurfaceRequest()).Latest()", StringComparison.Ordinal) ||
|
|
!source.Contains("RuntimeState.StationRefit.Latest()", StringComparison.Ordinal) ||
|
|
source.Contains("TryResolveObservedDockingIndex(out var dockingIndex)", StringComparison.Ordinal) ||
|
|
source.Contains("dockingIndex.TryResolveCurrentDockingBay", StringComparison.Ordinal) ||
|
|
source.Contains("var hasDockingBay = TryGetTypedCurrentDockingBayFacade", StringComparison.Ordinal) ||
|
|
source.Contains("TryResolveCurrentDockingBayFacade", StringComparison.Ordinal) ||
|
|
source.Contains("ResolveDockingState()?.CurrentDocking", StringComparison.Ordinal) ||
|
|
source.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel dropdown must carry typed entity identity through the shared Eve surface and must project docking display state from StationRefitAsync docking-bay rows.");
|
|
}
|
|
|
|
if (!inventoryMenu.Contains("TryResolveCurrentEntity(out var currentEntity)", StringComparison.Ordinal) ||
|
|
!inventoryMenu.Contains("TryResolveCurrentDockingBay(out var dockingBay)", StringComparison.Ordinal) ||
|
|
!inventoryMenu.Contains("_presentationEntityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out currentEntity)", StringComparison.Ordinal) ||
|
|
!inventoryMenu.Contains("_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey", StringComparison.Ordinal) ||
|
|
!inventoryMenu.Contains("currentEntityKey = CurrentEntitySnapshot()?.EntityKey ?? \"\"", StringComparison.Ordinal) ||
|
|
!inventoryMenu.Contains("currentEntity = CurrentEntitySnapshot();", StringComparison.Ordinal) ||
|
|
!inventoryMenu.Contains("StationRefitSnapshot", StringComparison.Ordinal) ||
|
|
inventoryMenu.Contains("TryResolveObservedDockingIndex(out var dockingIndex)", StringComparison.Ordinal) ||
|
|
inventoryMenu.Contains("dockingIndex.TryResolveCurrentDockingBay", StringComparison.Ordinal) ||
|
|
inventoryMenu.Contains("TryGetTypedCurrentDockingBayFacade", StringComparison.Ordinal) ||
|
|
inventoryMenu.Contains("TryResolveCurrentEntityFacade", StringComparison.Ordinal) ||
|
|
inventoryMenu.Contains("ResolveDockingState()?.CurrentDocking", StringComparison.Ordinal) ||
|
|
inventoryMenu.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal) ||
|
|
inventoryMenu.Contains("GameManager.TryGetObservedDockingBay(", StringComparison.Ordinal) ||
|
|
inventoryMenu.Contains("GameManager.DockingBay", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu must resolve typed current entity/refit state through managed current documents before adapting docking state to Unity objects.");
|
|
}
|
|
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeInventoryDropdownSurfaceBuilder",
|
|
"public const string SurfaceId = \"aetheria.inventory.panel.dropdown\"",
|
|
"public const string Close = \"aetheria.inventory.panel.dropdown.close\"",
|
|
"public const string SaveLoadout = \"aetheria.inventory.panel.dropdown.save_loadout\"",
|
|
"public const string DockingBay = \"aetheria.inventory.panel.dropdown.docking_bay\"",
|
|
"public enum AetheriaRuntimeInventoryDropdownCommandKind",
|
|
"public readonly struct AetheriaRuntimeInventoryDropdownCommand",
|
|
"public static class AetheriaRuntimeInventoryDropdownSurfaceCommands",
|
|
"public static bool TryRead(",
|
|
"AetheriaRuntimeInventoryDropdownGroup",
|
|
"AetheriaRuntimeInventoryDropdownOption",
|
|
"AetheriaRuntimeInventoryDropdownEntityOption",
|
|
"AetheriaRuntimeInventoryDropdownBayOption",
|
|
"AetheriaRuntimeInventoryDropdownLoadoutOption",
|
|
"AetheriaRuntimeInventoryDropdownSelectionKind",
|
|
"public string EntityKey { get; }",
|
|
"entityKey: entity.EntityKey",
|
|
"public static AetheriaRuntimeSurfaceDocument Build(",
|
|
"selectionKind",
|
|
"templateIndex",
|
|
"public static string EntityEquipmentCommand(",
|
|
"public static string EntityBayCommand(",
|
|
"public static string EntityCommand(",
|
|
"public static string LoadoutCommand(",
|
|
"private static AetheriaRuntimeSurfaceDocument Build(",
|
|
"providerKind: \"inventory.panel\"",
|
|
"The observing client lists available inventory navigation; the shared runtime surface owns the dropdown contract."
|
|
};
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !inventoryDropdownSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime inventory dropdown surface builder no longer owns the inventory dropdown shell contract: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
|
|
if (inventoryDropdownSurfaceBuilder.Contains("AetheriaRuntimeInventoryDropdownSurfaceState", StringComparison.Ordinal) ||
|
|
inventoryDropdownSurfaceBuilder.Contains("public AetheriaRuntimeInventoryDropdownSurfaceState State { get; }", StringComparison.Ordinal) ||
|
|
inventoryDropdownSurfaceBuilder.Contains("AetheriaRuntimeInventoryDropdownSurfaceModel", StringComparison.Ordinal) ||
|
|
inventoryDropdownSurfaceBuilder.Contains("public static AetheriaRuntimeInventoryDropdownSurfaceModel Compose(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime inventory dropdown builder must return the finished surface document and carry typed selections in command payload; do not rebuild a shadow SurfaceState/model projection layer.");
|
|
}
|
|
}
|
|
|
|
static void RequireStationRefitDockingBaysUseTypedDocuments(string root)
|
|
{
|
|
var runtimeDocumentsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeGameViewportDocuments.cs");
|
|
var runtimeDocumentsFactoryPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeGameDocuments.cs");
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
|
|
var runtimeDocuments = File.Exists(runtimeDocumentsPath)
|
|
? File.ReadAllText(runtimeDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify station-refit docking-bay document; runtime viewport documents are missing.");
|
|
var runtimeDocumentsFactory = File.Exists(runtimeDocumentsFactoryPath)
|
|
? File.ReadAllText(runtimeDocumentsFactoryPath)
|
|
: throw new InvalidOperationException("Cannot verify station-refit docking-bay document; runtime document factory is missing.");
|
|
var inventoryPanel = File.Exists(inventoryPanelPath)
|
|
? File.ReadAllText(inventoryPanelPath)
|
|
: throw new InvalidOperationException("Cannot verify station-refit docking-bay document; InventoryPanel.cs is missing.");
|
|
var inventoryMenu = File.Exists(inventoryMenuPath)
|
|
? File.ReadAllText(inventoryMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify station-refit docking-bay document; InventoryMenu.cs is missing.");
|
|
|
|
var requiredDocumentSymbols = new[]
|
|
{
|
|
"public IReadOnlyList<AetheriaRuntimeStationDockingBayRow> DockingBays",
|
|
"public sealed class AetheriaRuntimeStationDockingBayRow",
|
|
"public int DockingBayIndex",
|
|
"public string OccupiedEntityKey",
|
|
"public int OccupiedEntityIndex",
|
|
"public string OccupiedEntityName",
|
|
"public string OccupiedHullItemKey",
|
|
"public bool OccupiedByCurrentEntity",
|
|
"public IReadOnlyList<AetheriaRuntimeStationStockItem> CargoItems",
|
|
"public IReadOnlyList<AetheriaRuntimeStationCargoTargetRow> CargoTargets",
|
|
"public sealed class AetheriaRuntimeStationCargoTargetRow",
|
|
"public AetheriaRuntimeTradeCargoTargetKind Kind",
|
|
"public int TargetIndex",
|
|
"public string EntityKey",
|
|
"public int BayIndex",
|
|
"public int Price",
|
|
"public bool CanAfford",
|
|
"public int OwnedQuantity"
|
|
};
|
|
var missingDocumentSymbols = requiredDocumentSymbols
|
|
.Where(symbol => !runtimeDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"StationRefitAsync no longer exposes typed docking-bay rows: " +
|
|
string.Join(", ", missingDocumentSymbols));
|
|
}
|
|
|
|
var requiredDocumentFactorySymbols = new[]
|
|
{
|
|
"var dockingBays = parent == null",
|
|
"DockingBays = dockingBays",
|
|
"ProjectStationDockingBays(context, parent, currentEntityIndex)",
|
|
"private static IReadOnlyList<AetheriaRuntimeStationDockingBayRow> ProjectStationDockingBays(",
|
|
"dockParent.DockingBays",
|
|
"dockParent.DockingBayAssignments",
|
|
"dockParent.DockingBayContents",
|
|
"OccupiedEntityIndex = assignedEntity?.EntityIndex ?? assignedEntityIndex",
|
|
"OccupiedEntityKey = assignedEntity == null",
|
|
"AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, context.Zone.ZoneIndex, assignedEntity.EntityIndex)",
|
|
"OccupiedEntityName = assignedEntity?.Name",
|
|
"OccupiedHullItemKey = assignedEntity?.HullItemKey",
|
|
"OccupiedByCurrentEntity = assignedEntityIndex >= 0 && assignedEntityIndex == currentEntityIndex",
|
|
"CargoItems = dockingBayIndex < contents.Count",
|
|
"StationStock(contents[dockingBayIndex], dockingBayIndex)",
|
|
"var cargoTargets = parent == null",
|
|
"CargoTargets = cargoTargets",
|
|
"ProjectStationCargoTargets(parentKey, dockingBayIndex, dockingBays, availableEntities)",
|
|
"private static IReadOnlyList<AetheriaRuntimeStationCargoTargetRow> ProjectStationCargoTargets(",
|
|
"AetheriaRuntimeTradeCargoTargetKind.DockingBay",
|
|
"AetheriaRuntimeTradeCargoTargetKind.ShipBay",
|
|
"currentDockingBay?.CargoItems",
|
|
"entity.CargoItems ?? Array.Empty<AetheriaRuntimeStationStockItem>()",
|
|
"StationStockTradeFacts(",
|
|
"private static IReadOnlyList<AetheriaRuntimeStationStockItem> StationStockTradeFacts(",
|
|
"private static int StationStockPrice(",
|
|
"private static int CountStationOwnedQuantity(",
|
|
"AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue(",
|
|
"CanAfford = price >= 0 && credits >= price",
|
|
"OwnedQuantity = CountStationOwnedQuantity"
|
|
};
|
|
var missingDocumentFactorySymbols = requiredDocumentFactorySymbols
|
|
.Where(symbol => !runtimeDocumentsFactory.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingDocumentFactorySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"StationRefitAsync docking-bay rows are no longer derived from daemon station state: " +
|
|
string.Join(", ", missingDocumentFactorySymbols));
|
|
}
|
|
|
|
if (runtimeDocumentsFactory.Contains("private static string BuildEntityKey(", StringComparison.Ordinal) ||
|
|
runtimeDocumentsFactory.Contains("private static bool TryParseEntityKey(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime document factory reintroduced local entity key folklore; use AetheriaRuntimeRunCheckpointCommit entity key helpers.");
|
|
}
|
|
|
|
var requiredPanelSymbols = new[]
|
|
{
|
|
"StationRefitSnapshot",
|
|
"RuntimeState.StationRefit.Latest()",
|
|
"_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey"
|
|
};
|
|
var missingPanelSymbols = requiredPanelSymbols
|
|
.Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingPanelSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel must resolve remaining docking presentation from managed station-refit identity before touching Unity docking objects: " +
|
|
string.Join(", ", missingPanelSymbols));
|
|
}
|
|
|
|
var requiredMenuSymbols = new[]
|
|
{
|
|
"TryResolveCurrentDockingBayRow(out AetheriaRuntimeStationDockingBayRow dockingBay)",
|
|
"AetheriaRuntimeStationDockingBayRow dockingBay",
|
|
"StationRefitSnapshot",
|
|
"refit.DockingBays ?? Array.Empty<AetheriaRuntimeStationDockingBayRow>()"
|
|
};
|
|
var missingMenuSymbols = requiredMenuSymbols
|
|
.Where(symbol => !inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingMenuSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu must validate current docking-bay display through StationRefitAsync typed docking-bay rows: " +
|
|
string.Join(", ", missingMenuSymbols));
|
|
}
|
|
|
|
var forbiddenMenuSymbols = new[]
|
|
{
|
|
"GameManager.TryGetObservedDockingBay(",
|
|
"GameManager.DockingBay"
|
|
};
|
|
var forbiddenHits = forbiddenMenuSymbols
|
|
.Where(symbol => inventoryPanel.Contains(symbol, StringComparison.Ordinal) || inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (forbiddenHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory docking-bay display still asks ActionGameManager for broad docking state instead of typed station-refit rows: " +
|
|
string.Join(", ", forbiddenHits));
|
|
}
|
|
}
|
|
|
|
static void RequireNoDeadPopupShells(string root)
|
|
{
|
|
var deletedShellPaths = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "ContextMenu.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "ContextMenu.cs.meta"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "ContextMenuOption.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "ContextMenuOption.cs.meta"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Context Menu.prefab"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Context Menu.prefab.meta"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Context Menu Option.prefab"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Context Menu Option.prefab.meta")
|
|
};
|
|
|
|
var survivingShells = deletedShellPaths
|
|
.Where(File.Exists)
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
|
|
if (survivingShells.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The dead ContextMenu popup shell still survives in source or prefab assets: " +
|
|
string.Join(", ", survivingShells));
|
|
}
|
|
|
|
var sourceChecks = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs")] = new[]
|
|
{
|
|
"public ContextMenu ContextMenu;"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs")] = new[]
|
|
{
|
|
"public ContextMenu Context;",
|
|
"public DropdownMenu Dropdown;"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs")] = new[]
|
|
{
|
|
"public PropertiesPanel Properties;",
|
|
"Properties.gameObject.SetActive(false);"
|
|
}
|
|
};
|
|
|
|
var survivingSymbols = sourceChecks
|
|
.SelectMany(entry =>
|
|
{
|
|
var text = File.Exists(entry.Key) ? File.ReadAllText(entry.Key) : "";
|
|
return entry.Value
|
|
.Where(symbol => text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, entry.Key)}: {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (survivingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Renderer-local popup shell authority still survives in live source: " +
|
|
string.Join("; ", survivingSymbols));
|
|
}
|
|
|
|
var serializedChecks = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Assets", "Scenes", "ARPG.unity")] = new[]
|
|
{
|
|
"ContextMenu: {",
|
|
"propertyPath: ContextMenu",
|
|
"Context: {fileID:",
|
|
"Properties: {fileID:"
|
|
},
|
|
[Path.Combine(root, "Assets", "Prefabs", "UI", "Inventory.prefab")] = new[]
|
|
{
|
|
"ContextMenu: {"
|
|
}
|
|
};
|
|
|
|
var survivingSerializedShells = serializedChecks
|
|
.SelectMany(entry =>
|
|
{
|
|
var text = File.Exists(entry.Key) ? File.ReadAllText(entry.Key) : "";
|
|
return entry.Value
|
|
.Where(symbol => text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, entry.Key)}: {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (survivingSerializedShells.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Scene or prefab YAML still serializes the deleted popup shell or orphan PropertiesPanel links: " +
|
|
string.Join("; ", survivingSerializedShells));
|
|
}
|
|
}
|
|
|
|
static void RequirePlayerSettingsEveSurface(string root)
|
|
{
|
|
var surfaceDocumentsPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs");
|
|
var bridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs");
|
|
var providerPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs");
|
|
var daemonHostPath = Path.Combine(root, "Aetheria.State.Daemon", "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(surfaceDocumentsPath))
|
|
{
|
|
throw new InvalidOperationException("Player settings Eve surface document is missing.");
|
|
}
|
|
|
|
var surfaceDocuments = File.ReadAllText(surfaceDocumentsPath);
|
|
var requiredSurfaceDocumentSymbols = new[]
|
|
{
|
|
"AetheriaRuntimePlayerSettingsCommands.SurfaceId",
|
|
"AetheriaRuntimePlayerSettingsSurfaceBuilder.Build",
|
|
"settings.ActiveRunKey"
|
|
};
|
|
|
|
var missingSurfaceDocumentSymbols = requiredSurfaceDocumentSymbols
|
|
.Where(symbol => !surfaceDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSurfaceDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Player settings Eve surface document is missing required typed controls: " +
|
|
string.Join(", ", missingSurfaceDocumentSymbols));
|
|
}
|
|
|
|
if (surfaceDocuments.Contains("AetheriaRuntimePlayerSettingsSurfaceState", StringComparison.Ordinal) ||
|
|
surfaceDocuments.Contains("new AetheriaRuntimePlayerSettingsSurfaceState(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Player settings Eve surface document should pass typed settings directly into the shared builder, not repack them into a shadow surface state.");
|
|
}
|
|
|
|
var runtimeVerseClientPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeVerseClient.cs");
|
|
var runtimeEveCommandClientPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeEveCommandClient.cs");
|
|
var runtimeUiPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaUi.cs");
|
|
var runtimeVerseClient = File.Exists(runtimeVerseClientPath)
|
|
? File.ReadAllText(runtimeVerseClientPath)
|
|
: throw new InvalidOperationException("Managed runtime Verse client is missing.");
|
|
var runtimeEveCommandClient = File.Exists(runtimeEveCommandClientPath)
|
|
? File.ReadAllText(runtimeEveCommandClientPath)
|
|
: throw new InvalidOperationException("Managed runtime Eve command client is missing.");
|
|
var runtimeUi = File.Exists(runtimeUiPath)
|
|
? File.ReadAllText(runtimeUiPath)
|
|
: throw new InvalidOperationException("Managed runtime UI command facade 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 (!runtimeUi.Contains("public async Task<CultMeshOperationReceipt> SurfaceCommandAsync(", StringComparison.Ordinal) ||
|
|
!runtimeUi.Contains(".SubmitKnownSurfaceCommandAsync(request, clientId, flush)", StringComparison.Ordinal) ||
|
|
!runtimeVerseClient.Contains("SubmitKnownSurfaceCommandAsync(", StringComparison.Ordinal) ||
|
|
!runtimeVerseClient.Contains("AetheriaRuntimeEveCommandClient.TryCreateKnownSurfaceCommand(request, out var envelope)", StringComparison.Ordinal) ||
|
|
!runtimeVerseClient.Contains("Database.PutAsync(AetheriaRuntimeVerseRecordKeys.EveCommand(command.CommandId), command)", StringComparison.Ordinal) ||
|
|
!runtimeEveCommandClient.Contains("AetheriaRuntimePlayerSettingsCommands.SurfaceId", StringComparison.Ordinal) ||
|
|
!runtimeEveCommandClient.Contains("AetheriaRuntimePlayerSettingsCommands.IsKnown(command)", StringComparison.Ordinal) ||
|
|
!runtimeEveCommandClient.Contains("CreatePlayerSettingsCommand(CommandKindForSurface(request), request, clientId)", StringComparison.Ordinal) ||
|
|
!runtimeEveCommandClient.Contains("playerSettings: ReadPlayerSettingsBody(request)", StringComparison.Ordinal) ||
|
|
!runtimeEveCommandClient.Contains("invocation: request.Operation", StringComparison.Ordinal) ||
|
|
!runtimeEveCommandClient.Contains("payload: request.Payload", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Managed Eve command path no longer submits typed player-settings surface commands through CultMesh.");
|
|
}
|
|
|
|
if ((File.Exists(bridgePath) && File.ReadAllText(bridgePath).Contains("MutableDocument<EveSurfaceState>", StringComparison.Ordinal)) ||
|
|
(File.Exists(daemonHostPath) && File.ReadAllText(daemonHostPath).Contains("MutableDocument<EveSurfaceState>", StringComparison.Ordinal)) ||
|
|
runtimeVerseClient.Contains("EveSurfaceState", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Player-settings Eve command path still depends on local EveSurfaceState publication instead of managed CultMesh command documents.");
|
|
}
|
|
|
|
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.");
|
|
}
|
|
|
|
if (surfaceBuilder.Contains("AetheriaRuntimePlayerSettingsSurfaceState", StringComparison.Ordinal) ||
|
|
surfaceBuilder.Contains("new AetheriaRuntimePlayerSettingsSurfaceState(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared player-settings Eve surface builder should accept typed documents or semantic values directly, not a duplicate SurfaceState DTO.");
|
|
}
|
|
}
|
|
|
|
static void RequireDaemonHostDoesNotDrainRuntimeCommits(string root)
|
|
{
|
|
var economyServerPath = Path.Combine(root, "Economy" + ".Server", "Program.cs");
|
|
var economyServerProjectPath = Path.Combine(root, "Economy" + ".Server", "Economy" + ".Server.csproj");
|
|
var daemonHostPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs");
|
|
var drainCommandsPath = Path.Combine(root, "Aetheria.State.DrainCommands", "Program.cs");
|
|
var packageCommitLogPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeStateCommitLog.cs");
|
|
var generatedUnityProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj");
|
|
if (File.Exists(economyServerPath) || File.Exists(economyServerProjectPath))
|
|
{
|
|
throw new InvalidOperationException("Economy" + ".Server still exists beside the first-class Aetheria.State.Daemon host.");
|
|
}
|
|
|
|
var daemonHost = File.Exists(daemonHostPath)
|
|
? File.ReadAllText(daemonHostPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon host command authority; Aetheria.State.Daemon program is missing.");
|
|
var generatedUnityProject = File.Exists(generatedUnityProjectPath)
|
|
? File.ReadAllText(generatedUnityProjectPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon host command authority; GameCult.Aetheria.State.Unity.csproj is missing.");
|
|
|
|
if (File.Exists(drainCommandsPath) ||
|
|
File.Exists(Path.Combine(root, "Aetheria.State.DrainCommands", "Aetheria.State.DrainCommands.csproj")))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria.State.DrainCommands still exists as a sidecar command applicator; command acceptance belongs inside the Verse daemon.");
|
|
}
|
|
|
|
if (File.Exists(packageCommitLogPath) ||
|
|
generatedUnityProject.Contains("AetheriaRuntimeStateCommitLog.cs", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity package still exposes the legacy runtime state commit log instead of typed Eve/daemon requests.");
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"AetheriaLegacyRuntimeSnapshotImporter.ImportPendingAsync",
|
|
"DrainCommandsRuntimeCommitsAsync",
|
|
"CountPendingRuntimeCommits",
|
|
"Applied pending Aetheria runtime commits",
|
|
"AetheriaRuntimeCommitDrainStatus",
|
|
"PutRuntimeCommitDrainStatusAsync",
|
|
"GetRuntimeCommitDrainStatusAsync",
|
|
"PublishRuntimeCommitDrainDisabledAsync",
|
|
"disabled-daemon-authority"
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.SelectMany(symbol => new[]
|
|
{
|
|
(Path: daemonHostPath, Symbol: symbol, Hit: daemonHost.Contains(symbol, StringComparison.Ordinal))
|
|
})
|
|
.Where(entry => entry.Hit)
|
|
.Select(entry => $"{Path.GetRelativePath(root, entry.Path)}: {entry.Symbol}")
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"First-party daemon drains still apply legacy runtime commits instead of daemon/Eve command authority: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"AetheriaEveCommandBridge.AcceptObservedAsync",
|
|
"AppliedInputSettingsCommands = report.AcceptedInputSettingsCommands",
|
|
"AppliedLoadoutTemplateCommands = report.AcceptedLoadoutTemplateCommands"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !daemonHost.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria.State.Daemon no longer advertises daemon authority while accepting observed Eve command documents: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryDocumentSlotIdentity(string root)
|
|
{
|
|
var documentsFactoryPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeGameDocuments.cs");
|
|
var viewportDocumentsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeGameViewportDocuments.cs");
|
|
|
|
if (!File.Exists(documentsFactoryPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify inventory document slot identity; AetheriaRuntimeGameDocuments.cs is missing.");
|
|
}
|
|
if (!File.Exists(viewportDocumentsPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify inventory document slot identity; AetheriaRuntimeGameViewportDocuments.cs is missing.");
|
|
}
|
|
|
|
var documentsFactory = File.ReadAllText(documentsFactoryPath);
|
|
var viewportDocuments = File.ReadAllText(viewportDocumentsPath);
|
|
|
|
var requiredDocumentFactorySymbols = new[]
|
|
{
|
|
"AddSlot(items, \"equipment\", equipmentIndex, equipment[equipmentIndex])",
|
|
"AddSlot(items, \"cargo\", cargoBayIndex, slot)",
|
|
"SourceIndex = sourceIndex",
|
|
"X = slot.X",
|
|
"Y = slot.Y"
|
|
};
|
|
var missingDocumentFactorySymbols = requiredDocumentFactorySymbols
|
|
.Where(symbol => !documentsFactory.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingDocumentFactorySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory document no longer carries enough typed slot identity for cargo/equipment operations: " +
|
|
string.Join(", ", missingDocumentFactorySymbols));
|
|
}
|
|
|
|
var requiredDocumentSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaRuntimeInventoryItem",
|
|
"[Key(6)]",
|
|
"public int SourceIndex { get; set; } = -1;",
|
|
"[Key(7)]",
|
|
"public int X { get; set; } = -1;",
|
|
"[Key(8)]",
|
|
"public int Y { get; set; } = -1;"
|
|
};
|
|
var missingDocumentSymbols = requiredDocumentSymbols
|
|
.Where(symbol => !viewportDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory item document no longer publishes cargo/equipment slot identity to CultMesh clients: " +
|
|
string.Join(", ", missingDocumentSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryValidationUsesManagedTypedDocuments(string root)
|
|
{
|
|
var clientStatePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaClientState.cs");
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
|
|
if (!File.Exists(clientStatePath) ||
|
|
!File.Exists(inventoryMenuPath) ||
|
|
!File.Exists(inventoryPanelPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify managed inventory document access; expected client state and inventory UI sources are missing.");
|
|
}
|
|
|
|
var clientState = File.ReadAllText(clientStatePath);
|
|
if (!clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument> Inventory(", StringComparison.Ordinal) ||
|
|
!clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeZoneDetailsDocument> ZoneDetails(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshReactiveDocument<AetheriaRuntimeInventoryDocument> ReactiveInventory(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshReactiveDocument<AetheriaRuntimeZoneDetailsDocument> ReactiveZoneDetails(", StringComparison.Ordinal) ||
|
|
clientState.Contains("AetheriaClientState.Zone(", StringComparison.Ordinal) ||
|
|
clientState.Contains("AetheriaClientState.Entity(", StringComparison.Ordinal) ||
|
|
clientState.Contains("AetheriaClientZone", StringComparison.Ordinal) ||
|
|
clientState.Contains("AetheriaClientEntity", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose native typed document handles for current entity, station refit, inventory, and zone documents; callers use Handle.Reactive() when they need a stream.");
|
|
}
|
|
|
|
var sources = new Dictionary<string, string>
|
|
{
|
|
["InventoryMenu.cs"] = File.ReadAllText(inventoryMenuPath),
|
|
["InventoryPanel.cs"] = File.ReadAllText(inventoryPanelPath)
|
|
};
|
|
foreach (var (name, source) in sources)
|
|
{
|
|
var compact = CompactSource(source);
|
|
var runtimeId = name == "InventoryMenu.cs"
|
|
? "unity-inventory-menu"
|
|
: "unity-inventory";
|
|
var requiredSymbols = new[]
|
|
{
|
|
$"AetheriaUnityRuntimeClientProvider.RuntimeState(\"{runtimeId}\")",
|
|
"CurrentEntitySnapshot()",
|
|
"StationRefitSnapshot",
|
|
"RuntimeState.CurrentEntity.Latest()",
|
|
"RuntimeState.StationRefit.Latest()"
|
|
};
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
var forbiddenShortcutSymbols = new[]
|
|
{
|
|
$"AetheriaUnityRuntimeClientProvider.CurrentEntityState(\"{runtimeId}\")",
|
|
$"AetheriaUnityRuntimeClientProvider.CurrentStationRefit(\"{runtimeId}\")",
|
|
$"AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, \"{runtimeId}\")"
|
|
};
|
|
var shortcutHits = forbiddenShortcutSymbols
|
|
.Where(symbol => compact.Contains(CompactSource(symbol), StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{name} no longer samples inventory shell context through managed typed client documents: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
if (shortcutHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{name} still validates inventory slots through provider snapshot shortcuts instead of its managed runtime state handle: " +
|
|
string.Join(", ", shortcutHits));
|
|
}
|
|
|
|
var forbiddenInventoryValidationSymbols = new[]
|
|
{
|
|
"InventorySnapshot(entityIndex)",
|
|
"RuntimeState.Inventory(entityIndex).Latest()",
|
|
"TryResolveManagedInventoryRows(",
|
|
"TryValidateTypedCargoSlot(",
|
|
"TryValidateTypedEquipmentSlot("
|
|
}
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenInventoryValidationSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{name} still locally validates inventory mutation state instead of lowering the daemon-presented inventory surface: " +
|
|
string.Join(", ", forbiddenInventoryValidationSymbols));
|
|
}
|
|
|
|
var forbiddenCompactedSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeCurrentEntitySession",
|
|
"AetheriaRuntimeStationRefitSession",
|
|
"AetheriaRuntimeInventorySession",
|
|
".ObserveEntity()",
|
|
".ObserveStationRefit()",
|
|
".ObserveInventory(entityIndex)",
|
|
".Latest<AetheriaRuntimeCurrentEntityDocument>()",
|
|
".Latest<AetheriaRuntimeStationRefitDocument>()",
|
|
".Details.Latest<AetheriaRuntimeInventoryDocument>(entityIndex)",
|
|
".State.Current.Entity.LatestAsync()",
|
|
".State.StationRefit.LatestAsync()",
|
|
".Details.Inventory(entityIndex).LatestAsync()"
|
|
}
|
|
.Select(CompactSource)
|
|
.ToArray();
|
|
var hits = forbiddenCompactedSymbols
|
|
.Where(symbol => compact.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{name} still routes inventory validation through session wrappers instead of direct managed reactive typed documents.");
|
|
}
|
|
|
|
var forbiddenCurrentDocumentSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<",
|
|
$".ReactiveCurrentEntity(\"{runtimeId}\")",
|
|
$".ReactiveStationRefit(\"{runtimeId}\")",
|
|
".ReactiveInventory(entityIndex)",
|
|
"_currentEntity?.Dispose()",
|
|
"_stationRefit?.Dispose()",
|
|
"_inventory?.Dispose()"
|
|
}
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenCurrentDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{name} should sample inventory validation state through current typed documents instead of owning reactive handles: " +
|
|
string.Join(", ", forbiddenCurrentDocumentSymbols));
|
|
}
|
|
}
|
|
|
|
Console.WriteLine("Inventory validation: Unity samples shell context only; mutation authority belongs to daemon-presented typed inventory surfaces");
|
|
}
|
|
|
|
static void RequireMenuDockingUsesManagedTypedSnapshot(string root)
|
|
{
|
|
var clientStatePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaClientState.cs");
|
|
if (!File.Exists(clientStatePath))
|
|
throw new InvalidOperationException("Cannot verify managed docking snapshot access; AetheriaClientState.cs is missing.");
|
|
|
|
var clientState = File.ReadAllText(clientStatePath);
|
|
var requiredClientSymbols = new[]
|
|
{
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeStationRefitDocument> StationRefit { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCurrentDockingDocument> CurrentDocking { get; }"
|
|
};
|
|
var missingClientSymbols = requiredClientSymbols
|
|
.Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingClientSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose managed typed docking documents directly for Unity menu surfaces: " +
|
|
string.Join(", ", missingClientSymbols));
|
|
}
|
|
|
|
var forbiddenClientSymbols = new[]
|
|
{
|
|
"AetheriaClientDockingState",
|
|
"AetheriaClientDockingSnapshot",
|
|
"AetheriaClientReactiveDockingState",
|
|
"AetheriaClientCurrentState",
|
|
"LatestDockingState",
|
|
"ReactiveDockingState"
|
|
};
|
|
var forbiddenClientHits = forbiddenClientSymbols
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenClientHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState still exposes aggregate docking snapshot wrappers instead of direct managed typed documents: " +
|
|
string.Join(", ", forbiddenClientHits));
|
|
}
|
|
|
|
if (clientState.Contains("public AetheriaRuntimeObservedDockingState? CurrentDocking(", StringComparison.Ordinal) ||
|
|
clientState.Contains("AetheriaRuntimeObservedDockingState.TryCreateCurrent(entity, docking, refit", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must not expose one-shot observed docking aggregation; callers should hold the managed typed docking documents they need.");
|
|
}
|
|
|
|
var menuPaths = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "LocalMenu.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuPanel.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs")
|
|
};
|
|
|
|
var missingPaths = menuPaths.Where(path => !File.Exists(path)).ToArray();
|
|
if (missingPaths.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot verify managed docking snapshot menu access; missing sources: " +
|
|
string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path))));
|
|
}
|
|
|
|
var offenders = menuPaths
|
|
.Select(path => new
|
|
{
|
|
Path = path,
|
|
Source = File.ReadAllText(path)
|
|
})
|
|
.Select(entry => new
|
|
{
|
|
entry.Path,
|
|
entry.Source,
|
|
Compact = CompactSource(entry.Source)
|
|
})
|
|
.Where(entry =>
|
|
!HasManagedDockingSnapshotAccess(entry.Source) ||
|
|
entry.Source.Contains(".DockingState.Reactive()", StringComparison.Ordinal) ||
|
|
entry.Compact.Contains(CompactSource(".DockingState.Latest()"), StringComparison.Ordinal) ||
|
|
entry.Compact.Contains(CompactSource(".DockingState.TryLatest("), StringComparison.Ordinal))
|
|
.Select(entry => Path.GetRelativePath(root, entry.Path))
|
|
.ToArray();
|
|
|
|
if (offenders.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity menu docking state must read managed typed docking documents instead of nested latest/reactive wrappers: " +
|
|
string.Join(", ", offenders));
|
|
}
|
|
|
|
var menuPanel = File.ReadAllText(Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuPanel.cs"));
|
|
var tradeMenu = File.ReadAllText(Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"));
|
|
var directDockingOffenders = new List<string>();
|
|
if (menuPanel.Contains("CultMeshReactiveDocument<AetheriaRuntimeCurrentDockingDocument> _currentDocking", StringComparison.Ordinal) ||
|
|
menuPanel.Contains(".ReactiveCurrentDocking(\"unity-runtime-menu-tabs\")", StringComparison.Ordinal) ||
|
|
menuPanel.Contains("docking = _currentDocking.Current", StringComparison.Ordinal) ||
|
|
menuPanel.Contains("ResolveClient().State.Latest<AetheriaRuntimeCurrentDockingDocument>()", StringComparison.Ordinal))
|
|
{
|
|
directDockingOffenders.Add("Assets/Scripts/UI/Menu/MenuPanel.cs");
|
|
}
|
|
|
|
if (tradeMenu.Contains("CultMeshReactiveDocument<AetheriaRuntimeCurrentDockingDocument> _currentDocking", StringComparison.Ordinal) ||
|
|
tradeMenu.Contains(".ReactiveCurrentDocking(\"unity-trade\")", StringComparison.Ordinal) ||
|
|
tradeMenu.Contains("docking = _currentDocking.Current", StringComparison.Ordinal) ||
|
|
tradeMenu.Contains("CultMeshReactiveDocument<AetheriaRuntimeStationRefitDocument> _stationRefit", StringComparison.Ordinal) ||
|
|
tradeMenu.Contains(".ReactiveStationRefit(\"unity-trade\")", StringComparison.Ordinal) ||
|
|
tradeMenu.Contains("return _stationRefit?.Current", StringComparison.Ordinal) ||
|
|
tradeMenu.Contains("ResolveClient().State.Latest<AetheriaRuntimeCurrentDockingDocument>()", StringComparison.Ordinal))
|
|
{
|
|
directDockingOffenders.Add("Assets/Scripts/UI/Menu/TradeMenu.cs");
|
|
}
|
|
|
|
if (directDockingOffenders.Count > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity menu docking reads must use current typed state directly and adapt Unity objects at the caller edge; do not own duplicate reactive docking/refit documents: " +
|
|
string.Join(", ", directDockingOffenders));
|
|
}
|
|
|
|
Console.WriteLine("Menu docking state: Unity menus sample managed typed docking documents through native state handles");
|
|
}
|
|
|
|
static bool HasManagedDockingSnapshotAccess(string source)
|
|
{
|
|
if (source.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(", StringComparison.Ordinal) &&
|
|
(source.Contains("RuntimeState.CurrentDocking.Latest()", StringComparison.Ordinal) ||
|
|
source.Contains("_runtimeState.CurrentDocking.Latest()", StringComparison.Ordinal) ||
|
|
source.Contains("RuntimeState.StationRefit.Latest()", StringComparison.Ordinal) ||
|
|
source.Contains("_runtimeState.StationRefit.Latest()", StringComparison.Ordinal)) &&
|
|
!source.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal) &&
|
|
!source.Contains("CultMeshReactiveDocument<AetheriaRuntimeCurrentDockingDocument>", StringComparison.Ordinal) &&
|
|
!source.Contains("CultMeshReactiveDocument<AetheriaRuntimeStationRefitDocument>", StringComparison.Ordinal))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (source.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) &&
|
|
(source.Contains("CurrentDockingState(\"", StringComparison.Ordinal) ||
|
|
source.Contains("CurrentStationRefit(\"", StringComparison.Ordinal) ||
|
|
source.Contains(".CurrentStationRefit()", StringComparison.Ordinal)) &&
|
|
!source.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal) &&
|
|
!source.Contains("CultMeshReactiveDocument<AetheriaRuntimeCurrentDockingDocument>", StringComparison.Ordinal) &&
|
|
!source.Contains("CultMeshReactiveDocument<AetheriaRuntimeStationRefitDocument>", StringComparison.Ordinal))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void RequireUnitySharedDocumentAccessorErgonomics(string root)
|
|
{
|
|
var clientStatePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaClientState.cs");
|
|
if (!File.Exists(clientStatePath))
|
|
throw new InvalidOperationException("Cannot verify shared document accessor ergonomics; AetheriaClientState.cs is missing.");
|
|
|
|
var clientState = File.ReadAllText(clientStatePath);
|
|
var requiredClientSymbols = new[]
|
|
{
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> Catalog",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimePlayerSettingsDocument> PlayerSettings",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument> Inventory("
|
|
};
|
|
var missingClientSymbols = requiredClientSymbols
|
|
.Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingClientSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose named typed document handles for shared and indexed Unity documents, with reactive sync owned by CultMesh handles: " +
|
|
string.Join(", ", missingClientSymbols));
|
|
}
|
|
|
|
if (clientState.Contains("public Task<TDocument> LatestAsync<TDocument>", StringComparison.Ordinal) ||
|
|
clientState.Contains("public TDocument Latest<TDocument>", StringComparison.Ordinal) ||
|
|
clientState.Contains("public Task<CultMeshReactiveDocument<TDocument>> ReactiveAsync<TDocument>", StringComparison.Ordinal) ||
|
|
clientState.Contains("public Observable<TDocument> Watch<TDocument>", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must not expose latest/watch/session wrappers; callers should hold native typed documents and CultMesh reactive handles.");
|
|
}
|
|
|
|
var unityPaths = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionBarSlot.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRuntimeClientProvider.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityTargetPresentation.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "HUD", "SchematicDisplay.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "InputScreen", "InputDisplayLayout.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MapRenderer.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Zone Display", "VolumeCloudRenderer.cs")
|
|
};
|
|
|
|
var missingPaths = unityPaths.Where(path => !File.Exists(path)).ToArray();
|
|
if (missingPaths.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot verify shared Unity document accessor ergonomics; missing sources: " +
|
|
string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path))));
|
|
}
|
|
|
|
var forbiddenCompactedSymbols = new[]
|
|
{
|
|
".State.Catalog.Latest()",
|
|
".State.Settings.Player.Latest()",
|
|
".State.Settings.VerseHost.Latest()"
|
|
}
|
|
.Select(CompactSource)
|
|
.ToArray();
|
|
var offenders = unityPaths
|
|
.Select(path => new
|
|
{
|
|
Path = path,
|
|
Compact = CompactSource(File.ReadAllText(path))
|
|
})
|
|
.Where(entry => forbiddenCompactedSymbols.Any(symbol => entry.Compact.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(entry => Path.GetRelativePath(root, entry.Path))
|
|
.ToArray();
|
|
|
|
if (offenders.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity presentation code still walks shared CultMesh document handles instead of managed typed document access: " +
|
|
string.Join(", ", offenders));
|
|
}
|
|
|
|
var actionBarSlot = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"Gameplay",
|
|
"ActionBarSlot.cs"));
|
|
var requiredActionBarSymbols = new[]
|
|
{
|
|
"public abstract class ActionBarBinding",
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-action-bar\")",
|
|
"s_runtimeState.Catalog.Latest()?.FindItem(item, x => x.ItemKey)",
|
|
".FindItem(item, x => x.ItemKey)",
|
|
"private void OnDestroy()"
|
|
};
|
|
var missingActionBarSymbols = requiredActionBarSymbols
|
|
.Where(symbol => !actionBarSlot.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingActionBarSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionBarSlot should resolve runtime catalog items through a managed typed catalog handle: " +
|
|
string.Join(", ", missingActionBarSymbols));
|
|
}
|
|
|
|
if (actionBarSlot.Contains("AetheriaRuntimeCatalogSession _catalog", StringComparison.Ordinal) ||
|
|
actionBarSlot.Contains("CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot> _catalog", StringComparison.Ordinal) ||
|
|
actionBarSlot.Contains(".CurrentCatalog()", StringComparison.Ordinal) ||
|
|
actionBarSlot.Contains(".ReactiveCatalogSnapshot(\"unity-action-bar\")", StringComparison.Ordinal) ||
|
|
actionBarSlot.Contains("_catalog?.Current?.FindItem", StringComparison.Ordinal) ||
|
|
actionBarSlot.Contains("binding?.Dispose()", StringComparison.Ordinal) ||
|
|
actionBarSlot.Contains("public virtual void Dispose()", StringComparison.Ordinal) ||
|
|
actionBarSlot.Contains("Client.State.ObserveCatalog()", StringComparison.Ordinal) ||
|
|
actionBarSlot.Contains("Client.State.ReactiveCatalogSnapshot()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionBarSlot still routes through client/session/reactive catalog plumbing instead of reading the named current catalog from typed client state.");
|
|
}
|
|
|
|
var schematicDisplay = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"UI",
|
|
"HUD",
|
|
"SchematicDisplay.cs"));
|
|
var playerHudStatePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimePlayerHudState.cs");
|
|
var requiredSchematicDisplaySymbols = new[]
|
|
{
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-schematic-display\")",
|
|
"RuntimeState.Catalog.Latest()",
|
|
"RuntimeState.PlayerSettings.Latest()",
|
|
"RuntimeState.CurrentEntity.Latest()?.Hud",
|
|
"CatalogSnapshot()?.FindItem(item, x => x.ItemKey)",
|
|
"private AetheriaRuntimeCatalogSnapshot CatalogSnapshot()",
|
|
"private AetheriaRuntimePlayerSettingsDocument PlayerSettingsSnapshot()",
|
|
"private AetheriaRuntimeCurrentEntityHudStatus CurrentEntityHudStatus()",
|
|
"?.Hud ?? new AetheriaRuntimeCurrentEntityHudStatus();"
|
|
};
|
|
var missingSchematicDisplaySymbols = requiredSchematicDisplaySymbols
|
|
.Where(symbol => !schematicDisplay.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSchematicDisplaySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SchematicDisplay should sample shared catalog/settings/current-entity HUD through named current typed documents: " +
|
|
string.Join(", ", missingSchematicDisplaySymbols));
|
|
}
|
|
if (schematicDisplay.Contains("ResolveCatalog()?.Current?.FindItem", StringComparison.Ordinal) ||
|
|
schematicDisplay.Contains("private CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot> ResolveCatalog()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SchematicDisplay should expose catalog values through CatalogSnapshot() instead of returning reactive document plumbing to callers.");
|
|
}
|
|
|
|
var forbiddenSchematicDisplaySymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot>",
|
|
"CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument>",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeCurrentEntityDocument>",
|
|
".ReactiveCatalogSnapshot(\"unity-schematic-display\")",
|
|
".ReactivePlayerSettingsDocument(\"unity-schematic-display\")",
|
|
".ReactiveCurrentEntity(\"unity-schematic-display\")",
|
|
"_catalog?.Dispose()",
|
|
"_playerSettings?.Dispose()",
|
|
"_currentEntity?.Dispose()",
|
|
"AetheriaRuntimePlayerHudSession _playerHud",
|
|
"ResolvePlayerHud()",
|
|
"ResolveClient().State.ObservePlayerHud()",
|
|
"ResolvePlayerHud()?.Catalog",
|
|
"ResolvePlayerHud()?.PlayerSettings",
|
|
"ResolvePlayerHud()?.Hud"
|
|
};
|
|
var schematicDisplayHits = forbiddenSchematicDisplaySymbols
|
|
.Where(symbol => schematicDisplay.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (schematicDisplayHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SchematicDisplay still routes through legacy HUD/reactive wrappers instead of named current typed documents: " +
|
|
string.Join(", ", schematicDisplayHits));
|
|
}
|
|
|
|
if (File.Exists(playerHudStatePath) ||
|
|
clientState.Contains("ObservePlayerHud(", StringComparison.Ordinal) ||
|
|
clientState.Contains("AetheriaRuntimePlayerHudSession", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Player HUD state must be read as direct reactive catalog/settings/current-entity documents; remove the aggregate AetheriaRuntimePlayerHudSession helper.");
|
|
}
|
|
|
|
var volumeCloudRenderer = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"Zone Display",
|
|
"VolumeCloudRenderer.cs"));
|
|
var requiredVolumeCloudSymbols = new[]
|
|
{
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-volume-cloud-renderer\")",
|
|
"_runtimeState.PlayerSettings.Latest()"
|
|
};
|
|
var missingVolumeCloudSymbols = requiredVolumeCloudSymbols
|
|
.Where(symbol => !volumeCloudRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingVolumeCloudSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"VolumeCloudRenderer should sample player settings through its managed typed state handle: " +
|
|
string.Join(", ", missingVolumeCloudSymbols));
|
|
}
|
|
|
|
if (volumeCloudRenderer.Contains("CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument>", StringComparison.Ordinal) ||
|
|
volumeCloudRenderer.Contains(".CurrentPlayerSettings(\"unity-volume-cloud-renderer\")", StringComparison.Ordinal) ||
|
|
volumeCloudRenderer.Contains(".ReactivePlayerSettingsDocument(\"unity-volume-cloud-renderer\")", StringComparison.Ordinal) ||
|
|
volumeCloudRenderer.Contains("_playerSettings?.Dispose()", StringComparison.Ordinal) ||
|
|
volumeCloudRenderer.Contains("_playerSettings?.Current", StringComparison.Ordinal) ||
|
|
volumeCloudRenderer.Contains("AetheriaRuntimePlayerSettingsSession _playerSettings", StringComparison.Ordinal) ||
|
|
volumeCloudRenderer.Contains(".ObservePlayer()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"VolumeCloudRenderer still routes player settings through legacy session/reactive wrapper state instead of named current typed state.");
|
|
}
|
|
|
|
var targetPresentation = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"Gameplay",
|
|
"AetheriaUnityTargetPresentation.cs"));
|
|
var deletedObservedTargetQueryPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedTargetQuery.cs");
|
|
if (File.Exists(deletedObservedTargetQueryPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityObservedTargetQuery is dead contact-query chaff; target presentation should read typed zone contacts directly.");
|
|
}
|
|
|
|
var requiredTargetContactSymbols = new[]
|
|
{
|
|
"public Func<AetheriaRuntimeZoneContactsDocument> ResolveZoneContacts { get; set; }",
|
|
"GetObservedTarget(Entity observer)",
|
|
"GetObservedVisibleContacts(",
|
|
"ResolveZoneContacts?.Invoke()"
|
|
};
|
|
var missingTargetContactSymbols = requiredTargetContactSymbols
|
|
.Where(symbol => !targetPresentation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTargetContactSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityTargetPresentation should query typed zone contacts directly for target HUD state: " +
|
|
string.Join(", ", missingTargetContactSymbols));
|
|
}
|
|
|
|
if (targetPresentation.Contains("CultMeshReactiveDocument<AetheriaRuntimeZoneContactsDocument>", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains(".ReactiveZoneContacts(\"unity-observed-target-query\")", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains("_zoneContacts?.Dispose()", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains("_zoneContacts?.Current", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains("AetheriaRuntimeZoneContactsSession _zoneContacts", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains(".ObserveZoneContacts()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityTargetPresentation still routes zone contacts through legacy session/reactive wrapper state instead of typed current state.");
|
|
}
|
|
|
|
var mapRenderer = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"UI",
|
|
"Menu",
|
|
"MapRenderer.cs"));
|
|
var requiredMapRendererSharedDocumentSymbols = new[]
|
|
{
|
|
"AetheriaUnityGameViewportDocuments _viewportDocuments",
|
|
"ClearViewportCaches()",
|
|
"_viewportDocuments?.Dispose()",
|
|
"_viewportDocuments?.CurrentObjectsViewport",
|
|
"_viewportDocuments?.CurrentRenderSplatsViewport",
|
|
"private void OnDestroy()"
|
|
};
|
|
var missingMapRendererSharedDocumentSymbols = requiredMapRendererSharedDocumentSymbols
|
|
.Where(symbol => !mapRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
var compactMapRenderer = CompactSource(mapRenderer);
|
|
missingMapRendererSharedDocumentSymbols = missingMapRendererSharedDocumentSymbols
|
|
.Concat(new[]
|
|
{
|
|
".MapViewportDocuments(viewport,\"unity-map-renderer\")",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-map-renderer\")",
|
|
"_runtimeState.PlayerSettings.Latest()"
|
|
}
|
|
.Select(CompactSource)
|
|
.Where(symbol => !compactMapRenderer.Contains(symbol, StringComparison.Ordinal)))
|
|
.ToArray();
|
|
if (missingMapRendererSharedDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MapRenderer should bind viewport render feeds through managed reactive documents and sample settings through current typed state: " +
|
|
string.Join(", ", missingMapRendererSharedDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenMapRendererSharedDocumentSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<AetheriaRuntimeObjectsViewportDocument> _objectsViewport",
|
|
"CultMeshReactiveDocument<EveFieldsSplatsDocument> _renderSplatsViewport",
|
|
".ReactiveObjectsViewport(viewport, \"unity-map-renderer\")",
|
|
".ReactiveRenderSplatsViewport(viewport, \"unity-map-renderer\")",
|
|
"AetheriaRuntimeObjectsViewportSession _objectsViewport",
|
|
"AetheriaRuntimeRenderSplatsViewportSession _renderSplatsViewport",
|
|
"CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument>",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-map-renderer\")",
|
|
".ReactivePlayerSettingsDocument(\"unity-map-renderer\")",
|
|
"AetheriaRuntimePlayerSettingsSession",
|
|
".ObserveObjects(viewport)",
|
|
".ObserveRenderSplats(viewport)",
|
|
".ObservePlayer()",
|
|
"_playerSettings?.Dispose()",
|
|
"RtsIcon",
|
|
"RTS Map Icon",
|
|
"RTS Command Icons",
|
|
"local Verse state"
|
|
};
|
|
var mapRendererRawDocumentHits = forbiddenMapRendererSharedDocumentSymbols
|
|
.Where(symbol => mapRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (mapRendererRawDocumentHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MapRenderer still routes viewport documents through session wrappers instead of managed reactive typed documents: " +
|
|
string.Join(", ", mapRendererRawDocumentHits));
|
|
}
|
|
|
|
var mainMenu = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"UI",
|
|
"MainMenu.cs"));
|
|
var requiredMainMenuSharedDocumentSymbols = new[]
|
|
{
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
".RuntimeState(stateBoot, \"unity-main-menu\")",
|
|
"ResolveMainMenuSurface(string surfaceId)",
|
|
".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)",
|
|
".Latest()",
|
|
"private void OnDestroy()"
|
|
};
|
|
var missingMainMenuSharedDocumentSymbols = requiredMainMenuSharedDocumentSymbols
|
|
.Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingMainMenuSharedDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu should sample daemon-published menu surfaces through a managed typed Aetheria runtime state handle: " +
|
|
string.Join(", ", missingMainMenuSharedDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenMainMenuSharedDocumentSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<AetheriaRuntimeSectorMapDocument>",
|
|
"CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument>",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeVerseHostSettingsDocument>",
|
|
".ReactiveSectorMap(",
|
|
".ReactivePlayerSettingsDocument(",
|
|
".ReactiveVerseHostSettings(",
|
|
"AetheriaRuntimeSectorMapSession",
|
|
"AetheriaRuntimePlayerSettingsSession",
|
|
"AetheriaRuntimeVerseHostSettingsSession",
|
|
".ObserveSectorMap()",
|
|
".ObservePlayer()",
|
|
".ObserveVerseHost()"
|
|
};
|
|
var forbiddenMainMenuSharedDocumentHits = forbiddenMainMenuSharedDocumentSymbols
|
|
.Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenMainMenuSharedDocumentHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu should sample managed current typed documents instead of owning reactive wrappers: " +
|
|
string.Join(", ", forbiddenMainMenuSharedDocumentHits));
|
|
}
|
|
|
|
var managedVerseCopySources = new Dictionary<string, string>
|
|
{
|
|
["Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs"] = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaClient.cs")),
|
|
["Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs"] = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeClientTargetSurfaceBuilder.cs")),
|
|
["Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs"] = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"Gameplay",
|
|
"AetheriaDaemonObserver.cs")),
|
|
["Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs"] = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"UI",
|
|
"InputScreen",
|
|
"InputDisplayLayout.cs")),
|
|
["Assets/Scripts/UI/MainMenu.cs"] = mainMenu,
|
|
["Assets/Scripts/UI/Menu/InventoryPanel.cs"] = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"UI",
|
|
"Menu",
|
|
"InventoryPanel.cs")),
|
|
["Assets/Scripts/UI/Menu/SectorRenderer.cs"] = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"UI",
|
|
"Menu",
|
|
"SectorRenderer.cs"))
|
|
};
|
|
var staleLocalVerseCopyHits = managedVerseCopySources
|
|
.Where(pair =>
|
|
pair.Value.Contains("local Verse state", StringComparison.Ordinal) ||
|
|
pair.Value.Contains("local Aetheria Verse state", StringComparison.Ordinal) ||
|
|
pair.Value.Contains("selected local Verse state file", StringComparison.Ordinal) ||
|
|
pair.Value.Contains("readable local Verse state file", StringComparison.Ordinal))
|
|
.Select(pair => pair.Key)
|
|
.ToArray();
|
|
if (staleLocalVerseCopyHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Managed client/UI surfaces must describe typed Verse targets/documents instead of teaching local-file Verse authority: " +
|
|
string.Join(", ", staleLocalVerseCopyHits));
|
|
}
|
|
|
|
var tradeMenu = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"UI",
|
|
"Menu",
|
|
"TradeMenu.cs"));
|
|
var compactTradeMenu = CompactSource(tradeMenu);
|
|
var requiredTradeMenuSharedDocumentSymbols = new[]
|
|
{
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-trade\")",
|
|
"RuntimeState.Catalog.Latest()",
|
|
"RuntimeState.PlayerSettings.Latest()",
|
|
"RuntimeState.StationRefit.Latest()",
|
|
"RuntimeState.CurrentDocking.Latest()",
|
|
"private void OnDestroy()"
|
|
};
|
|
var missingTradeMenuSharedDocumentSymbols = requiredTradeMenuSharedDocumentSymbols
|
|
.Where(symbol => !compactTradeMenu.Contains(symbol, StringComparison.Ordinal) &&
|
|
!tradeMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTradeMenuSharedDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu should sample shared documents through its managed runtime state handle: " +
|
|
string.Join(", ", missingTradeMenuSharedDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenTradeMenuSharedDocumentSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-trade\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-trade\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentStationRefit(\"unity-trade\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentDockingState(\"unity-trade\")",
|
|
".ReactiveCatalogSnapshot(\"unity-trade\")",
|
|
".ReactivePlayerSettingsDocument(\"unity-trade\")",
|
|
"AetheriaRuntimeCatalogSession",
|
|
"AetheriaRuntimePlayerSettingsSession",
|
|
"ResolveClient().State.ObserveCatalog()",
|
|
".ObservePlayer()",
|
|
"_catalog?.Dispose()",
|
|
"_playerSettings?.Dispose()"
|
|
}
|
|
.Where(symbol => tradeMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenTradeMenuSharedDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu still routes catalog/settings through wrapper/session/reactive handles instead of current typed documents: " +
|
|
string.Join(", ", forbiddenTradeMenuSharedDocumentSymbols));
|
|
}
|
|
|
|
var inventoryMenu = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"UI",
|
|
"Menu",
|
|
"InventoryMenu.cs"));
|
|
var compactInventoryMenu = CompactSource(inventoryMenu);
|
|
var requiredInventoryMenuSharedDocumentSymbols = new[]
|
|
{
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-inventory-menu\")",
|
|
"RuntimeState.Catalog.Latest()",
|
|
"RuntimeState.PlayerSettings.Latest()",
|
|
"RuntimeState.CurrentEntity.Latest()",
|
|
"RuntimeState.StationRefit.Latest()",
|
|
"private void OnDestroy()"
|
|
};
|
|
var missingInventoryMenuSharedDocumentSymbols = requiredInventoryMenuSharedDocumentSymbols
|
|
.Where(symbol => !compactInventoryMenu.Contains(symbol, StringComparison.Ordinal) &&
|
|
!inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingInventoryMenuSharedDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu should sample shared documents through its managed runtime state handle: " +
|
|
string.Join(", ", missingInventoryMenuSharedDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenInventoryMenuSharedDocumentSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-inventory-menu\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-inventory-menu\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentEntityState(\"unity-inventory-menu\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentStationRefit(\"unity-inventory-menu\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, \"unity-inventory-menu\")",
|
|
".ReactiveCatalogSnapshot(\"unity-inventory-menu\")",
|
|
".ReactivePlayerSettingsDocument(\"unity-inventory-menu\")",
|
|
"AetheriaRuntimeCatalogSession",
|
|
"AetheriaRuntimePlayerSettingsSession",
|
|
"ResolveClient().State.ObserveCatalog()",
|
|
".ObservePlayer()",
|
|
"_catalog?.Dispose()",
|
|
"_playerSettings?.Dispose()"
|
|
}
|
|
.Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenInventoryMenuSharedDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still routes catalog/settings through wrapper/session/reactive handles instead of current typed documents: " +
|
|
string.Join(", ", forbiddenInventoryMenuSharedDocumentSymbols));
|
|
}
|
|
|
|
var inventoryPanel = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"UI",
|
|
"Menu",
|
|
"InventoryPanel.cs"));
|
|
var compactInventoryPanel = CompactSource(inventoryPanel);
|
|
var requiredInventoryPanelSharedDocumentSymbols = new[]
|
|
{
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-inventory\")",
|
|
"RuntimeState.CurrentEntity.Latest()",
|
|
"RuntimeState.StationRefit.Latest()",
|
|
"RuntimeState.DaemonFrame.Latest()",
|
|
"RuntimeState.InventoryPanelSurface(BuildInventoryPanelSurfaceRequest()).Latest()",
|
|
"RuntimeState.InventoryDropdownSurface(BuildDropdownSurfaceRequest()).Latest()",
|
|
"BuildInventoryPanelSurfaceRequest()",
|
|
"DragSession.TryGetDraggedItem(out var itemDragObject)",
|
|
"private void OnDestroy()"
|
|
};
|
|
var missingInventoryPanelSharedDocumentSymbols = requiredInventoryPanelSharedDocumentSymbols
|
|
.Where(symbol => !compactInventoryPanel.Contains(symbol, StringComparison.Ordinal) &&
|
|
!inventoryPanel.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingInventoryPanelSharedDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel should sample shared documents through its managed runtime state handle: " +
|
|
string.Join(", ", missingInventoryPanelSharedDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenInventoryPanelSharedDocumentSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-inventory\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-inventory\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentEntityState(\"unity-inventory\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentStationRefit(\"unity-inventory\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, \"unity-inventory\")",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentInventoryPanelSurface(",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentInventoryDropdownSurface(",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentDaemonFrame(\"unity-inventory\")",
|
|
".ReactiveCatalogSnapshot(\"unity-inventory\")",
|
|
".ReactivePlayerSettingsDocument(\"unity-inventory\")",
|
|
"AetheriaRuntimeCatalogSession",
|
|
"AetheriaRuntimePlayerSettingsSession",
|
|
"ResolveClient().State.ObserveCatalog()",
|
|
".ObservePlayer()",
|
|
"_catalog?.Dispose()",
|
|
"_playerSettings?.Dispose()"
|
|
}
|
|
.Where(symbol => inventoryPanel.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenInventoryPanelSharedDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still routes catalog/settings through wrapper/session/reactive handles instead of current typed documents: " +
|
|
string.Join(", ", forbiddenInventoryPanelSharedDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenInventoryPanelUnityCellSymbols = new[]
|
|
{
|
|
"NodePrototype",
|
|
"NodeTextures",
|
|
"ThermalTextures",
|
|
"TemperatureDisplay",
|
|
"GearOccupancy",
|
|
"Occupancy[",
|
|
"CellInstances.Add",
|
|
"TryValidateTypedCargoSlot",
|
|
"TryValidateTypedEquipmentSlot",
|
|
"RuntimeState.Inventory(entityIndex).Latest()",
|
|
"origin.Cargo.TryGetValue(item, out var originPosition)",
|
|
"RequestDraggedItemToEntity",
|
|
"RequestDraggedItemToCargo",
|
|
"RegisterTarget(drag"
|
|
}
|
|
.Where(symbol => inventoryPanel.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenInventoryPanelUnityCellSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel must not own inventory cell composition, drag/drop mutation, or thermal grid rendering; those belong to the daemon-presented CultUI surface: " +
|
|
string.Join(", ", forbiddenInventoryPanelUnityCellSymbols));
|
|
}
|
|
|
|
var inventoryPanelSurfaceBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeInventoryPanelSurfaceBuilder.cs");
|
|
var inventoryPanelSurfaceBuilder = File.Exists(inventoryPanelSurfaceBuilderPath)
|
|
? File.ReadAllText(inventoryPanelSurfaceBuilderPath)
|
|
: throw new InvalidOperationException("Cannot verify shared inventory panel surface; AetheriaRuntimeInventoryPanelSurfaceBuilder.cs is missing.");
|
|
var requiredInventoryPanelSurfaceSymbols = new[]
|
|
{
|
|
"BuildFromDocuments(",
|
|
"AetheriaRuntimeInventoryDocument displayedInventory",
|
|
"inventory.drag_session",
|
|
"AddAssetProps(props, \"backgroundAtlas\"",
|
|
"AddAssetProps(props, \"foregroundAtlas\"",
|
|
"AddAssetProps(props, \"thermalLayer\"",
|
|
"AddAssetProps(props, \"icon\""
|
|
}
|
|
.Where(symbol => !inventoryPanelSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (requiredInventoryPanelSurfaceSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory panel surface composition must live in the shared runtime package and expose drag/drop state plus renderer-independent asset refs: " +
|
|
string.Join(", ", requiredInventoryPanelSurfaceSymbols));
|
|
}
|
|
|
|
var runtimeAssetsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeAssets.cs");
|
|
var runtimeAssets = File.Exists(runtimeAssetsPath)
|
|
? File.ReadAllText(runtimeAssetsPath)
|
|
: throw new InvalidOperationException("Cannot verify runtime asset manifest; AetheriaRuntimeAssets.cs is missing.");
|
|
var requiredInventoryAssetSymbols = new[]
|
|
{
|
|
"InventoryUiAssets()",
|
|
"inventory.cell.background_atlas",
|
|
"inventory.cell.foreground_atlas",
|
|
"inventory.cell.thermal_layer_atlas",
|
|
"Sprites/Flat UI/Nodes/Nodes8BG",
|
|
"Sprites/Flat UI/Nodes/Nodes8",
|
|
"Sprites/Flat UI/pipes"
|
|
}
|
|
.Where(symbol => !runtimeAssets.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (requiredInventoryAssetSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime asset manifest must publish inventory UI spritesheets and palettes for CultUI inventory rendering: " +
|
|
string.Join(", ", requiredInventoryAssetSymbols));
|
|
}
|
|
|
|
var sectorRenderer = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"UI",
|
|
"Menu",
|
|
"SectorRenderer.cs"));
|
|
var compactSectorRenderer = CompactSource(sectorRenderer);
|
|
var requiredSectorRendererSharedDocumentSymbols = new[]
|
|
{
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-renderer\")",
|
|
"RuntimeState.CurrentZone.Latest()",
|
|
"RuntimeState.ZoneDetailsSurface(zoneIndex).Latest()"
|
|
}
|
|
.Select(CompactSource)
|
|
.Where(symbol => !compactSectorRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (requiredSectorRendererSharedDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SectorRenderer should sample shared sector UI state through named current typed Aetheria documents: " +
|
|
string.Join(", ", requiredSectorRendererSharedDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenSectorRendererSharedDocumentSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<",
|
|
".ReactiveSectorMap(\"unity-sector-renderer\")",
|
|
".ReactiveCurrentZone(\"unity-sector-renderer\")",
|
|
".ReactiveZoneDetails(zoneIndex, \"unity-sector-renderer\")",
|
|
".ReactiveCatalogSnapshot(\"unity-sector-renderer\")",
|
|
".ReactivePlayerSettingsDocument(\"unity-sector-renderer\")",
|
|
"AetheriaRuntimeSectorMapSession _sectorMap",
|
|
"AetheriaRuntimeCurrentZoneSession _currentZone",
|
|
"AetheriaRuntimeZoneDetailsSession _zoneDetails",
|
|
"AetheriaRuntimeCatalogSession _catalog",
|
|
"AetheriaRuntimePlayerSettingsSession _playerSettings",
|
|
".ObserveSectorMap()",
|
|
".ObserveZone()",
|
|
".ObserveZone(zoneIndex)",
|
|
"ResolveClient().State.ObserveCatalog()",
|
|
".ObservePlayer()",
|
|
"_sectorMap?.Dispose()",
|
|
"_currentZone?.Dispose()",
|
|
"_zoneDetails?.Dispose()",
|
|
"_catalog?.Dispose()",
|
|
"_playerSettings?.Dispose()"
|
|
};
|
|
var sectorRendererRawDocumentHits = forbiddenSectorRendererSharedDocumentSymbols
|
|
.Where(symbol => sectorRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (sectorRendererRawDocumentHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SectorRenderer still routes map/current-zone/details through wrapper/session/reactive handles instead of current typed documents: " +
|
|
string.Join(", ", sectorRendererRawDocumentHits));
|
|
}
|
|
|
|
var zoneRenderer = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"Zone Display",
|
|
"ZoneRenderer.cs"));
|
|
var requiredZoneRendererSharedDocumentSymbols = new[]
|
|
{
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-zone-renderer\")",
|
|
"_runtimeState.Catalog.Latest()",
|
|
"AetheriaUnityGameViewportDocuments _zonePresentationDocuments",
|
|
".ZonePresentationDocuments(",
|
|
"_zonePresentationDocuments?.Dispose()",
|
|
"_zonePresentationDocuments?.CurrentContacts",
|
|
"private void OnDestroy()"
|
|
};
|
|
var missingZoneRendererSharedDocumentSymbols = requiredZoneRendererSharedDocumentSymbols
|
|
.Where(symbol => !zoneRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingZoneRendererSharedDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer should sample catalog through current typed state while keeping contact state on managed reactive Aetheria documents: " +
|
|
string.Join(", ", missingZoneRendererSharedDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenZoneRendererSharedDocumentSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<AetheriaRuntimeZoneContactsDocument> _zoneContacts",
|
|
".ReactiveZoneContacts(\"unity-zone-renderer\")",
|
|
"_zoneContacts?.Current",
|
|
"_zoneContacts?.Dispose()",
|
|
"AetheriaRuntimeZoneContactsSession _zoneContacts",
|
|
"ResolveClient().State.ObserveZoneContacts()",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot>",
|
|
"AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-zone-renderer\")",
|
|
".ReactiveCatalogSnapshot(\"unity-zone-renderer\")",
|
|
"AetheriaRuntimeCatalogSession _catalog",
|
|
"ResolveClient().State.ObserveCatalog()",
|
|
"_catalog?.Dispose()"
|
|
};
|
|
var zoneRendererRawDocumentHits = forbiddenZoneRendererSharedDocumentSymbols
|
|
.Where(symbol => zoneRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (zoneRendererRawDocumentHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer still routes contact state through session wrappers instead of managed reactive typed documents: " +
|
|
string.Join(", ", zoneRendererRawDocumentHits));
|
|
}
|
|
|
|
Console.WriteLine("Shared document accessors: Unity shared runtime reads use named managed typed documents");
|
|
}
|
|
|
|
static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root)
|
|
{
|
|
var clientStatePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaClientState.cs");
|
|
if (!File.Exists(clientStatePath))
|
|
throw new InvalidOperationException("Cannot verify Unity viewport/map managed accessors; AetheriaClientState.cs is missing.");
|
|
|
|
var clientState = File.ReadAllText(clientStatePath);
|
|
var requiredClientSymbols = new[]
|
|
{
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeObjectsViewportDocument> ObjectsViewport(",
|
|
"public CultMeshDocumentHandle<EveFieldsSplatsDocument> RenderSplatsViewport(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeZoneDetailsDocument> ZoneDetails("
|
|
};
|
|
var missingClientSymbols = requiredClientSymbols
|
|
.Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingClientSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose named parameterized document handles for map, viewport, indexed, contacts, and current-entity documents: " +
|
|
string.Join(", ", missingClientSymbols));
|
|
}
|
|
|
|
var forbiddenClientSymbols = new[]
|
|
{
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeObjectsViewportDocument> ReactiveObjectsViewport(",
|
|
"public CultMeshReactiveDocument<EveFieldsSplatsDocument> ReactiveRenderSplatsViewport(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeZoneDetailsDocument> ReactiveZoneDetails("
|
|
};
|
|
var survivingClientSymbols = forbiddenClientSymbols
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingClientSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose parameterized map/viewport/indexed state as handles; callers use Handle.Reactive() when needed: " +
|
|
string.Join(", ", survivingClientSymbols));
|
|
}
|
|
|
|
var unityPaths = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityTargetPresentation.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRenderSplatViewportSource.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "HUD", "SchematicDisplay.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MapRenderer.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorMap.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs")
|
|
};
|
|
|
|
var missingPaths = unityPaths.Where(path => !File.Exists(path)).ToArray();
|
|
if (missingPaths.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot verify Unity viewport/map managed accessors; missing sources: " +
|
|
string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path))));
|
|
}
|
|
|
|
var forbiddenCompactedSymbols = new[]
|
|
{
|
|
".State.SectorMap.LatestAsync()",
|
|
".State.ZoneContacts.LatestAsync()",
|
|
".State.Current.Entity.LatestAsync()",
|
|
".State.Viewports.Objects(",
|
|
".State.Viewports.RenderSplats(",
|
|
".Details.Zone(zoneIndex).LatestAsync()"
|
|
}
|
|
.Select(CompactSource)
|
|
.ToArray();
|
|
var offenders = unityPaths
|
|
.Select(path => new
|
|
{
|
|
Path = path,
|
|
Compact = CompactSource(File.ReadAllText(path))
|
|
})
|
|
.Where(entry => forbiddenCompactedSymbols.Any(symbol => entry.Compact.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(entry => Path.GetRelativePath(root, entry.Path))
|
|
.ToArray();
|
|
|
|
if (offenders.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity map, viewport, contact, and current-entity reads still walk CultMesh document handles instead of managed typed document access: " +
|
|
string.Join(", ", offenders));
|
|
}
|
|
|
|
var renderSplatViewportSource = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"Gameplay",
|
|
"AetheriaUnityRenderSplatViewportSource.cs"));
|
|
var requiredRenderSplatViewportSourceSymbols = new[]
|
|
{
|
|
"AetheriaUnityGameViewportDocuments _viewportDocuments",
|
|
"_viewportDocuments?.CurrentRenderSplatsViewport",
|
|
"ClearViewportDocument()"
|
|
};
|
|
var missingRenderSplatViewportSourceSymbols = requiredRenderSplatViewportSourceSymbols
|
|
.Where(symbol => !renderSplatViewportSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (!CompactSource(renderSplatViewportSource).Contains(
|
|
".RenderSplatViewportDocuments(viewport,\"unity-render-splat-viewport\")",
|
|
StringComparison.Ordinal))
|
|
{
|
|
missingRenderSplatViewportSourceSymbols = missingRenderSplatViewportSourceSymbols
|
|
.Concat(new[] { ".RenderSplatViewportDocuments(viewport,\"unity-render-splat-viewport\")" })
|
|
.ToArray();
|
|
}
|
|
if (missingRenderSplatViewportSourceSymbols.Length > 0 ||
|
|
renderSplatViewportSource.Contains(".LatestRenderSplats(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityRenderSplatViewportSource must keep a managed reactive render-splats viewport document instead of polling latest snapshots: " +
|
|
string.Join(", ", missingRenderSplatViewportSourceSymbols));
|
|
}
|
|
|
|
if (renderSplatViewportSource.Contains("CultMeshReactiveDocument<EveFieldsSplatsDocument> _renderSplatsViewport", StringComparison.Ordinal) ||
|
|
renderSplatViewportSource.Contains(".ReactiveRenderSplatsViewport(", StringComparison.Ordinal) ||
|
|
renderSplatViewportSource.Contains("AetheriaRuntimeRenderSplatsViewportSession _renderSplatsViewport", StringComparison.Ordinal) ||
|
|
renderSplatViewportSource.Contains(".ObserveRenderSplats(viewport)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityRenderSplatViewportSource still routes render-splats viewports through raw/session wrapper state instead of a managed viewport document.");
|
|
}
|
|
|
|
if (renderSplatViewportSource.Contains("_fallbackRenderSplatsViewport", StringComparison.Ordinal) ||
|
|
renderSplatViewportSource.Contains("EmptyRenderSplatsViewport", StringComparison.Ordinal) ||
|
|
renderSplatViewportSource.Contains("new EveFieldsSplatsDocument", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityRenderSplatViewportSource must not fabricate renderer-local render splat documents; daemon/CultMesh viewport documents own render fields.");
|
|
}
|
|
|
|
var zoneRenderer = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"Zone Display",
|
|
"ZoneRenderer.cs"));
|
|
var requiredZoneRendererViewportSymbols = new[]
|
|
{
|
|
"AetheriaUnityGameViewportDocuments _zonePresentationDocuments",
|
|
"_zonePresentationDocuments.CurrentObjectsViewport",
|
|
"_zonePresentationDocuments?.Dispose()"
|
|
};
|
|
var missingZoneRendererViewportSymbols = requiredZoneRendererViewportSymbols
|
|
.Where(symbol => !zoneRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (!CompactSource(zoneRenderer).Contains(
|
|
".ZonePresentationDocuments(viewportBounds,\"unity-zone-renderer\")",
|
|
StringComparison.Ordinal))
|
|
{
|
|
missingZoneRendererViewportSymbols = missingZoneRendererViewportSymbols
|
|
.Concat(new[] { ".ZonePresentationDocuments(viewportBounds,\"unity-zone-renderer\")" })
|
|
.ToArray();
|
|
}
|
|
if (missingZoneRendererViewportSymbols.Length > 0 ||
|
|
zoneRenderer.Contains(".LatestObjects(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer fallback presentation queries must keep a managed reactive objects viewport document instead of polling latest snapshots: " +
|
|
string.Join(", ", missingZoneRendererViewportSymbols));
|
|
}
|
|
|
|
if (zoneRenderer.Contains("CultMeshReactiveDocument<AetheriaRuntimeObjectsViewportDocument> _objectsViewport", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains(".ReactiveObjectsViewport(", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains("AetheriaRuntimeObjectsViewportSession _objectsViewport", StringComparison.Ordinal) ||
|
|
zoneRenderer.Contains(".ObserveObjects(viewportBounds)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ZoneRenderer still routes fallback objects viewports through raw/session wrapper state instead of a managed viewport document.");
|
|
}
|
|
|
|
Console.WriteLine("Viewport and map document accessors: Unity reads fixed map/contact state and parameterized viewport state through named managed typed documents");
|
|
}
|
|
|
|
static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root)
|
|
{
|
|
var clientStatePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaClientState.cs");
|
|
var observedDaemonStatePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeDaemonRenderView.cs");
|
|
var daemonRuntimeDocumentTestsPath = Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"Tests",
|
|
"DaemonRuntimeDocumentTests.cs");
|
|
var starbridgePlayerSeatTestsPath = Path.Combine(
|
|
root,
|
|
"Assets",
|
|
"Scripts",
|
|
"Tests",
|
|
"StarbridgePlayerSeatDocumentTests.cs");
|
|
|
|
var requiredPaths = new[]
|
|
{
|
|
clientStatePath,
|
|
observedDaemonStatePath,
|
|
daemonRuntimeDocumentTestsPath,
|
|
starbridgePlayerSeatTestsPath
|
|
};
|
|
var missingPaths = requiredPaths.Where(path => !File.Exists(path)).ToArray();
|
|
if (missingPaths.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot verify Aetheria managed state accessor coverage; missing sources: " +
|
|
string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path))));
|
|
}
|
|
|
|
var clientState = File.ReadAllText(clientStatePath);
|
|
var daemonRuntimeDocumentTests = File.ReadAllText(daemonRuntimeDocumentTestsPath);
|
|
var topLevelClientState = clientState.Split(
|
|
"public sealed class AetheriaClientDaemonState",
|
|
StringSplitOptions.None)[0];
|
|
var requiredClientSymbols = new[]
|
|
{
|
|
"public enum AetheriaClientEveSurface",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonProviderAdvertisementDocument> ProviderAdvertisement { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonHealthDocument> Health { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonCommandBoundaryDocument> CommandBoundary { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeVerseAuthorityPolicyDocument> AuthorityPolicy { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> DaemonFrame { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> DaemonSoaView { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeZoneRenderDocument> ZoneRender { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeLoadoutTemplatesDocument> LoadoutTemplates { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeSectorMapDocument> SectorMap { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCurrentZoneDocument> CurrentZone { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeZoneContactsDocument> ZoneContacts { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeVerseHostSettingsDocument> VerseHostSettings { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCurrentDockingDocument> CurrentDocking { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCurrentEntityDocument> CurrentEntity { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeStationRefitDocument> StationRefit { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> Catalog { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimePlayerSettingsDocument> PlayerSettings { get; }",
|
|
"public static bool TryResolveEveSurface(",
|
|
"public CultMeshDocumentHandle<EveSurfaceDocument>? EveSurfaceDocument(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeGameViewportDocument> GameViewport(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeObjectsViewportDocument> ObjectsViewport(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeGravityViewportDocument> GravityViewport(",
|
|
"public CultMeshDocumentHandle<EveFieldsSplatsDocument> RenderSplatsViewport(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeZoneDetailsDocument> ZoneDetails(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument> ZoneDetailsSurface(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument> InventoryPanelSurface(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument> InventoryDropdownSurface(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeSelectedObjectDocument> SelectedObject(int entityIndex)",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument> Inventory(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeStarbridgeScenarioDocument> StarbridgeScenario { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeStarbridgeSessionDocument> StarbridgeSession { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeStarbridgeSessionSummaryDocument> StarbridgeSummary { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeStarbridgePlayerSeatDocument> StarbridgePlayerSeat("
|
|
};
|
|
var missingClientSymbols = requiredClientSymbols
|
|
.Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingClientSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose managed document handles for remaining domain documents: " +
|
|
string.Join(", ", missingClientSymbols));
|
|
}
|
|
if (clientState.Contains("SelectedObject(int zoneIndex)", StringComparison.Ordinal) ||
|
|
clientState.Contains("_selectedObject(zoneIndex)", StringComparison.Ordinal) ||
|
|
clientState.Contains("Aetheria zone index must be non-negative.\";\r\n\r\n return _selectedObject", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Selected object documents are entity-indexed; AetheriaClientState must not expose a zone-indexed selected-object handle.");
|
|
}
|
|
|
|
var forbiddenFixedReactiveSymbols = new[]
|
|
{
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeDaemonProviderAdvertisementDocument> ReactiveProviderAdvertisement()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeDaemonHealthDocument> ReactiveHealth()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeDaemonCommandBoundaryDocument> ReactiveCommandBoundary()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeVerseAuthorityPolicyDocument> ReactiveAuthorityPolicy()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> ReactiveDaemonFrame()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeDaemonSoaViewDocument> ReactiveDaemonSoaView()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeZoneRenderDocument> ReactiveZoneRender()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeLoadoutTemplatesDocument> ReactiveLoadoutTemplates()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeSectorMapDocument> ReactiveSectorMap()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeCurrentZoneDocument> ReactiveCurrentZone()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeZoneContactsDocument> ReactiveZoneContacts()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeVerseHostSettingsDocument> ReactiveVerseHostSettings()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeCurrentDockingDocument> ReactiveCurrentDocking()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeCurrentEntityDocument> ReactiveCurrentEntity()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeStationRefitDocument> ReactiveStationRefit()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot> ReactiveCatalogSnapshot()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument> ReactivePlayerSettingsDocument()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeStarbridgeScenarioDocument> ReactiveStarbridgeScenario()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeStarbridgeSessionDocument> ReactiveStarbridgeSession()",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeStarbridgeSessionSummaryDocument> ReactiveStarbridgeSummary()",
|
|
"public CultMeshReactiveDocument<global::Aetheria.State.Documents.EveSurfaceState>? ReactiveEveSurface(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeGameViewportDocument> ReactiveGameViewport(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeObjectsViewportDocument> ReactiveObjectsViewport(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeGravityViewportDocument> ReactiveGravityViewport(",
|
|
"public CultMeshReactiveDocument<EveFieldsSplatsDocument> ReactiveRenderSplatsViewport(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeZoneDetailsDocument> ReactiveZoneDetails(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeSurfaceDocument> ReactiveZoneDetailsSurface(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeSurfaceDocument> ReactiveInventoryPanelSurface(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeSurfaceDocument> ReactiveInventoryDropdownSurface(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeSelectedObjectDocument> ReactiveSelectedObject(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeInventoryDocument> ReactiveInventory(",
|
|
"public CultMeshReactiveDocument<AetheriaRuntimeStarbridgePlayerSeatDocument> ReactiveStarbridgePlayerSeat("
|
|
};
|
|
var survivingFixedReactiveSymbols = forbiddenFixedReactiveSymbols
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingFixedReactiveSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose fixed documents as handles; callers use Handle.Reactive() instead of wrapper methods: " +
|
|
string.Join(", ", survivingFixedReactiveSymbols));
|
|
}
|
|
|
|
if (clientState.Contains("public Task<TDocument> LatestAsync<TDocument>", StringComparison.Ordinal) ||
|
|
clientState.Contains("public TDocument Latest<TDocument>", StringComparison.Ordinal) ||
|
|
clientState.Contains("public TDocument Current<TDocument>", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<TDocument> Document<TDocument>", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshReactiveDocument<TDocument> Reactive<TDocument>", StringComparison.Ordinal) ||
|
|
clientState.Contains("TryGetDocument<TDocument>", StringComparison.Ordinal) ||
|
|
clientState.Contains("CultMeshDocumentCatalog", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeDaemonProviderAdvertisementDocument> ProviderAdvertisementDocument(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeDaemonHealthDocument> HealthDocument(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeDaemonCommandBoundaryDocument> CommandBoundaryDocument(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeVerseAuthorityPolicyDocument> AuthorityPolicyDocument(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> DaemonFrameDocument(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> DaemonSoaViewDocument(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> CatalogSnapshot(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimePlayerSettingsDocument> PlayerSettingsDocument(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeCurrentEntityDocument> CurrentEntityDocument(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeCurrentDockingDocument> CurrentDocking(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeStationRefitDocument> StationRefitDocument(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeStarbridgeSessionSummaryDocument> StarbridgeSummaryDocument(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public Task<CultMeshReactiveDocument<TDocument>> ReactiveAsync<TDocument>", StringComparison.Ordinal) ||
|
|
clientState.Contains("public Observable<TDocument> Watch<TDocument>", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must not reintroduce generic document catalogs, latest/watch shortcuts, or fixed FooDocument() wrappers; callers should use named typed document handles directly.");
|
|
}
|
|
|
|
var forbiddenCurrentStateAccessors = new[]
|
|
{
|
|
"public AetheriaRuntimeDaemonFrameDocument CurrentDaemonFrame()",
|
|
"public AetheriaRuntimeDaemonSoaViewDocument CurrentDaemonSoaView()",
|
|
"public AetheriaRuntimeCatalogSnapshot CurrentCatalog()",
|
|
"public AetheriaRuntimeLoadoutTemplatesDocument CurrentLoadoutTemplates()",
|
|
"public AetheriaRuntimePlayerSettingsDocument CurrentPlayerSettings()",
|
|
"public AetheriaRuntimeVerseHostSettingsDocument CurrentVerseHostSettings()",
|
|
"public AetheriaRuntimeCurrentZoneDocument CurrentZoneState()",
|
|
"public AetheriaRuntimeCurrentEntityDocument CurrentEntityState()",
|
|
"public AetheriaRuntimeCurrentDockingDocument CurrentDockingState()",
|
|
"public AetheriaRuntimeZoneContactsDocument CurrentZoneContacts()",
|
|
"public AetheriaRuntimeStationRefitDocument CurrentStationRefit()",
|
|
"public AetheriaRuntimeSectorMapDocument CurrentSectorMap()",
|
|
"public AetheriaRuntimeZoneRenderDocument CurrentZoneRender()",
|
|
"public AetheriaRuntimeGameViewportDocument CurrentGameViewport(",
|
|
"public AetheriaRuntimeObjectsViewportDocument CurrentObjectsViewport(",
|
|
"public AetheriaRuntimeGravityViewportDocument CurrentGravityViewport(",
|
|
"public EveFieldsSplatsDocument CurrentRenderSplatsViewport(",
|
|
"public AetheriaRuntimeZoneDetailsDocument CurrentZoneDetails(",
|
|
"public AetheriaRuntimeSurfaceDocument CurrentZoneDetailsSurface(",
|
|
"public AetheriaRuntimeSurfaceDocument CurrentInventoryPanelSurface(",
|
|
"public AetheriaRuntimeSurfaceDocument CurrentInventoryDropdownSurface(",
|
|
"public AetheriaRuntimeSelectedObjectDocument CurrentSelectedObject(",
|
|
"public AetheriaRuntimeInventoryDocument CurrentInventory(",
|
|
"public AetheriaRuntimeStarbridgeScenarioDocument CurrentStarbridgeScenario()",
|
|
"public AetheriaRuntimeStarbridgeSessionDocument CurrentStarbridgeSession()",
|
|
"public AetheriaRuntimeStarbridgeSessionSummaryDocument CurrentStarbridgeSummary()",
|
|
"public AetheriaRuntimeStarbridgePlayerSeatDocument CurrentStarbridgePlayerSeat("
|
|
}
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenCurrentStateAccessors.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose document handles; CurrentX snapshot helpers hide the managed typed document access model: " +
|
|
string.Join(", ", forbiddenCurrentStateAccessors));
|
|
}
|
|
|
|
var forbiddenFixedReactiveWrappers = new[]
|
|
{
|
|
"LatestCatalog(",
|
|
"LatestCatalogAsync(",
|
|
"ReactiveCatalog(",
|
|
"ReactiveCatalogAsync(",
|
|
"LatestLoadoutTemplates(",
|
|
"LatestLoadoutTemplatesAsync(",
|
|
"ReactiveLoadoutTemplatesAsync(",
|
|
"LatestSectorMap(",
|
|
"LatestSectorMapAsync(",
|
|
"ReactiveSectorMapAsync(",
|
|
"LatestZoneContacts(",
|
|
"LatestZoneContactsAsync(",
|
|
"ReactiveZoneContactsAsync(",
|
|
"LatestStationRefit(",
|
|
"LatestStationRefitAsync(",
|
|
"ReactiveStationRefitAsync(",
|
|
"LatestDaemonFrame(",
|
|
"LatestDaemonFrameAsync(",
|
|
"ReactiveDaemonFrameAsync(",
|
|
"LatestDaemonSoaView(",
|
|
"LatestDaemonSoaViewAsync(",
|
|
"ReactiveDaemonSoaViewAsync(",
|
|
"LatestZoneRender(",
|
|
"LatestZoneRenderAsync(",
|
|
"ReactiveZoneRenderAsync(",
|
|
"LatestPlayer(",
|
|
"LatestPlayerAsync(",
|
|
"ReactivePlayer(",
|
|
"ReactivePlayerAsync(",
|
|
"LatestVerseHost(",
|
|
"LatestVerseHostAsync(",
|
|
"ReactiveVerseHost(",
|
|
"ReactiveVerseHostAsync(",
|
|
"LatestProviderAdvertisement(",
|
|
"LatestProviderAdvertisementAsync(",
|
|
"LatestHealth(",
|
|
"LatestHealthAsync(",
|
|
"LatestCommandBoundary(",
|
|
"LatestCommandBoundaryAsync(",
|
|
"LatestAuthorityPolicy(",
|
|
"LatestAuthorityPolicyAsync(",
|
|
"LatestFrameDocument(",
|
|
"LatestFrameDocumentAsync(",
|
|
"LatestSoaViewDocument(",
|
|
"LatestSoaViewDocumentAsync(",
|
|
"LatestScenario(",
|
|
"LatestScenarioAsync(",
|
|
"ReactiveScenario(",
|
|
"ReactiveScenarioAsync(",
|
|
"LatestSession(",
|
|
"LatestSessionAsync(",
|
|
"ReactiveSession(",
|
|
"ReactiveSessionAsync(",
|
|
"LatestSummary(",
|
|
"LatestSummaryAsync(",
|
|
"ReactiveSummary(",
|
|
"ReactiveSummaryAsync(",
|
|
"LatestGameSurface(",
|
|
"LatestGameSurfaceAsync(",
|
|
"ReactiveGameSurface(",
|
|
"ReactiveGameSurfaceAsync(",
|
|
"LatestGameTuiSurface(",
|
|
"LatestGameTuiSurfaceAsync(",
|
|
"ReactiveGameTuiSurface(",
|
|
"ReactiveGameTuiSurfaceAsync(",
|
|
"LatestEditorSurface(",
|
|
"LatestEditorSurfaceAsync(",
|
|
"ReactiveEditorSurface(",
|
|
"ReactiveEditorSurfaceAsync(",
|
|
"LatestEditorTuiSurface(",
|
|
"LatestEditorTuiSurfaceAsync(",
|
|
"ReactiveEditorTuiSurface(",
|
|
"ReactiveEditorTuiSurfaceAsync("
|
|
};
|
|
var forbiddenObservedDaemonConvenienceWrappers = new[]
|
|
{
|
|
"LatestObservedDaemonAsync(",
|
|
"LatestObservedDaemon()",
|
|
"TryReadDaemonSoaViewAsync("
|
|
};
|
|
var survivingObservedDaemonConvenienceWrappers = forbiddenObservedDaemonConvenienceWrappers
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingObservedDaemonConvenienceWrappers.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState still exposes one-shot observed-daemon aggregate wrappers; use caller-owned managed typed documents: " +
|
|
string.Join(", ", survivingObservedDaemonConvenienceWrappers));
|
|
}
|
|
var survivingFixedReactiveWrappers = forbiddenFixedReactiveWrappers
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingFixedReactiveWrappers.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must not reintroduce latest/async fixed-document wrappers; use named reactive typed documents for ordinary gameplay state: " +
|
|
string.Join(", ", survivingFixedReactiveWrappers));
|
|
}
|
|
|
|
var forbiddenClientStateWrappers = new[]
|
|
{
|
|
"public AetheriaClientDaemonState Daemon { get; }",
|
|
"public AetheriaClientSettingsState Settings { get; }",
|
|
"public AetheriaClientCurrentState Current { get; }",
|
|
"public AetheriaClientViewportState Viewports { get; }",
|
|
"public AetheriaClientDetailState Details { get; }",
|
|
"public AetheriaClientStarbridgeState Starbridge { get; }",
|
|
"public sealed class AetheriaClientDaemonState",
|
|
"public sealed class AetheriaClientSettingsState",
|
|
"public sealed class AetheriaClientCurrentState",
|
|
"public sealed class AetheriaClientViewportState",
|
|
"public sealed class AetheriaClientDetailState",
|
|
"public sealed class AetheriaClientStarbridgeState"
|
|
};
|
|
var survivingClientStateWrappers = forbiddenClientStateWrappers
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingClientStateWrappers.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState still exposes nested wrapper state; use flat named typed handles for ordinary gameplay state: " +
|
|
string.Join(", ", survivingClientStateWrappers));
|
|
}
|
|
|
|
var forbiddenViewportConvenienceWrappers = new[]
|
|
{
|
|
"LatestMap(",
|
|
"LatestMapAsync(",
|
|
"ReactiveMap(",
|
|
"ReactiveMapAsync(",
|
|
"LatestObjects(",
|
|
"LatestObjectsAsync(",
|
|
"ReactiveObjectsAsync(",
|
|
"LatestGravity(",
|
|
"LatestGravityAsync(",
|
|
"ReactiveGravityAsync(",
|
|
"LatestRenderSplats(",
|
|
"LatestRenderSplatsAsync(",
|
|
"ReactiveRenderSplatsAsync("
|
|
};
|
|
var survivingViewportConvenienceWrappers = forbiddenViewportConvenienceWrappers
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingViewportConvenienceWrappers.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must avoid latest/async viewport wrappers; callers should use named reactive typed viewport documents: " +
|
|
string.Join(", ", survivingViewportConvenienceWrappers));
|
|
}
|
|
|
|
var forbiddenDetailConvenienceWrappers = new[]
|
|
{
|
|
"LatestZone(",
|
|
"LatestZoneAsync(",
|
|
"ReactiveZone(",
|
|
"ReactiveZoneAsync(",
|
|
"LatestSelectedObject(",
|
|
"LatestSelectedObjectAsync(",
|
|
"ReactiveSelectedObjectAsync(",
|
|
"LatestInventory(",
|
|
"LatestInventoryAsync(",
|
|
"ReactiveInventoryAsync("
|
|
};
|
|
var survivingDetailConvenienceWrappers = forbiddenDetailConvenienceWrappers
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingDetailConvenienceWrappers.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must avoid latest/async scoped wrappers; callers should use named reactive typed scoped documents: " +
|
|
string.Join(", ", survivingDetailConvenienceWrappers));
|
|
}
|
|
|
|
var forbiddenCurrentConvenienceWrappers = new[]
|
|
{
|
|
"LatestZone(",
|
|
"LatestZoneAsync(",
|
|
"ReactiveZone(",
|
|
"ReactiveZoneAsync(",
|
|
"LatestEntity(",
|
|
"LatestEntityAsync(",
|
|
"ReactiveEntity(",
|
|
"ReactiveEntityAsync(",
|
|
"LatestDocking(",
|
|
"LatestDockingAsync(",
|
|
"ReactiveDocking(",
|
|
"ReactiveDockingAsync("
|
|
};
|
|
var survivingCurrentConvenienceWrappers = forbiddenCurrentConvenienceWrappers
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingCurrentConvenienceWrappers.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must use top-level generic document access instead of named current-state wrappers: " +
|
|
string.Join(", ", survivingCurrentConvenienceWrappers));
|
|
}
|
|
|
|
var compactClientState = CompactSource(clientState);
|
|
var requiredStarbridgeParameterizedSymbols = new[]
|
|
{
|
|
"publicCultMeshDocumentHandle<AetheriaRuntimeStarbridgePlayerSeatDocument>StarbridgePlayerSeat(stringseatId)"
|
|
};
|
|
var missingStarbridgeParameterizedSymbols = requiredStarbridgeParameterizedSymbols
|
|
.Where(symbol => !compactClientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingStarbridgeParameterizedSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose named parameterized document handles for player-seat state: " +
|
|
string.Join(", ", missingStarbridgeParameterizedSymbols));
|
|
}
|
|
|
|
var forbiddenStarbridgeConvenienceWrappers = new[]
|
|
{
|
|
"LatestPlayerSeat(",
|
|
"LatestPlayerSeatAsync(",
|
|
"ReactivePlayerSeatAsync("
|
|
};
|
|
var survivingStarbridgeConvenienceWrappers = forbiddenStarbridgeConvenienceWrappers
|
|
.Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingStarbridgeConvenienceWrappers.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must avoid latest/async player-seat wrappers; callers should use named reactive typed player-seat documents: " +
|
|
string.Join(", ", survivingStarbridgeConvenienceWrappers));
|
|
}
|
|
|
|
var observedDaemonState = File.ReadAllText(observedDaemonStatePath);
|
|
if (!observedDaemonState.Contains("public static bool TryCreateCurrent(", StringComparison.Ordinal) ||
|
|
!observedDaemonState.Contains("new AetheriaRuntimeDaemonRenderView(currentFrame, currentSoaView, currentZoneRender)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed daemon state must compose current values from managed typed frame, SoA, and zone-render documents.");
|
|
}
|
|
|
|
if (File.Exists(Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeObservedDockingState.cs")))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeObservedDockingState must not return as a docking aggregate wrapper; callers should own the managed typed docking documents directly.");
|
|
}
|
|
|
|
if (!daemonRuntimeDocumentTests.Contains("using var currentEntityDocumentReactive = client.State.CurrentEntity.Reactive()", StringComparison.Ordinal) ||
|
|
!daemonRuntimeDocumentTests.Contains("using var currentDockingReactive = client.State.CurrentDocking.Reactive()", StringComparison.Ordinal) ||
|
|
!daemonRuntimeDocumentTests.Contains("using var stationRefitReactive = client.State.StationRefit.Reactive()", StringComparison.Ordinal) ||
|
|
!daemonRuntimeDocumentTests.Contains("currentDockingReactive.Current.CurrentEntityKey", StringComparison.Ordinal) ||
|
|
daemonRuntimeDocumentTests.Contains("client.State.ReactiveCurrentEntity()", StringComparison.Ordinal) ||
|
|
daemonRuntimeDocumentTests.Contains("client.State.ReactiveCurrentDocking()", StringComparison.Ordinal) ||
|
|
daemonRuntimeDocumentTests.Contains("client.State.ReactiveStationRefit()", StringComparison.Ordinal) ||
|
|
daemonRuntimeDocumentTests.Contains("AetheriaRuntimeObservedDockingState", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon runtime document tests must teach direct docking samples from caller-owned managed reactive typed documents.");
|
|
}
|
|
|
|
if (observedDaemonState.Contains("state.LatestFrame.ReactiveAsync", StringComparison.Ordinal) ||
|
|
observedDaemonState.Contains("state.LatestSoaView.ReactiveAsync", StringComparison.Ordinal) ||
|
|
observedDaemonState.Contains("AetheriaRuntimeReactiveObservedDaemonState", StringComparison.Ordinal) ||
|
|
observedDaemonState.Contains("ReadAsync(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed daemon state must not hide typed document access behind aggregate reactive compatibility wrappers.");
|
|
}
|
|
|
|
if (observedDaemonState.Contains("state.LatestAsync<AetheriaRuntimeDaemonFrameDocument>()", StringComparison.Ordinal) ||
|
|
observedDaemonState.Contains("state.LatestDaemonSoaViewAsync()", StringComparison.Ordinal) ||
|
|
observedDaemonState.Contains("state.ReactiveObservedDaemonAsync()", StringComparison.Ordinal) ||
|
|
observedDaemonState.Contains("TryReadLatestSoaViewAsync", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed daemon state ReadAsync still performs one-shot daemon reads instead of sampling the managed reactive observed document.");
|
|
}
|
|
|
|
var checkedSources = new Dictionary<string, string>
|
|
{
|
|
["DaemonRuntimeDocumentTests.cs"] = File.ReadAllText(daemonRuntimeDocumentTestsPath),
|
|
["StarbridgePlayerSeatDocumentTests.cs"] = File.ReadAllText(starbridgePlayerSeatTestsPath)
|
|
};
|
|
var forbiddenCompactedSymbols = new[]
|
|
{
|
|
".State.Catalog.LatestAsync()",
|
|
".State.Settings.Player.LatestAsync()",
|
|
".State.Settings.VerseHost.LatestAsync()",
|
|
".State.Current.Entity.LatestAsync()",
|
|
".State.Viewports.Objects(",
|
|
".State.Details.Zone(",
|
|
".State.Details.Inventory(",
|
|
".State.Starbridge.PlayerSeat(",
|
|
".State.DockingState.Latest(",
|
|
".State.DockingState.Reactive(",
|
|
"AetheriaRuntimeDaemonRenderView.ReadAsync(",
|
|
".State.Daemon.LatestFrame.LatestAsync()",
|
|
".State.Daemon.LatestSoaView.LatestAsync()",
|
|
".State.Daemon.AuthorityPolicy.LatestAsync()",
|
|
".State.Daemon.GameSurface.LatestAsync()",
|
|
".State.Daemon.GameTuiSurface.LatestAsync()",
|
|
".State.Daemon.EditorSurface.LatestAsync()",
|
|
".State.Daemon.EditorTuiSurface.LatestAsync()",
|
|
"client.State.Daemon.LatestGameSurface()",
|
|
"client.State.Daemon.LatestGameTuiSurface()",
|
|
"client.State.Daemon.LatestEditorSurface()",
|
|
"client.State.Daemon.LatestEditorTuiSurface()",
|
|
"client.State.LatestGameSurface()",
|
|
"client.State.LatestGameTuiSurface()",
|
|
"client.State.LatestEditorSurface()",
|
|
"client.State.LatestEditorTuiSurface()",
|
|
"client.State.ReactiveGameSurface()",
|
|
"client.State.ReactiveGameTuiSurface()",
|
|
"client.State.ReactiveEditorSurface()",
|
|
"client.State.ReactiveEditorTuiSurface()",
|
|
"client.State.ObserveCatalog()",
|
|
"client.State.ObserveDaemonFrame()",
|
|
"client.State.ObserveLoadoutTemplates()",
|
|
"client.State.ObserveSectorMap()",
|
|
"client.State.Settings.ObservePlayer()",
|
|
"client.State.ObserveZoneContacts()",
|
|
"client.State.Details.ObserveInventory(0)",
|
|
"client.State.Viewports.ObserveObjects(viewport)",
|
|
"client.State.Viewports.ObserveRenderSplats(viewport)"
|
|
}
|
|
.Select(CompactSource)
|
|
.ToArray();
|
|
var offenders = checkedSources
|
|
.Select(entry => new
|
|
{
|
|
entry.Key,
|
|
Compact = CompactSource(entry.Value)
|
|
})
|
|
.Where(entry => forbiddenCompactedSymbols.Any(symbol => entry.Compact.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(entry => entry.Key)
|
|
.ToArray();
|
|
|
|
if (offenders.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Managed state examples still teach raw CultMesh handle walks for ordinary latest reads: " +
|
|
string.Join(", ", offenders));
|
|
}
|
|
|
|
if (!clientState.Contains("StarbridgeSummary", StringComparison.Ordinal) ||
|
|
!checkedSources["DaemonRuntimeDocumentTests.cs"].Contains(
|
|
"client.State.StarbridgeSummary",
|
|
StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Starbridge summary must be registered in managed Aetheria typed document access.");
|
|
}
|
|
|
|
if (!checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains(
|
|
".StarbridgePlayerSeat(seat.SeatId)",
|
|
StringComparison.Ordinal) ||
|
|
!checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains(
|
|
".StarbridgePlayerSeat(seat.SeatId).Reactive()",
|
|
StringComparison.Ordinal) ||
|
|
checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains(
|
|
".ReactiveStarbridgePlayerSeat(seat.SeatId)",
|
|
StringComparison.Ordinal) ||
|
|
checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains(
|
|
".Latest<AetheriaRuntimeStarbridgePlayerSeatDocument>(seat.SeatId)",
|
|
StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Starbridge player-seat examples must use named parameterized managed document access instead of raw handle walks.");
|
|
}
|
|
|
|
Console.WriteLine("Domain document accessors: managed Aetheria state exposes named fixed-document reads plus parameterized viewport/detail/Starbridge/Eve-surface access");
|
|
}
|
|
|
|
static void RequireVerseHostSettingsAuthority(string root)
|
|
{
|
|
var settingsPath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaVerseHostSettings.cs");
|
|
var normalizerPath = Path.Combine(root, "Aetheria.State", "AetheriaVerseHostSettingsNormalizer.cs");
|
|
var verseCatalogDocumentsPath = Path.Combine(root, "Aetheria.State", "AetheriaVerseCatalogDocuments.cs");
|
|
var deletedVerseCatalogProjectorPath = Path.Combine(root, "Aetheria.State", "AetheriaVerseCatalogProjector.cs");
|
|
var verseDiscoveryHostPath = Path.Combine(root, "Aetheria.State", "AetheriaVerseDiscoveryHost.cs");
|
|
var registryPath = Path.Combine(root, "Aetheria.State", "AetheriaDocumentRegistry.cs");
|
|
var nodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs");
|
|
var providerPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs");
|
|
var operationsPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs");
|
|
var daemonHostPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs");
|
|
|
|
var requiredFiles = new[]
|
|
{
|
|
settingsPath,
|
|
normalizerPath,
|
|
verseCatalogDocumentsPath,
|
|
verseDiscoveryHostPath,
|
|
registryPath,
|
|
nodePath,
|
|
providerPath,
|
|
operationsPath,
|
|
daemonHostPath
|
|
};
|
|
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse host authority cut is incomplete; missing files: " + string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var settings = File.ReadAllText(settingsPath);
|
|
var normalizer = File.ReadAllText(normalizerPath);
|
|
if (File.Exists(deletedVerseCatalogProjectorPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaVerseCatalogProjector should stay deleted; Verse discovery publishes typed catalog documents, not projector chaff.");
|
|
}
|
|
|
|
var verseCatalogDocuments = File.ReadAllText(verseCatalogDocumentsPath);
|
|
var verseDiscoveryHost = File.ReadAllText(verseDiscoveryHostPath);
|
|
var registry = File.ReadAllText(registryPath);
|
|
var node = File.ReadAllText(nodePath);
|
|
var provider = File.ReadAllText(providerPath);
|
|
var operations = File.ReadAllText(operationsPath);
|
|
var daemonHost = File.ReadAllText(daemonHostPath);
|
|
|
|
var requiredSettingsSymbols = new[]
|
|
{
|
|
"[CultDocument(\"aetheria.verse_host_settings\", \"aetheria.verse_host_settings.v1\")]",
|
|
"public sealed class AetheriaVerseHostSettings",
|
|
"public string VerseId",
|
|
"public string CultMeshAddress",
|
|
"public string Visibility"
|
|
};
|
|
var missingSettingsSymbols = requiredSettingsSymbols
|
|
.Where(symbol => !settings.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSettingsSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed verse-host settings document is missing required authority fields: " +
|
|
string.Join(", ", missingSettingsSymbols));
|
|
}
|
|
|
|
if (!normalizer.Contains("public static AetheriaVerseHostSettings Normalize", StringComparison.Ordinal) ||
|
|
!normalizer.Contains("public static bool Equivalent", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse-host settings normalizer is missing normalize/equivalence ownership.");
|
|
}
|
|
|
|
var requiredVerseCatalogDocumentSymbols = new[]
|
|
{
|
|
"public static class AetheriaVerseCatalogDocuments",
|
|
"CultMeshVerseDescriptor",
|
|
"BuildDiscoveryEndpoint",
|
|
"CultMeshVerseDescriptor.ComputeRulesHash",
|
|
"cultnet://"
|
|
};
|
|
var missingVerseCatalogDocumentSymbols = requiredVerseCatalogDocumentSymbols
|
|
.Where(symbol => !verseCatalogDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingVerseCatalogDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse catalog documents no longer derive public CultMesh discovery from typed host settings: " +
|
|
string.Join(", ", missingVerseCatalogDocumentSymbols));
|
|
}
|
|
|
|
var requiredVerseDiscoveryHostSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaVerseDiscoveryHost",
|
|
"AetheriaVerseCatalogDocuments.Build(normalized)",
|
|
"CultMesh.CreateVerseCatalog()",
|
|
"CultMesh.ServeVerseCatalog(_node.MeshNode, _catalog)",
|
|
"normalized.Visibility"
|
|
};
|
|
var missingVerseDiscoveryHostSymbols = requiredVerseDiscoveryHostSymbols
|
|
.Where(symbol => !verseDiscoveryHost.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingVerseDiscoveryHostSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse discovery host no longer gates served Verse catalogs through the typed host settings: " +
|
|
string.Join(", ", missingVerseDiscoveryHostSymbols));
|
|
}
|
|
|
|
if (!registry.Contains("CultMesh.CreateCultNetDocumentRegistry(DocumentTypes, registry)", StringComparison.Ordinal) ||
|
|
!registry.Contains("typeof(AetheriaVerseHostSettings)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria document registry does not register typed verse-host settings.");
|
|
}
|
|
|
|
if (!node.Contains("public CultMeshMutableStatePointer<TDocument> MutableDocument<TDocument>(CultRecordKey key)", StringComparison.Ordinal) ||
|
|
!node.Contains("public static CultRecordKey VerseHostSettingsKey", StringComparison.Ordinal) ||
|
|
!node.Contains("public static CultRecordKey PlayerSettingsKey", StringComparison.Ordinal) ||
|
|
!node.Contains("public static CultRecordKey EveCommandAcceptanceStatusKey", StringComparison.Ordinal) ||
|
|
!node.Contains("public CultMeshNode MeshNode => _node;", StringComparison.Ordinal) ||
|
|
!node.Contains("global:aetheria.verse_host_settings.v1", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state node does not expose typed verse-host/settings managed state pointers.");
|
|
}
|
|
|
|
if (node.Contains("public CultMeshMutableStatePointer<AetheriaVerseHostSettings> VerseHostSettings()", StringComparison.Ordinal) ||
|
|
node.Contains("public CultMeshMutableStatePointer<AetheriaPlayerSettings> PlayerSettings()", StringComparison.Ordinal) ||
|
|
node.Contains("public CultMeshMutableStatePointer<AetheriaEveCommandAcceptanceStatus> EveCommandAcceptanceStatus()", StringComparison.Ordinal) ||
|
|
node.Contains("public CultMeshMutableStatePointer<EveSurfaceState> CatalogSurface()", StringComparison.Ordinal) ||
|
|
node.Contains("public CultMeshMutableStatePointer<EveSurfaceState> OperationsSurface()", StringComparison.Ordinal) ||
|
|
node.Contains("public CultMeshMutableStatePointer<EveSurfaceState> PlayerSettingsSurface()", StringComparison.Ordinal) ||
|
|
node.Contains("public CultMeshMutableStatePointer<EveProviderAdvertisementState> ProviderAdvertisementSurface()", StringComparison.Ordinal) ||
|
|
node.Contains("public CultMeshMutableStatePointer<AetheriaRuntimeSession> RuntimeSession(", StringComparison.Ordinal) ||
|
|
node.Contains("public CultMeshMutableStatePointer<AetheriaTradeValuePolicy> TradeValuePolicy()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state node still exposes named document helpers instead of generic mutable typed document access.");
|
|
}
|
|
|
|
var forbiddenHardcodedProviderSymbols = new[]
|
|
{
|
|
"VerseId = \"aetheria.local\"",
|
|
"RootVerse = \"asgard\"",
|
|
"CanonicalService = \"asgard.aetheria\"",
|
|
"LocatedService = \"asgard.local.aetheria\"",
|
|
"CultMeshAddress = \"asgard.local.aetheria/eve\""
|
|
};
|
|
var survivingHardcodedProviderSymbols = forbiddenHardcodedProviderSymbols
|
|
.Where(symbol => provider.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingHardcodedProviderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Provider advertisement still hardcodes daemon verse identity: " +
|
|
string.Join(", ", survivingHardcodedProviderSymbols));
|
|
}
|
|
|
|
if (!provider.Contains("AetheriaVerseHostSettings settings", StringComparison.Ordinal) ||
|
|
!provider.Contains("AetheriaVerseHostSettingsNormalizer.Normalize(settings)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Provider advertisement no longer derives Verse identity from typed daemon settings.");
|
|
}
|
|
|
|
if (!operations.Contains("AetheriaVerseHostSettings? verseHostSettings = null", StringComparison.Ordinal) ||
|
|
!operations.Contains("AetheriaVerseHostSettingsNormalizer.Normalize(verseHostSettings)", StringComparison.Ordinal) ||
|
|
!operations.Contains("\"aetheria.operations.verseHost\"", StringComparison.Ordinal) ||
|
|
!operations.Contains("Metric(\"verseHost.visibility\"", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Operations surface does not expose the typed daemon Verse-host telemetry card.");
|
|
}
|
|
|
|
var requiredDaemonHostSymbols = new[]
|
|
{
|
|
"EnsureVerseHostSettingsAsync",
|
|
"AetheriaVerseDiscoveryHost",
|
|
"discoveryHost.Update",
|
|
"ReadOption(args, \"--verse-id\")",
|
|
"ReadOption(args, \"--cultmesh-address\")",
|
|
"node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey).ReadAsync()",
|
|
"node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey)",
|
|
".ReplaceAsync(normalized)",
|
|
"AetheriaEveSurfaceDocuments.BuildProviderAdvertisement(verseHost, node.StatePath, updatedAtUtc)"
|
|
};
|
|
var missingDaemonHostSymbols = requiredDaemonHostSymbols
|
|
.Where(symbol => !daemonHost.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonHostSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria.State.Daemon is missing typed verse-host startup authority: " +
|
|
string.Join(", ", missingDaemonHostSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireClientTargetBootAuthority(string root)
|
|
{
|
|
var unityPackageProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj");
|
|
var boundaryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateBoundary.cs");
|
|
var bootPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateBoot.cs");
|
|
var clientTargetStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetStore.cs");
|
|
var clientTargetCommandsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetCommands.cs");
|
|
var aetheriaStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaState.cs");
|
|
var verseDiscoveryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseDiscovery.cs");
|
|
var replicaBridgePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseReplicaBridge.cs");
|
|
var mainMenuSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeMainMenuSurfaceBuilder.cs");
|
|
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");
|
|
var eveRuntimeClientCachePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveRuntimeUnityClientCache.cs");
|
|
var eveRuntimeHookInstallerPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveRuntimeUnityHookInstaller.cs");
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var gameplayBootShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs");
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
|
|
var requiredFiles = new[]
|
|
{
|
|
unityPackageProjectPath,
|
|
boundaryPath,
|
|
bootPath,
|
|
clientTargetStorePath,
|
|
clientTargetCommandsPath,
|
|
aetheriaStatePath,
|
|
verseDiscoveryPath,
|
|
replicaBridgePath,
|
|
mainMenuSurfaceBuilderPath,
|
|
bootstrapPath,
|
|
presenterPath,
|
|
eveRuntimeClientCachePath,
|
|
eveRuntimeHookInstallerPath,
|
|
actionGameManagerPath,
|
|
gameplayBootShellPath,
|
|
mainMenuPath
|
|
};
|
|
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Client target boot authority cannot be verified because required files are missing: " +
|
|
string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var unityPackageProject = File.ReadAllText(unityPackageProjectPath);
|
|
var boundary = File.ReadAllText(boundaryPath);
|
|
var boot = File.ReadAllText(bootPath);
|
|
var clientTargetStore = File.ReadAllText(clientTargetStorePath);
|
|
var clientTargetCommands = File.ReadAllText(clientTargetCommandsPath);
|
|
var aetheriaState = File.ReadAllText(aetheriaStatePath);
|
|
var verseDiscovery = File.ReadAllText(verseDiscoveryPath);
|
|
var replicaBridge = File.ReadAllText(replicaBridgePath);
|
|
var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath);
|
|
var bootstrap = File.ReadAllText(bootstrapPath);
|
|
var presenter = File.ReadAllText(presenterPath);
|
|
var eveRuntimeClientCache = File.ReadAllText(eveRuntimeClientCachePath);
|
|
var eveRuntimeHookInstaller = File.ReadAllText(eveRuntimeHookInstallerPath);
|
|
var actionGameManager = File.ReadAllText(actionGameManagerPath);
|
|
var gameplayBootShell = File.ReadAllText(gameplayBootShellPath);
|
|
var mainMenu = File.ReadAllText(mainMenuPath);
|
|
|
|
var requiredUnityPackageProjectSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeClientTargetStore.cs",
|
|
"AetheriaState.cs",
|
|
"AetheriaRuntimeVerseDiscovery.cs",
|
|
"AetheriaRuntimeStateBoundary.cs",
|
|
"AetheriaRuntimeStateBoot.cs",
|
|
"AetheriaRuntimeVerseReplicaBridge.cs"
|
|
};
|
|
var missingUnityPackageProjectSymbols = requiredUnityPackageProjectSymbols
|
|
.Where(symbol => !unityPackageProject.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingUnityPackageProjectSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"GameCult.Aetheria.State.Unity.csproj no longer compiles the shared client-target boot files: " +
|
|
string.Join(", ", missingUnityPackageProjectSymbols));
|
|
}
|
|
|
|
var requiredBoundarySymbols = new[]
|
|
{
|
|
"RuntimeClientTargetFileName = \"aetheria-client.cc\"",
|
|
"RuntimeReplicaDirectoryName = \"Verses\"",
|
|
"RuntimeStatePathOverrideEnvironmentVariable = \"AETHERIA_STATE_PATH\"",
|
|
"GetClientTargetPath",
|
|
"GetReplicaStateFilePath",
|
|
"ResolveStatePathOverride"
|
|
};
|
|
var missingBoundarySymbols = requiredBoundarySymbols
|
|
.Where(symbol => !boundary.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBoundarySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime state boundary no longer owns the client target file and override resolution: " +
|
|
string.Join(", ", missingBoundarySymbols));
|
|
}
|
|
|
|
var requiredClientTargetSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeClientTargetKinds",
|
|
"public sealed class AetheriaRuntimeClientTargetDocument",
|
|
"SchemaId = \"gamecult.aetheria.runtime_client_target.v1\"",
|
|
"TargetKind",
|
|
"StateFilePath",
|
|
"CultMeshAddress",
|
|
"DiscoveryEndpoints",
|
|
"DiscoveredVerses",
|
|
"LastDiscoveryAtUtc",
|
|
"LastDiscoveryError",
|
|
"ReplicaStateFilePath",
|
|
"LastReplicaSyncAtUtc",
|
|
"LastReplicaSyncError",
|
|
"ReadOrInitialize",
|
|
"CreateDefault",
|
|
"Write(string clientTargetPath, AetheriaRuntimeClientTargetDocument document)",
|
|
"Read(string clientTargetPath)"
|
|
};
|
|
var missingClientTargetSymbols = requiredClientTargetSymbols
|
|
.Where(symbol => !clientTargetStore.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingClientTargetSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed client target store is incomplete: " +
|
|
string.Join(", ", missingClientTargetSymbols));
|
|
}
|
|
|
|
var requiredBootSymbols = new[]
|
|
{
|
|
"ClientTargetPath",
|
|
"TargetKind",
|
|
"TargetSource",
|
|
"SupportsLocalStateFileRead",
|
|
"FailureMessage",
|
|
"DiscoveryEndpoints",
|
|
"DiscoveredVerses",
|
|
"LastDiscoveryAtUtc",
|
|
"LastDiscoveryError",
|
|
"ReplicaStateFilePath",
|
|
"LastReplicaSyncAtUtc",
|
|
"LastReplicaSyncError",
|
|
"TargetLabel",
|
|
"AetheriaRuntimeClientTargetStore.ReadOrInitialize",
|
|
"AetheriaRuntimeStateBoundary.ResolveStatePathOverride()",
|
|
"AetheriaRuntimeClientTargetKinds.CultMeshVerse",
|
|
"GetReplicaStateFilePath",
|
|
"Sync the local replica",
|
|
"state-path-override",
|
|
"client-target"
|
|
};
|
|
var missingBootSymbols = requiredBootSymbols
|
|
.Where(symbol => !boot.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBootSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime boot no longer resolves the active Verse through the client target owner: " +
|
|
string.Join(", ", missingBootSymbols));
|
|
}
|
|
|
|
var requiredVerseDiscoverySymbols = new[]
|
|
{
|
|
"CultMesh.CreateVerseCatalog()",
|
|
"CultMesh.CreateVerseDiscoveryClient()",
|
|
"DiscoverAsync(catalog, endpoints)",
|
|
"DiscoveredVerses",
|
|
"LastDiscoveryAtUtc",
|
|
"LastDiscoveryError"
|
|
};
|
|
var missingVerseDiscoverySymbols = requiredVerseDiscoverySymbols
|
|
.Where(symbol => !verseDiscovery.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingVerseDiscoverySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime Verse discovery helper no longer refreshes the typed client-target catalog through CultMesh discovery: " +
|
|
string.Join(", ", missingVerseDiscoverySymbols));
|
|
}
|
|
|
|
var requiredAetheriaStateSymbols = new[]
|
|
{
|
|
"public readonly struct AetheriaState",
|
|
"public static AetheriaState At(DirectoryInfo gameDataDirectory)",
|
|
"public AetheriaClientTarget ClientTarget",
|
|
"public readonly struct AetheriaClientTarget",
|
|
"public AetheriaRuntimeClientTargetDocument Refresh()",
|
|
"public AetheriaRuntimeClientTargetDocument DiscoverVerses()",
|
|
"public AetheriaRuntimeClientTargetDocument SyncReplica()",
|
|
"public AetheriaRuntimeClientTargetDocument CycleTransport()",
|
|
"public AetheriaRuntimeClientTargetDocument RequestTitle(string title)",
|
|
"public AetheriaRuntimeClientTargetDocument RequestVerseId(string verseId)",
|
|
"public AetheriaRuntimeClientTargetDocument RequestCultMeshAddress(string cultMeshAddress)",
|
|
"public AetheriaRuntimeClientTargetDocument RequestStateFilePath(string stateFilePath)",
|
|
"public AetheriaRuntimeClientTargetDocument RequestDiscoveryEndpoints(IEnumerable<string>? discoveryEndpoints)",
|
|
"public AetheriaRuntimeClientTargetDocument SelectDiscoveredVerse(",
|
|
"AetheriaRuntimeStateBoundary.GetClientTargetPath(_gameDataDirectory)",
|
|
"AetheriaRuntimeStateBoundary.GetStateFilePath(_gameDataDirectory)",
|
|
"AetheriaRuntimeVerseDiscovery.RefreshClientTarget(",
|
|
"AetheriaRuntimeVerseReplicaBridge.Sync(",
|
|
"AetheriaRuntimeClientTargetStore.Update(",
|
|
"NormalizeDiscoveryEndpoints(",
|
|
"GetReplicaStateFilePath(gameDataDirectory, document.VerseId)"
|
|
};
|
|
var missingAetheriaStateSymbols = requiredAetheriaStateSymbols
|
|
.Where(symbol => !aetheriaState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingAetheriaStateSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar no longer owns typed client-target mutation: " +
|
|
string.Join(", ", missingAetheriaStateSymbols));
|
|
}
|
|
|
|
if (aetheriaState.Contains("public bool TryApply(AetheriaClientTargetOperation", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("public AetheriaRuntimeClientTargetDocument Apply(AetheriaClientTargetOperation", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar still exposes Apply vocabulary for client-target edits; Unity should request typed target changes.");
|
|
}
|
|
|
|
if (aetheriaState.Contains("public bool TryRequest(AetheriaClientTargetOperation", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("public AetheriaRuntimeClientTargetDocument Request(AetheriaClientTargetOperation", StringComparison.Ordinal) ||
|
|
clientTargetCommands.Contains("public readonly struct AetheriaClientTargetOperation", StringComparison.Ordinal) ||
|
|
clientTargetCommands.Contains("public enum AetheriaClientTargetOperationKind", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria client-target sugar still exposes generic operation-bag dispatch instead of native request methods plus a surface document API.");
|
|
}
|
|
|
|
var forbiddenClientTargetSetters = new[]
|
|
{
|
|
"public AetheriaRuntimeClientTargetDocument SetTitle(",
|
|
"public AetheriaRuntimeClientTargetDocument SetVerseId(",
|
|
"public AetheriaRuntimeClientTargetDocument SetCultMeshAddress(",
|
|
"public AetheriaRuntimeClientTargetDocument SetStateFilePath(",
|
|
"public AetheriaRuntimeClientTargetDocument SetDiscoveryEndpoints("
|
|
};
|
|
var clientTargetSetterHits = forbiddenClientTargetSetters
|
|
.Where(symbol => aetheriaState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (clientTargetSetterHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar still exposes Set vocabulary for client-target edits; Unity should request typed target changes: " +
|
|
string.Join(", ", clientTargetSetterHits));
|
|
}
|
|
|
|
if (aetheriaState.Contains("DaemonOperations", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar regressed to command-applier naming instead of native state handles.");
|
|
}
|
|
|
|
if (aetheriaState.Contains("AetheriaRuntimeStateReader", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("TryReadDaemonFrame", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("ReadVerseHostSettings", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("AetheriaRuntimeDaemonFrameStore", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("AetheriaRuntimeCatalogStore", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar still exposes file-backed daemon state reads; clients should use AetheriaRuntimeVerseClient.");
|
|
}
|
|
|
|
if (aetheriaState.Contains("IReadOnlyDictionary<string, string>", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar exposes generic dictionary payload plumbing instead of typed client-target operations.");
|
|
}
|
|
|
|
if (aetheriaState.Contains("EveSurfaceCommandRequest", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("ReadPayloadValue(", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("request.Payload", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar still exposes Eve request payload plumbing instead of typed client-target operations.");
|
|
}
|
|
|
|
var requiredReplicaBridgeSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeVerseReplicaBridge",
|
|
"AetheriaRuntimeVerseReplicaSyncResult",
|
|
"Aetheria.State.Replica",
|
|
"Sync(",
|
|
"ProcessStartInfo",
|
|
"AetheriaRuntimeStateBoundary.GetReplicaStateFilePath"
|
|
};
|
|
var missingReplicaBridgeSymbols = requiredReplicaBridgeSymbols
|
|
.Where(symbol => !replicaBridge.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingReplicaBridgeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime Verse replica bridge is incomplete: " +
|
|
string.Join(", ", missingReplicaBridgeSymbols));
|
|
}
|
|
|
|
var requiredActionGameManagerSymbols = new[]
|
|
{
|
|
"private AetheriaUnityGameplayBootShell GameplayBootShell =>",
|
|
"var boot = GameplayBootShell.Boot();",
|
|
"SceneWiring.ConfigureCurrentEntityPresentation(_currentEntityPresentation, boot.RuntimeCatalog);",
|
|
"SceneWiring.ConfigureTargetPresentation(",
|
|
"boot.RuntimeCatalog,",
|
|
"SceneWiring.ConfigureActionBarPresentation(",
|
|
"ItemManager = boot.ItemManager;",
|
|
"_loadoutItemFactory = boot.LoadoutItemFactory;"
|
|
};
|
|
var missingActionGameManagerSymbols = requiredActionGameManagerSymbols
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingActionGameManagerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager no longer delegates gameplay boot through AetheriaUnityGameplayBootShell: " +
|
|
string.Join(", ", missingActionGameManagerSymbols));
|
|
}
|
|
|
|
var forbiddenActionGameManagerBootSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory)",
|
|
"Aetheria runtime target: {stateBoot.TargetLabel} via {stateBoot.TargetKind} ({stateBoot.TargetSource})",
|
|
"!stateBoot.SupportsLocalStateFileRead",
|
|
"stateBoot.StateFileExists",
|
|
"private static AetheriaRuntimeCatalogSnapshot _runtimeCatalog",
|
|
"FindTypedRuntimeItem(",
|
|
"_runtimeCatalog?.FindItem(",
|
|
".OpenRuntimeCatalog()"
|
|
};
|
|
var actionGameManagerBootHits = forbiddenActionGameManagerBootSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (actionGameManagerBootHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns runtime-target boot details instead of the Unity gameplay boot shell: " +
|
|
string.Join(", ", actionGameManagerBootHits));
|
|
}
|
|
|
|
var requiredGameplayBootShellSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityGameplayBootShell",
|
|
"public AetheriaUnityGameplayBootResult Boot()",
|
|
"AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory)",
|
|
"Aetheria runtime target: {stateBoot.TargetLabel} via {stateBoot.TargetKind} ({stateBoot.TargetSource})",
|
|
"Aetheria runtime id: {stateBoot.RuntimeId}",
|
|
"Aetheria runtime state file: {stateBoot.StateFilePath}",
|
|
"!stateBoot.SupportsLocalStateFileRead",
|
|
"stateBoot.StateFileExists",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)",
|
|
"runtimeState.Catalog.Latest()",
|
|
"runtimeState.SectorMap.Latest()",
|
|
"new ItemManager(",
|
|
"new AetheriaUnityLoadoutItemFactory(itemManager, runtimeCatalog)",
|
|
"ZoneRenderer.SetDroppedPickupItemFactory(loadoutItemFactory.CreateLoadoutItem)",
|
|
"ZoneRenderer.BodySettingsCollections = Settings.BodySettingsCollections",
|
|
"runtimeState?.DaemonFrame?.Latest()",
|
|
"render/game-feel settings are daemon authority",
|
|
"CockpitHudShell.SetRenderSettings(ZoneRenderer.RenderSettings)",
|
|
"AetheriaUnityRuntimeClientProvider.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap",
|
|
"public readonly struct AetheriaUnityGameplayBootResult"
|
|
};
|
|
var missingGameplayBootShellSymbols = requiredGameplayBootShellSymbols
|
|
.Where(symbol => !gameplayBootShell.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingGameplayBootShellSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityGameplayBootShell no longer boots gameplay through the shared client-target report: " +
|
|
string.Join(", ", missingGameplayBootShellSymbols));
|
|
}
|
|
|
|
if (gameplayBootShell.Contains(".ObserveCatalog()", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains(".ObserveSectorMap()", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains(".ReactiveCatalogSnapshot()", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("runtimeCatalogDocument.Current", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains(".ReactiveSectorMap()", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("sectorMapDocument.Current", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentCatalog(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentSectorMap(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.ResolveClient(stateBoot.StateFilePath, stateBoot.RuntimeId)", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains(".State;"))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityGameplayBootShell must acquire runtime state through provider-native typed state access, not legacy wrappers or full-client peels.");
|
|
}
|
|
|
|
var requiredPresenterSymbols = new[]
|
|
{
|
|
"ResolveStateBoot()",
|
|
"AetheriaEveRuntimeUnityHooks.RequireStateBoot(",
|
|
"!stateBoot.SupportsLocalStateFileRead",
|
|
"stateBoot.StateFileExists"
|
|
};
|
|
var missingPresenterSymbols = requiredPresenterSymbols
|
|
.Where(symbol => !presenter.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingPresenterSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve presenter no longer mounts through the shared client-target boot report: " +
|
|
string.Join(", ", missingPresenterSymbols));
|
|
}
|
|
|
|
var requiredEveRuntimeClientCacheSymbols = new[]
|
|
{
|
|
"public static class AetheriaEveRuntimeUnityClientCache",
|
|
"new DirectoryInfo(Application.dataPath).Parent.CreateSubdirectory(\"GameData\")",
|
|
"AetheriaRuntimeStateBoot.Inspect(GameDataDirectory).StateFilePath",
|
|
"AetheriaClient",
|
|
".OpenAsync(",
|
|
"startServer: false",
|
|
"pullOnOpen: true",
|
|
"CreateEveSurfaceCultMeshStateRefResolver()",
|
|
"public static void Dispose()"
|
|
};
|
|
var missingEveRuntimeClientCacheSymbols = requiredEveRuntimeClientCacheSymbols
|
|
.Where(symbol => !eveRuntimeClientCache.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingEveRuntimeClientCacheSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve runtime package no longer owns its generic Unity client cache: " +
|
|
string.Join(", ", missingEveRuntimeClientCacheSymbols));
|
|
}
|
|
|
|
var requiredEveRuntimeHookInstallerSymbols = new[]
|
|
{
|
|
"public static class AetheriaEveRuntimeUnityHookInstaller",
|
|
"RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)",
|
|
"public static void Install()",
|
|
"AetheriaEveRuntimeUnityHooks.ResolveStateBoot",
|
|
"AetheriaRuntimeStateBoot.Inspect(",
|
|
"AetheriaEveRuntimeUnityClientCache.GameDataDirectory",
|
|
"AetheriaEveRuntimeUnityHooks.RuntimeState",
|
|
"AetheriaEveRuntimeUnityHooks.Control",
|
|
"AetheriaEveRuntimeUnityHooks.Ui",
|
|
"AetheriaEveRuntimeUnityHooks.StateRefResolver"
|
|
};
|
|
var missingEveRuntimeHookInstallerSymbols = requiredEveRuntimeHookInstallerSymbols
|
|
.Where(symbol => !eveRuntimeHookInstaller.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingEveRuntimeHookInstallerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve runtime package no longer owns Unity client-target hook installation: " +
|
|
string.Join(", ", missingEveRuntimeHookInstallerSymbols));
|
|
}
|
|
|
|
if (File.Exists(Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityEveRuntimeHooks.cs")))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve runtime hooks are still installed by the legacy Unity gameplay assembly; the provider package must own this boundary.");
|
|
}
|
|
|
|
if (bootstrap.Contains("StatePathEnvironmentVariable", StringComparison.Ordinal) ||
|
|
bootstrap.Contains("presenter.StateFilePathOverride =", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve runtime bootstrap still tries to own state-path override resolution.");
|
|
}
|
|
|
|
var requiredMainMenuSymbols = new[]
|
|
{
|
|
"CurrentStateBoot()",
|
|
"ResolveSectorMap(AetheriaRuntimeStateBootReport stateBoot)",
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
".RuntimeState(stateBoot, \"unity-main-menu\")",
|
|
".SectorMap",
|
|
".Latest()",
|
|
"ResolveMainMenuSurface(",
|
|
".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)",
|
|
"AetheriaState.At(AetheriaUnityRuntimePaths.GameDataDirectory)",
|
|
".ClientTarget",
|
|
"RequestClientTargetCommand(request)",
|
|
"AetheriaRuntimeClientTargetSurfaceCommands.TryRequest("
|
|
};
|
|
var requiredMainMenuBuilderSymbols = new[]
|
|
{
|
|
"private static AetheriaRuntimeSurfaceDocument BuildRoot(",
|
|
"\"STARBRIDGE\"",
|
|
"GravitySurface($",
|
|
"AetheriaRuntimeDaemonSchemas.RenderSplatsViewport",
|
|
"AetheriaRuntimeDaemonSchemas.GravityViewport",
|
|
"AetheriaRuntimeDaemonSchemas.ObjectsViewport",
|
|
"new AetheriaRuntimeEmbeddedDocumentSlot("
|
|
};
|
|
var missingMainMenuSymbols = requiredMainMenuSymbols
|
|
.Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingMainMenuSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu no longer lowers the shared client-target boot state through Eve: " +
|
|
string.Join(", ", missingMainMenuSymbols));
|
|
}
|
|
var missingMainMenuBuilderSymbols = requiredMainMenuBuilderSymbols
|
|
.Where(symbol => !mainMenuSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingMainMenuBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared main-menu surface builder no longer lowers the client-target boot state through Eve: " +
|
|
string.Join(", ", missingMainMenuBuilderSymbols));
|
|
}
|
|
|
|
if (mainMenu.Contains("PrepareDocumentAccess(stateBoot)", StringComparison.Ordinal) ||
|
|
mainMenu.Contains(".ReactiveSectorMap(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains(".ReactivePlayerSettingsDocument(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains(".ReactiveVerseHostSettings(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("CultMeshReactiveDocument<AetheriaRuntimeSectorMapDocument>", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument>", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("CultMeshReactiveDocument<AetheriaRuntimeVerseHostSettingsDocument>", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu should sample named current typed documents instead of owning reactive document handles.");
|
|
}
|
|
|
|
var forbiddenDirectPathSymbols = new Dictionary<string, string[]>
|
|
{
|
|
[actionGameManagerPath] = new[]
|
|
{
|
|
"AetheriaRuntimeStateBoundary.GetStateFilePath(GameDataDirectory)"
|
|
},
|
|
[presenterPath] = new[]
|
|
{
|
|
"AetheriaRuntimeStateBoundary.GetStateFilePath(gameDataDirectory)"
|
|
},
|
|
[bootstrapPath] = new[]
|
|
{
|
|
"StatePathOverride()"
|
|
},
|
|
[mainMenuPath] = new[]
|
|
{
|
|
"AetheriaRuntimeStateBoundary.",
|
|
"AetheriaRuntimeClientTargetStore.",
|
|
"AetheriaRuntimeVerseDiscovery.RefreshClientTarget(",
|
|
"AetheriaRuntimeVerseReplicaBridge.Sync(",
|
|
"AetheriaRuntimeClientTarget.",
|
|
"DaemonOperations",
|
|
}
|
|
};
|
|
|
|
var hits = forbiddenDirectPathSymbols
|
|
.SelectMany(entry =>
|
|
{
|
|
var source = File.ReadAllText(entry.Key);
|
|
return entry.Value
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, entry.Key)} -> {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity boot still contains direct local state-path owners instead of the shared client target: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireClientRuntimeConformanceContract(string root)
|
|
{
|
|
var conformancePath = Path.Combine(root, "Aetheria.State", "docs", "client-runtime-conformance.md");
|
|
var authorityPlanPath = Path.Combine(root, "Aetheria.State", "docs", "verse-authority-implementation-plan.md");
|
|
var ergonomicsPlanPath = Path.Combine(root, "Aetheria.State", "docs", "cultmesh-ergonomics-staged-migration-plan.md");
|
|
|
|
var conformance = File.Exists(conformancePath)
|
|
? File.ReadAllText(conformancePath)
|
|
: throw new InvalidOperationException("Client runtime conformance contract is missing.");
|
|
var authorityPlan = File.Exists(authorityPlanPath)
|
|
? File.ReadAllText(authorityPlanPath)
|
|
: throw new InvalidOperationException("Verse authority implementation plan is missing.");
|
|
var ergonomicsPlan = File.Exists(ergonomicsPlanPath)
|
|
? File.ReadAllText(ergonomicsPlanPath)
|
|
: throw new InvalidOperationException("CultMesh ergonomics staged migration plan is missing.");
|
|
|
|
var requiredContractSymbols = new[]
|
|
{
|
|
"Hermodr | Unspecialized Eve/browser lowering sanity check for the RTS surface.",
|
|
"Electron | Player-facing Starbridge RTS client.",
|
|
"Unity | Current ARPG reference client and demolition target.",
|
|
"Godot | Future unspecialized Eve/runtime parity target for the ARPG surface.",
|
|
"No daemon-side `Godot` mode, `Electron` mode, `Hermodr` mode, or `Unity`",
|
|
"No Aetheria-specific Hermodr plugin for RTS map semantics.",
|
|
"Godot lowers the ARPG contract without inheriting Unity authority."
|
|
};
|
|
var missingContractSymbols = requiredContractSymbols
|
|
.Where(symbol => !conformance.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingContractSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Client runtime conformance contract no longer names the runtime authority map: " +
|
|
string.Join(", ", missingContractSymbols));
|
|
}
|
|
|
|
var requiredPlanSymbols = new[]
|
|
{
|
|
"Hermodr is the unspecialized Eve sanity check for the RTS surface",
|
|
"player-facing RTS client",
|
|
"future unspecialized Eve parity target for the ARPG surface",
|
|
"Hermodr must be able to reconstruct the RTS surface from Eve plus daemon API",
|
|
"Any daemon-side `Godot` behavior branch would be the wrong cut"
|
|
};
|
|
var missingPlanSymbols = requiredPlanSymbols
|
|
.Where(symbol => !authorityPlan.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingPlanSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse authority plan no longer preserves Hermodr/Electron/Godot/Unity conformance roles: " +
|
|
string.Join(", ", missingPlanSymbols));
|
|
}
|
|
|
|
var requiredErgonomicsSymbols = new[]
|
|
{
|
|
"Hermodr can reconstruct the RTS gameplay surface as an unspecialized Eve",
|
|
"Electron may be the shipped RTS shell but cannot own private",
|
|
"Godot can become the ARPG equivalent conformance target"
|
|
};
|
|
var missingErgonomicsSymbols = requiredErgonomicsSymbols
|
|
.Where(symbol => !ergonomicsPlan.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingErgonomicsSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"CultMesh ergonomics plan no longer carries the client runtime conformance gate: " +
|
|
string.Join(", ", missingErgonomicsSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireVerseReplicaTool(string root)
|
|
{
|
|
var replicaToolProjectPath = Path.Combine(root, "Aetheria.State.Replica", "Aetheria.State.Replica.csproj");
|
|
var replicaToolProgramPath = Path.Combine(root, "Aetheria.State.Replica", "Program.cs");
|
|
var replicaHostPath = Path.Combine(root, "Aetheria.State", "AetheriaVerseReplica.cs");
|
|
var stateReadmePath = Path.Combine(root, "Aetheria.State", "README.md");
|
|
|
|
var requiredFiles = new[]
|
|
{
|
|
replicaToolProjectPath,
|
|
replicaToolProgramPath,
|
|
replicaHostPath,
|
|
stateReadmePath
|
|
};
|
|
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse replica tool cannot be verified because required files are missing: " +
|
|
string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var replicaToolProject = File.ReadAllText(replicaToolProjectPath);
|
|
var replicaToolProgram = File.ReadAllText(replicaToolProgramPath);
|
|
var replicaHost = File.ReadAllText(replicaHostPath);
|
|
var stateReadme = File.ReadAllText(stateReadmePath);
|
|
|
|
var requiredProjectSymbols = new[]
|
|
{
|
|
"<ProjectReference Include=\"..\\Aetheria.State\\Aetheria.State.csproj\" />"
|
|
};
|
|
var missingProjectSymbols = requiredProjectSymbols
|
|
.Where(symbol => !replicaToolProject.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingProjectSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse replica tool project is incomplete: " +
|
|
string.Join(", ", missingProjectSymbols));
|
|
}
|
|
|
|
if (replicaToolProject.Contains("Aetheria.State.Unity", StringComparison.Ordinal) ||
|
|
replicaToolProject.Contains("GameCult.Aetheria.State.Unity", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse replica tool should not depend on Unity state assemblies; replica sync must stay daemon-state native.");
|
|
}
|
|
|
|
var requiredProgramSymbols = new[]
|
|
{
|
|
"sync",
|
|
"follow",
|
|
"AetheriaVerseReplica",
|
|
"ResolveReplicaPath",
|
|
"--endpoint",
|
|
"--replica",
|
|
"--game-data-root",
|
|
"--verse-id",
|
|
"--runtime-id"
|
|
};
|
|
var missingProgramSymbols = requiredProgramSymbols
|
|
.Where(symbol => !replicaToolProgram.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingProgramSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse replica tool program is incomplete: " +
|
|
string.Join(", ", missingProgramSymbols));
|
|
}
|
|
|
|
var requiredReplicaHostSymbols = new[]
|
|
{
|
|
"public static class AetheriaVerseReplica",
|
|
"SyncSnapshotAsync",
|
|
"RunReplicaAsync",
|
|
"CultNetSchemaShardSnapshotFetcher",
|
|
"CultNetSchemaShardLogFetcher",
|
|
"CultNetShardReplicator",
|
|
"ApplyRawSnapshotResponseAsync"
|
|
};
|
|
var missingReplicaHostSymbols = requiredReplicaHostSymbols
|
|
.Where(symbol => !replicaHost.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingReplicaHostSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse replica host is incomplete: " +
|
|
string.Join(", ", missingReplicaHostSymbols));
|
|
}
|
|
|
|
if (!stateReadme.Contains("Aetheria.State.Replica", StringComparison.Ordinal) ||
|
|
!stateReadme.Contains("GameData\\Verses\\<verse>.cc", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria.State README does not describe the cache-only Verse replica workflow.");
|
|
}
|
|
}
|
|
|
|
static void RequireVerseSettingsShellAndBridge(string root)
|
|
{
|
|
var unityPackageProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj");
|
|
var stateProjectPath = Path.Combine(root, "Aetheria.State", "Aetheria.State.csproj");
|
|
var clientTargetCommandsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetCommands.cs");
|
|
var aetheriaStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaState.cs");
|
|
var clientTargetSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetSurfaceBuilder.cs");
|
|
var verseHostCommandsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseHostCommands.cs");
|
|
var clientTargetStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetStore.cs");
|
|
var verseDiscoveryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseDiscovery.cs");
|
|
var replicaBridgePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseReplicaBridge.cs");
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
var commandBridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs");
|
|
var acceptanceStatusPath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaEveCommandAcceptanceStatus.cs");
|
|
var operationsSurfaceDocumentsPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs");
|
|
var daemonHostPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs");
|
|
|
|
var requiredFiles = new[]
|
|
{
|
|
unityPackageProjectPath,
|
|
stateProjectPath,
|
|
clientTargetCommandsPath,
|
|
aetheriaStatePath,
|
|
clientTargetSurfaceBuilderPath,
|
|
verseHostCommandsPath,
|
|
clientTargetStorePath,
|
|
verseDiscoveryPath,
|
|
replicaBridgePath,
|
|
mainMenuPath,
|
|
commandBridgePath,
|
|
acceptanceStatusPath,
|
|
operationsSurfaceDocumentsPath,
|
|
daemonHostPath
|
|
};
|
|
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse settings shell/bridge cannot be verified because required files are missing: " +
|
|
string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var unityPackageProject = File.ReadAllText(unityPackageProjectPath);
|
|
var stateProject = File.ReadAllText(stateProjectPath);
|
|
var clientTargetCommands = File.ReadAllText(clientTargetCommandsPath);
|
|
var aetheriaState = File.ReadAllText(aetheriaStatePath);
|
|
var clientTargetSurfaceBuilder = File.ReadAllText(clientTargetSurfaceBuilderPath);
|
|
var verseHostCommands = File.ReadAllText(verseHostCommandsPath);
|
|
var clientTargetStore = File.ReadAllText(clientTargetStorePath);
|
|
var verseDiscovery = File.ReadAllText(verseDiscoveryPath);
|
|
var replicaBridge = File.ReadAllText(replicaBridgePath);
|
|
var mainMenu = File.ReadAllText(mainMenuPath);
|
|
var commandBridge = File.ReadAllText(commandBridgePath);
|
|
var acceptanceStatus = File.ReadAllText(acceptanceStatusPath);
|
|
var operationsSurfaceDocuments = File.ReadAllText(operationsSurfaceDocumentsPath);
|
|
var daemonHost = File.ReadAllText(daemonHostPath);
|
|
|
|
var requiredUnityPackageProjectSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeClientTargetCommands.cs",
|
|
"AetheriaState.cs",
|
|
"AetheriaRuntimeClientTargetSurfaceBuilder.cs",
|
|
"AetheriaRuntimeVerseDiscovery.cs",
|
|
"AetheriaRuntimeVerseReplicaBridge.cs"
|
|
};
|
|
var missingUnityPackageProjectSymbols = requiredUnityPackageProjectSymbols
|
|
.Where(symbol => !unityPackageProject.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingUnityPackageProjectSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"GameCult.Aetheria.State.Unity.csproj does not compile the Verse settings shell files: " +
|
|
string.Join(", ", missingUnityPackageProjectSymbols));
|
|
}
|
|
|
|
var stateProjectIncludesRuntimePackage = stateProject.Contains(@"Runtime\*.cs", StringComparison.Ordinal);
|
|
if (!stateProjectIncludesRuntimePackage &&
|
|
!stateProject.Contains("AetheriaRuntimeVerseHostCommands.cs", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria.State.csproj does not compile the Verse-host Eve command contract.");
|
|
}
|
|
|
|
var requiredClientTargetCommandSymbols = new[]
|
|
{
|
|
"SurfaceId = \"aetheria.client_target\"",
|
|
"CycleTargetKind",
|
|
"SetTitle",
|
|
"SetVerseId",
|
|
"SetCultMeshAddress",
|
|
"SetStateFilePath",
|
|
"SetDiscoveryEndpoints",
|
|
"DiscoverVerses",
|
|
"SelectDiscoveredVerse",
|
|
"SyncReplica",
|
|
"IsKnown"
|
|
};
|
|
var missingClientTargetCommandSymbols = requiredClientTargetCommandSymbols
|
|
.Where(symbol => !clientTargetCommands.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingClientTargetCommandSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Client-target command contract is incomplete: " +
|
|
string.Join(", ", missingClientTargetCommandSymbols));
|
|
}
|
|
|
|
var requiredVerseHostCommandSymbols = new[]
|
|
{
|
|
"SurfaceId = \"aetheria.verse_host_settings\"",
|
|
"CycleVisibility",
|
|
"Refresh",
|
|
"IsKnown"
|
|
};
|
|
var missingVerseHostCommandSymbols = requiredVerseHostCommandSymbols
|
|
.Where(symbol => !verseHostCommands.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingVerseHostCommandSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse-host command contract is incomplete: " +
|
|
string.Join(", ", missingVerseHostCommandSymbols));
|
|
}
|
|
|
|
var requiredSurfaceBuilderSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeStateBootReport stateBoot",
|
|
"AetheriaRuntimeVerseHostSettingsDocument verseHost",
|
|
"var targetKind = stateBoot.TargetKind ?? AetheriaRuntimeClientTargetKinds.StateFile",
|
|
"var discoveredVerses = stateBoot.DiscoveredVerses ?? Array.Empty<AetheriaRuntimeDiscoveredVerse>()",
|
|
"Metric(\"aetheria.clientTarget.summary.target\", \"Target\", stateBoot.TargetLabel)",
|
|
"Metric(\"aetheria.clientTarget.host.verse\", \"Verse\", BuildVerseLabel(hostTitle, hostVerseId))",
|
|
"\"Client Target\"",
|
|
"\"Target Fields\"",
|
|
"\"Verse Discovery\"",
|
|
"\"Daemon Verse Host\"",
|
|
"\"Replica State File\"",
|
|
"\"Replica Sync\"",
|
|
"AetheriaRuntimeClientTargetCommands.SetStateFilePath",
|
|
"AetheriaRuntimeClientTargetCommands.SetDiscoveryEndpoints",
|
|
"AetheriaRuntimeClientTargetCommands.DiscoverVerses",
|
|
"AetheriaRuntimeClientTargetCommands.SelectDiscoveredVerse",
|
|
"AetheriaRuntimeClientTargetCommands.SyncReplica",
|
|
"AetheriaRuntimeVerseHostCommands.CycleVisibility",
|
|
"AetheriaRuntimeVerseHostCommands.Refresh"
|
|
};
|
|
var missingSurfaceBuilderSymbols = requiredSurfaceBuilderSymbols
|
|
.Where(symbol => !clientTargetSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSurfaceBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Client-target Eve surface builder is incomplete: " +
|
|
string.Join(", ", missingSurfaceBuilderSymbols));
|
|
}
|
|
|
|
if (clientTargetSurfaceBuilder.Contains("AetheriaRuntimeClientTargetSurfaceState", StringComparison.Ordinal) ||
|
|
clientTargetSurfaceBuilder.Contains("new AetheriaRuntimeClientTargetSurfaceState(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Client-target Eve surface builder must compose from typed boot and Verse-host documents directly; do not rebuild a shadow SurfaceState layer.");
|
|
}
|
|
|
|
if (!clientTargetStore.Contains("Update(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Client-target store no longer exposes typed update ownership for local target edits.");
|
|
}
|
|
|
|
var requiredAetheriaStateSymbols = new[]
|
|
{
|
|
"public readonly struct AetheriaState",
|
|
"public AetheriaClientTarget ClientTarget",
|
|
"public readonly struct AetheriaClientTarget",
|
|
"public AetheriaRuntimeClientTargetDocument Refresh()",
|
|
"public AetheriaRuntimeClientTargetDocument DiscoverVerses()",
|
|
"public AetheriaRuntimeClientTargetDocument SyncReplica()",
|
|
"public AetheriaRuntimeClientTargetDocument CycleTransport()",
|
|
"public AetheriaRuntimeClientTargetDocument RequestTitle(string title)",
|
|
"public AetheriaRuntimeClientTargetDocument RequestVerseId(string verseId)",
|
|
"public AetheriaRuntimeClientTargetDocument RequestCultMeshAddress(string cultMeshAddress)",
|
|
"public AetheriaRuntimeClientTargetDocument RequestStateFilePath(string stateFilePath)",
|
|
"public AetheriaRuntimeClientTargetDocument RequestDiscoveryEndpoints(IEnumerable<string>? discoveryEndpoints)",
|
|
"public AetheriaRuntimeClientTargetDocument SelectDiscoveredVerse(",
|
|
"AetheriaRuntimeClientTargetStore.Update(",
|
|
"AetheriaRuntimeVerseDiscovery.RefreshClientTarget(",
|
|
"AetheriaRuntimeVerseReplicaBridge.Sync(",
|
|
"NormalizeDiscoveryEndpoints("
|
|
};
|
|
var missingAetheriaStateSymbols = requiredAetheriaStateSymbols
|
|
.Where(symbol => !aetheriaState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingAetheriaStateSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar no longer owns the Verse settings mutation bridge: " +
|
|
string.Join(", ", missingAetheriaStateSymbols));
|
|
}
|
|
|
|
if (aetheriaState.Contains("public bool TryApply(AetheriaClientTargetOperation", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("public AetheriaRuntimeClientTargetDocument Apply(AetheriaClientTargetOperation", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar still exposes Apply vocabulary for Verse settings edits; Unity should request typed target changes.");
|
|
}
|
|
|
|
if (aetheriaState.Contains("public bool TryRequest(AetheriaClientTargetOperation", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("public AetheriaRuntimeClientTargetDocument Request(AetheriaClientTargetOperation", StringComparison.Ordinal) ||
|
|
clientTargetCommands.Contains("public readonly struct AetheriaClientTargetOperation", StringComparison.Ordinal) ||
|
|
clientTargetCommands.Contains("public enum AetheriaClientTargetOperationKind", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse settings sugar still exposes generic operation-bag dispatch instead of native request methods plus a surface document API.");
|
|
}
|
|
|
|
var forbiddenClientTargetSetters = new[]
|
|
{
|
|
"public AetheriaRuntimeClientTargetDocument SetTitle(",
|
|
"public AetheriaRuntimeClientTargetDocument SetVerseId(",
|
|
"public AetheriaRuntimeClientTargetDocument SetCultMeshAddress(",
|
|
"public AetheriaRuntimeClientTargetDocument SetStateFilePath(",
|
|
"public AetheriaRuntimeClientTargetDocument SetDiscoveryEndpoints("
|
|
};
|
|
var clientTargetSetterHits = forbiddenClientTargetSetters
|
|
.Where(symbol => aetheriaState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (clientTargetSetterHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar still exposes Set vocabulary for Verse settings edits; Unity should request typed target changes: " +
|
|
string.Join(", ", clientTargetSetterHits));
|
|
}
|
|
|
|
if (aetheriaState.Contains("DaemonOperations", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar regressed to command-applier naming instead of native state handles.");
|
|
}
|
|
|
|
if (aetheriaState.Contains("AetheriaRuntimeStateReader", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("TryReadDaemonFrame", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("ReadVerseHostSettings", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("AetheriaRuntimeDaemonFrameStore", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("AetheriaRuntimeCatalogStore", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar still exposes file-backed daemon state reads; clients should use AetheriaRuntimeVerseClient.");
|
|
}
|
|
|
|
if (aetheriaState.Contains("IReadOnlyDictionary<string, string>", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar exposes generic dictionary payload plumbing instead of typed client-target operations.");
|
|
}
|
|
|
|
if (aetheriaState.Contains("EveSurfaceCommandRequest", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("ReadPayloadValue(", StringComparison.Ordinal) ||
|
|
aetheriaState.Contains("request.Payload", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria state sugar still exposes Eve request payload plumbing instead of typed client-target operations.");
|
|
}
|
|
|
|
var requiredVerseDiscoverySymbols = new[]
|
|
{
|
|
"CultMesh.CreateVerseCatalog()",
|
|
"CultMesh.CreateVerseDiscoveryClient()",
|
|
"DiscoverAsync(catalog, endpoints)"
|
|
};
|
|
var missingVerseDiscoverySymbols = requiredVerseDiscoverySymbols
|
|
.Where(symbol => !verseDiscovery.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingVerseDiscoverySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse discovery helper no longer routes scans through the CultMesh catalog client: " +
|
|
string.Join(", ", missingVerseDiscoverySymbols));
|
|
}
|
|
|
|
var requiredReplicaBridgeSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeVerseReplicaBridge",
|
|
"ProcessStartInfo",
|
|
"Aetheria.State.Replica",
|
|
"Sync("
|
|
};
|
|
var missingReplicaBridgeSymbols = requiredReplicaBridgeSymbols
|
|
.Where(symbol => !replicaBridge.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingReplicaBridgeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse replica bridge no longer exposes the sync transport used by the Unity shell: " +
|
|
string.Join(", ", missingReplicaBridgeSymbols));
|
|
}
|
|
|
|
var requiredMainMenuSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings",
|
|
"ShowVerseSettingsSurface(",
|
|
"HandleVerseSettingsSurfaceCommand(EveSurfaceCommandRequest request)",
|
|
"ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId)",
|
|
".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)",
|
|
"AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)",
|
|
"AetheriaRuntimeMainMenuCommandKind.ClientTargetCommand",
|
|
"AetheriaRuntimeMainMenuCommandKind.VerseHostCommand",
|
|
"RequestClientTargetCommand(request)",
|
|
"SendKnownAetheriaEveCommand(request, \"Verse-host\")",
|
|
".Ui(stateBoot, \"unity-main-menu\")",
|
|
".SurfaceCommandAsync(request, \"unity-main-menu\")",
|
|
"AetheriaState.At(AetheriaUnityRuntimePaths.GameDataDirectory)",
|
|
".ClientTarget",
|
|
"AetheriaRuntimeClientTargetSurfaceCommands.TryRequest("
|
|
};
|
|
var missingMainMenuSymbols = requiredMainMenuSymbols
|
|
.Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingMainMenuSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu no longer owns the typed Verse settings shell handoff: " +
|
|
string.Join(", ", missingMainMenuSymbols));
|
|
}
|
|
|
|
var forbiddenSubmitAcceptanceSymbols = new[]
|
|
{
|
|
"private static bool TryRequestClientTargetCommand(",
|
|
"private static bool TrySendKnownAetheriaEveCommand(",
|
|
"if (TryRequestClientTargetCommand(request))",
|
|
"if (TrySendKnownAetheriaEveCommand(request, \"Verse-host\"))"
|
|
};
|
|
var submitAcceptanceHits = forbiddenSubmitAcceptanceSymbols
|
|
.Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (submitAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu Verse settings command lowering still treats submission as local acceptance state: " +
|
|
string.Join(", ", submitAcceptanceHits));
|
|
}
|
|
|
|
var forbiddenMainMenuMutationSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeClientTargetStore.",
|
|
"AetheriaRuntimeVerseDiscovery.RefreshClientTarget(",
|
|
"AetheriaRuntimeVerseReplicaBridge.Sync(",
|
|
"AetheriaRuntimeStateBoundary.",
|
|
"AetheriaRuntimeClientTarget.",
|
|
"DaemonOperations",
|
|
"ReadPayloadValue(",
|
|
"ParseDiscoveryEndpoints(",
|
|
"request.Payload",
|
|
"IReadOnlyDictionary<string, string>",
|
|
"clientTarget.SetTitle(",
|
|
"clientTarget.SetVerseId(",
|
|
"clientTarget.SetCultMeshAddress(",
|
|
"clientTarget.SetStateFilePath(",
|
|
"clientTarget.SetDiscoveryEndpoints(",
|
|
"clientTarget.DiscoverVerses()",
|
|
"clientTarget.SelectDiscoveredVerse(",
|
|
"clientTarget.SyncReplica()",
|
|
"TryQueueVerseHostCommand(",
|
|
"TrySendVerseHostCommand(request.Command)",
|
|
"CommandKindForSurface(request)",
|
|
"AetheriaRuntimeClientTargetCommands.IsKnown(request.Command",
|
|
"AetheriaRuntimeVerseHostCommands.IsKnown(request.Command",
|
|
"new EveSurfaceCommandRequest(",
|
|
"AetheriaRuntimeEveCommandLog.Queue",
|
|
};
|
|
var mainMenuMutationHits = forbiddenMainMenuMutationSymbols
|
|
.Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (mainMenuMutationHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still mutates the client target directly instead of dispatching through the runtime daemon operations: " +
|
|
string.Join(", ", mainMenuMutationHits));
|
|
}
|
|
|
|
var requiredBridgeSymbols = new[]
|
|
{
|
|
"AcceptedVerseHostCommands",
|
|
"AetheriaRuntimeEveCommandKind.VerseHostRefresh",
|
|
"AetheriaRuntimeEveCommandKind.CycleVerseHostVisibility",
|
|
"ExecuteVerseHostCommandAsync",
|
|
"node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey)",
|
|
".ReplaceAsync(normalized)",
|
|
"node.MutableDocument<EveProviderAdvertisementDocument>(AetheriaStateNode.ProviderAdvertisementSurfaceKey)",
|
|
"AetheriaEveSurfaceDocuments.BuildOperationsSurface(",
|
|
"switch (command.Kind)"
|
|
};
|
|
var missingBridgeSymbols = requiredBridgeSymbols
|
|
.Where(symbol => !commandBridge.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBridgeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve command bridge no longer routes Verse-host commands through the provider owner: " +
|
|
string.Join(", ", missingBridgeSymbols));
|
|
}
|
|
|
|
var requiredAcceptanceSymbols = new[]
|
|
{
|
|
"AetheriaEveCommandAcceptanceStatus",
|
|
"aetheria.eve_command_acceptance_status.v1",
|
|
"ObservedBeforeAccept",
|
|
"AppliedVerseHostCommands"
|
|
};
|
|
var missingAcceptanceSymbols = requiredAcceptanceSymbols
|
|
.Where(symbol => !acceptanceStatus.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingAcceptanceSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve command acceptance status no longer records Verse-host command counts: " +
|
|
string.Join(", ", missingAcceptanceSymbols));
|
|
}
|
|
|
|
if (File.Exists(Path.Combine(root, "Aetheria.State", "Documents", "AetheriaEveCommandDrainStatus.cs")) ||
|
|
acceptanceStatus.Contains("AetheriaEveCommandDrainStatus", StringComparison.Ordinal) ||
|
|
acceptanceStatus.Contains("aetheria.eve_command_drain_status", StringComparison.Ordinal) ||
|
|
daemonHost.Contains("DrainEveCommandsAsync", StringComparison.Ordinal) ||
|
|
daemonHost.Contains("PutEveCommandDrainStatusAsync", StringComparison.Ordinal) ||
|
|
daemonHost.Contains("GetEveCommandDrainStatusAsync", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve command acceptance regressed to drain/queue vocabulary; the daemon should accept observed typed command records.");
|
|
}
|
|
|
|
if (acceptanceStatus.Contains("public int PendingBeforeApply", StringComparison.Ordinal) &&
|
|
!acceptanceStatus.Contains("[IgnoreMember]", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve command acceptance exposes the legacy PendingBeforeApply schema shim as serialized command state.");
|
|
}
|
|
|
|
var pendingBeforeApplyUseSources = new Dictionary<string, string>
|
|
{
|
|
["Aetheria daemon host"] = daemonHost,
|
|
["Aetheria Eve command bridge"] = commandBridge,
|
|
["operations surface document"] = operationsSurfaceDocuments
|
|
};
|
|
var pendingBeforeApplyUses = pendingBeforeApplyUseSources
|
|
.Where(source => source.Value.Contains("PendingBeforeApply", StringComparison.Ordinal))
|
|
.Select(source => source.Key)
|
|
.ToArray();
|
|
if (pendingBeforeApplyUses.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Live Eve command acceptance code still uses the legacy PendingBeforeApply shim instead of ObservedBeforeAccept: " +
|
|
string.Join(", ", pendingBeforeApplyUses));
|
|
}
|
|
|
|
if (!operationsSurfaceDocuments.Contains("Verse Host Commands", StringComparison.Ordinal) ||
|
|
!operationsSurfaceDocuments.Contains("AppliedVerseHostCommands", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Operations surface no longer projects Verse-host Eve command counts.");
|
|
}
|
|
|
|
if (!commandBridge.Contains("report.AcceptedVerseHostCommands++", StringComparison.Ordinal) ||
|
|
!daemonHost.Contains("AppliedVerseHostCommands = report.AcceptedVerseHostCommands", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon command acceptance no longer publishes Verse-host Eve command totals.");
|
|
}
|
|
}
|
|
|
|
static void RequireTypedStatRecipeOperations(string root)
|
|
{
|
|
var statRecipesPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStatRecipes.cs");
|
|
var legacyApplierPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStatRecipeCommandApplier.cs");
|
|
var catalogStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs");
|
|
var testsPath = Path.Combine(root, "Assets", "Scripts", "Tests", "StatRecipeSurfaceCommandTests.cs");
|
|
|
|
if (!File.Exists(statRecipesPath))
|
|
throw new InvalidOperationException("Typed stat recipe operations are missing.");
|
|
if (File.Exists(legacyApplierPath))
|
|
throw new InvalidOperationException("Legacy stat recipe command applier still exists.");
|
|
if (!File.Exists(catalogStorePath) || !File.Exists(testsPath))
|
|
throw new InvalidOperationException("Cannot verify typed stat recipe operations; required files are missing.");
|
|
|
|
var statRecipes = File.ReadAllText(statRecipesPath);
|
|
var catalogStore = File.ReadAllText(catalogStorePath);
|
|
var tests = File.ReadAllText(testsPath);
|
|
|
|
var requiredTypedSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeStatRecipes",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState Refresh(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState SelectStat(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState AddStat(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState RemoveStat(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState SetStatName(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState SetBaseValue(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState SetConditionEnabled(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState ToggleCondition(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState CycleInfluenceOperation(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState SetInfluenceAmount(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState SetInfluenceCurve(",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState SetPreviewCondition("
|
|
};
|
|
var missingTypedSymbols = requiredTypedSymbols
|
|
.Where(symbol => !statRecipes.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTypedSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed stat recipe operations are incomplete: " +
|
|
string.Join(", ", missingTypedSymbols));
|
|
}
|
|
|
|
var forbiddenPublicStringlySymbols = new[]
|
|
{
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState Apply",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState ApplyCommand",
|
|
"public static class AetheriaRuntimeStatRecipeDaemonOperations",
|
|
"public static AetheriaRuntimeStatRecipeSurfaceState",
|
|
"IReadOnlyDictionary<string, string>? payload"
|
|
};
|
|
var publicStringlyHits = forbiddenPublicStringlySymbols
|
|
.Where(symbol =>
|
|
symbol == "public static AetheriaRuntimeStatRecipeSurfaceState"
|
|
? statRecipes.Contains("public static AetheriaRuntimeStatRecipeSurfaceState Apply(", StringComparison.Ordinal)
|
|
: statRecipes.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (publicStringlyHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Stat recipe client API still exposes stringly command/payload operations: " +
|
|
string.Join(", ", publicStringlyHits));
|
|
}
|
|
|
|
var forbiddenCatalogDecoderSymbols = new[]
|
|
{
|
|
"DrainCommandsStatRecipeCommand(",
|
|
"AetheriaRuntimeStatRecipes.ApplyCommand(",
|
|
"ReadPayloadDouble(",
|
|
"ReadPayloadBool("
|
|
};
|
|
var catalogDecoderHits = forbiddenCatalogDecoderSymbols
|
|
.Where(symbol => catalogStore.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (catalogDecoderHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Catalog store still replays stringly stat recipe command payloads: " +
|
|
string.Join(", ", catalogDecoderHits));
|
|
}
|
|
|
|
if (tests.Contains("AetheriaRuntimeStatRecipeDaemonOperations", StringComparison.Ordinal) ||
|
|
tests.Contains("AetheriaRuntimeStatRecipeCommands.", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Stat recipe tests still exercise string command constants instead of typed operations.");
|
|
}
|
|
}
|
|
|
|
static void RequireTypedDaemonCommandPayloads(string root)
|
|
{
|
|
var daemonDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonDocuments.cs");
|
|
var daemonClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationClient.cs");
|
|
var daemonRuntimeOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs");
|
|
var legacyDaemonApplierPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonCommandApplier.cs");
|
|
var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs");
|
|
var observerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonObserver.cs");
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var testsPath = Path.Combine(root, "Assets", "Scripts", "Tests", "DaemonRuntimeDocumentTests.cs");
|
|
var daemonSurfaceCommandsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSurfaceCommands.cs");
|
|
var starbridgeDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStarbridgeDocuments.cs");
|
|
|
|
if (File.Exists(legacyDaemonApplierPath))
|
|
throw new InvalidOperationException("Legacy daemon command applier still exists; daemon operations must own execution.");
|
|
|
|
var requiredFiles = new[] { daemonDocumentsPath, daemonClientPath, daemonRuntimeOperationsPath, stateNodePath, observerPath, actionGameManagerPath, testsPath, daemonSurfaceCommandsPath, starbridgeDocumentsPath };
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed daemon command payloads cannot be verified because required files are missing: " +
|
|
string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var daemonDocuments = File.ReadAllText(daemonDocumentsPath);
|
|
var daemonClient = File.ReadAllText(daemonClientPath);
|
|
var daemonOperationsSource = File.ReadAllText(daemonRuntimeOperationsPath);
|
|
var stateNode = File.ReadAllText(stateNodePath);
|
|
var observer = File.ReadAllText(observerPath);
|
|
var actionGameManager = File.ReadAllText(actionGameManagerPath);
|
|
var tests = File.ReadAllText(testsPath);
|
|
var daemonSurfaceCommands = File.ReadAllText(daemonSurfaceCommandsPath);
|
|
var starbridgeDocuments = File.ReadAllText(starbridgeDocumentsPath);
|
|
|
|
if (daemonOperationsSource.Contains("private static string BuildEntityKey(", StringComparison.Ordinal) ||
|
|
daemonOperationsSource.Contains("private static bool TryParseEntityKey(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon operations reintroduced local entity key parsing/building; use AetheriaRuntimeRunCheckpointCommit entity key helpers.");
|
|
}
|
|
|
|
if (starbridgeDocuments.Contains("private static string BuildEntityKey(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Starbridge documents reintroduced local entity key building; use AetheriaRuntimeRunCheckpointCommit entity key helpers.");
|
|
}
|
|
|
|
var requiredPayloadTypes = new[]
|
|
{
|
|
"public enum AetheriaRuntimeDaemonCommandKinds",
|
|
"public sealed class AetheriaRuntimeCargoTransferCommand",
|
|
"public sealed class AetheriaRuntimeTradePurchaseCommand",
|
|
"public sealed class AetheriaRuntimeLoadoutRestoreCommand",
|
|
"public sealed class AetheriaRuntimeEquipmentTransferCommand",
|
|
"public sealed class AetheriaRuntimeStoreItemCommand"
|
|
};
|
|
var missingPayloadTypes = requiredPayloadTypes
|
|
.Where(symbol => !daemonDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingPayloadTypes.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon command document is missing typed command payloads: " +
|
|
string.Join(", ", missingPayloadTypes));
|
|
}
|
|
|
|
if (!daemonDocuments.Contains("public sealed class AetheriaRuntimeLootPickupCommand", StringComparison.Ordinal) ||
|
|
!daemonDocuments.Contains("public AetheriaRuntimeLootPickupCommand LootPickup { get; set; }", StringComparison.Ordinal) ||
|
|
!daemonDocuments.Contains("Serialization tombstone", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The retired loot-pickup payload must remain only as an explicit serialization tombstone for persisted command compatibility.");
|
|
}
|
|
|
|
var forbiddenRetiredCommandOpinionUses = new[]
|
|
{
|
|
".LootPickup.",
|
|
"purchase.PurchaseKind",
|
|
"purchase.UnitPrice",
|
|
"purchase.TotalPrice",
|
|
"purchase.StationEntityKey",
|
|
"purchase.TargetEntityKey",
|
|
"purchase.CreatesDockedShip",
|
|
"command.TradePurchase.PurchaseKind",
|
|
"command.TradePurchase.UnitPrice",
|
|
"command.TradePurchase.TotalPrice",
|
|
"command.TradePurchase.StationEntityKey",
|
|
"command.TradePurchase.TargetEntityKey",
|
|
"command.TradePurchase.CreatesDockedShip"
|
|
};
|
|
var liveCommandImplementation = daemonClient + "\n" + daemonOperationsSource;
|
|
var survivingRetiredCommandOpinionUses = forbiddenRetiredCommandOpinionUses
|
|
.Where(symbol => liveCommandImplementation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingRetiredCommandOpinionUses.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Retired compatibility fields still influence live loot or trade command behavior: " +
|
|
string.Join(", ", survivingRetiredCommandOpinionUses));
|
|
}
|
|
|
|
if (daemonDocuments.Contains("public sealed class AetheriaRuntimeActionBarBindingCommand", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Action-bar input mappings are Unity-local and must not be daemon command payloads.");
|
|
}
|
|
|
|
var liveSources = new Dictionary<string, string>
|
|
{
|
|
["daemon documents"] = daemonDocuments,
|
|
["daemon operation client"] = daemonClient,
|
|
["daemon operations"] = daemonOperationsSource,
|
|
["state node"] = stateNode,
|
|
["Unity daemon observer"] = observer,
|
|
["ActionGameManager"] = actionGameManager,
|
|
["daemon surface commands"] = daemonSurfaceCommands
|
|
};
|
|
foreach (var source in liveSources)
|
|
{
|
|
var forbidden = new[]
|
|
{
|
|
"Dictionary<string, string> Payload",
|
|
".Payload[",
|
|
"ReadPayloadInt(",
|
|
"ReadPayloadDouble(",
|
|
"ReadPayloadBool(",
|
|
"Payload(command,"
|
|
}
|
|
.Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbidden.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{source.Key} still uses stringly daemon command payloads: " +
|
|
string.Join(", ", forbidden));
|
|
}
|
|
}
|
|
|
|
var forbiddenDaemonKindSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeDaemonCommandKinds",
|
|
"public const string SetTarget",
|
|
"Queue(\r\n string kind",
|
|
"Create(\r\n string kind"
|
|
};
|
|
var daemonKindSources = new Dictionary<string, string>
|
|
{
|
|
["daemon documents"] = daemonDocuments,
|
|
["daemon operation client"] = daemonClient,
|
|
["Unity daemon observer"] = observer
|
|
};
|
|
foreach (var source in daemonKindSources)
|
|
{
|
|
var forbidden = forbiddenDaemonKindSymbols
|
|
.Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbidden.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{source.Key} still exposes daemon command kind as strings: " +
|
|
string.Join(", ", forbidden));
|
|
}
|
|
}
|
|
|
|
if (!daemonDocuments.Contains("public AetheriaRuntimeDaemonCommandKinds Kind { get; set; }", StringComparison.Ordinal) ||
|
|
!daemonDocuments.Contains("public AetheriaRuntimeDaemonCommandKinds Kind { get; }", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon command document/envelope must expose typed enum command kinds.");
|
|
}
|
|
|
|
var requiredClientSymbols = new[]
|
|
{
|
|
"public const string DefaultClientId = \"aetheria-daemon-client\"",
|
|
"ClientId = string.IsNullOrWhiteSpace(clientId) ? DefaultClientId : clientId",
|
|
"private AetheriaRuntimeDaemonCommandEnvelope Send(AetheriaRuntimeDaemonCommandDocument command)",
|
|
"Func<AetheriaRuntimeDaemonOperationClient, AetheriaRuntimeDaemonFrameDocument?, AetheriaRuntimeDaemonCommandEnvelope> submit",
|
|
"return Submit((client, frame) => client.SetTarget(frame, targetEntityKey));",
|
|
"return Send((client, frame) => client.TransferCargoItem(",
|
|
"command.CargoTransfer.OriginEntityKey",
|
|
"ReadPayloadString(request, \"itemKey\", \"\")",
|
|
"command.LoadoutRestore.TemplateName",
|
|
"command.EquipmentTransfer.SourceKind",
|
|
"command.StoreItem.SourceEquipmentIndex"
|
|
};
|
|
var daemonOperationsClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationsClient.cs");
|
|
var daemonOperationsClient = File.Exists(daemonOperationsClientPath)
|
|
? File.ReadAllText(daemonOperationsClientPath)
|
|
: throw new InvalidOperationException("Shared daemon operations client is missing from the runtime package.");
|
|
var requiredDaemonOperationsFacadeSymbols = new[]
|
|
{
|
|
"using GameCult.Mesh;",
|
|
"public AetheriaRuntimeDaemonCommandEnvelope SetTarget(string targetEntityKey)",
|
|
"public AetheriaRuntimeDaemonCommandEnvelope SetMoveVector(",
|
|
"public CultMeshOperationReceipt TransferCargoItem(",
|
|
"private AetheriaRuntimeDaemonCommandEnvelope Submit(",
|
|
"private CultMeshOperationReceipt Send("
|
|
};
|
|
var missingDaemonOperationsFacadeSymbols = requiredDaemonOperationsFacadeSymbols
|
|
.Where(symbol => !daemonOperationsClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonOperationsFacadeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime operations facade no longer exposes shared CultMesh receipts: " +
|
|
string.Join(", ", missingDaemonOperationsFacadeSymbols));
|
|
}
|
|
|
|
var forbiddenPublicEnvelopeSymbols = new[]
|
|
{
|
|
"public AetheriaRuntimeDaemonCommandEnvelope ClearTarget(",
|
|
"public AetheriaRuntimeDaemonCommandEnvelope TransferCargoItem(",
|
|
"public AetheriaRuntimeDaemonCommandEnvelope SetLookDirection(",
|
|
"public AetheriaRuntimeDaemonCommandEnvelope SetTractorPower("
|
|
};
|
|
var publicEnvelopeHits = forbiddenPublicEnvelopeSymbols
|
|
.Where(symbol => daemonOperationsClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (publicEnvelopeHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime operations facade exposes command envelopes beyond the typed identity-sensitive command path: " +
|
|
string.Join(", ", publicEnvelopeHits));
|
|
}
|
|
|
|
var legacyUnityOperationsPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonOperations.cs");
|
|
if (File.Exists(legacyUnityOperationsPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria daemon operation facade still lives under Unity gameplay; move shared client operations into the runtime package.");
|
|
}
|
|
|
|
var clientAndObserver = daemonClient + "\n" + observer + "\n" + stateNode + "\n" + daemonOperationsClient;
|
|
var missingClientSymbols = requiredClientSymbols
|
|
.Where(symbol => !clientAndObserver.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingClientSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon command producers are not filling typed payloads: " +
|
|
string.Join(", ", missingClientSymbols));
|
|
}
|
|
|
|
if (clientAndObserver.Contains("ActionBarBinding", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon command producers still expose Unity-local action-bar input bindings.");
|
|
}
|
|
|
|
var forbiddenQueueSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeDaemonCommandLog",
|
|
"internal static class AetheriaRuntimeDaemonCommandLog",
|
|
"public AetheriaRuntimeDaemonCommandEnvelope Queue(",
|
|
"public static AetheriaRuntimeDaemonCommandEnvelope QueueCommand(",
|
|
"AetheriaRuntimeDaemonCommandLog.QueueCommand(",
|
|
"AetheriaRuntimeDaemonCommandLog.",
|
|
"AetheriaRuntimeDaemonCommandInbox",
|
|
"ReadPending(",
|
|
".daemon.pending",
|
|
"stateFilePath + \".daemon.commands\"",
|
|
"_operationClient.Queue("
|
|
};
|
|
var daemonQueueSources = new Dictionary<string, string>
|
|
{
|
|
["daemon operation client"] = daemonClient,
|
|
["state node"] = stateNode,
|
|
["Unity daemon observer"] = observer,
|
|
["ActionGameManager"] = actionGameManager,
|
|
["daemon command tests"] = tests
|
|
};
|
|
var survivingQueueSymbols = daemonQueueSources
|
|
.SelectMany(source => forbiddenQueueSymbols
|
|
.Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{source.Key}: {symbol}"))
|
|
.ToArray();
|
|
|
|
if (survivingQueueSymbols.Length > 0 ||
|
|
observer.Contains("_operationClient.Queue(", StringComparison.Ordinal) ||
|
|
daemonClient.Contains("AetheriaRuntimeDaemonCommandLog.", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity-facing daemon operation clients still expose queue semantics instead of sending typed daemon operations: " +
|
|
string.Join(", ", survivingQueueSymbols));
|
|
}
|
|
|
|
var forbiddenUnityDocumentSubmitSymbols = new[]
|
|
{
|
|
"SendOperation(AetheriaRuntimeDaemonCommandKinds",
|
|
"Action<AetheriaRuntimeDaemonCommandDocument>",
|
|
"_operationClient.Create(",
|
|
"_operationClient.TrySend(command",
|
|
"command.TargetEntityKey =",
|
|
"command.ActionBarBinding.",
|
|
"command.CargoTransfer.",
|
|
"command.TradePurchase.",
|
|
"command.LootPickup.",
|
|
"command.LoadoutRestore.",
|
|
"command.EquipmentTransfer.",
|
|
"command.StoreItem."
|
|
};
|
|
var unityDocumentSubmitHits = new Dictionary<string, string>
|
|
{
|
|
["Unity daemon observer"] = observer
|
|
}
|
|
.SelectMany(source => forbiddenUnityDocumentSubmitSymbols
|
|
.Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{source.Key}: {symbol}"))
|
|
.ToArray();
|
|
if (unityDocumentSubmitHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity daemon operation controllers still fill command documents instead of delegating to typed runtime operations: " +
|
|
string.Join(", ", unityDocumentSubmitHits));
|
|
}
|
|
|
|
var forbiddenPublicDocumentSubmitSymbols = new[]
|
|
{
|
|
"public AetheriaRuntimeDaemonCommandEnvelope Send(",
|
|
"public bool TrySend(\r\n AetheriaRuntimeDaemonCommandKinds",
|
|
"public bool TrySend(\r\n AetheriaRuntimeDaemonCommandDocument",
|
|
"public AetheriaRuntimeDaemonCommandDocument Create("
|
|
};
|
|
var publicDocumentSubmitHits = forbiddenPublicDocumentSubmitSymbols
|
|
.Where(symbol => daemonClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (publicDocumentSubmitHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon operation client still exposes public document-shaped submission APIs instead of typed operations: " +
|
|
string.Join(", ", publicDocumentSubmitHits));
|
|
}
|
|
|
|
var forbiddenClientDocumentBuilderSymbols = new[]
|
|
{
|
|
"TrySendCommandKind(",
|
|
"Action<AetheriaRuntimeDaemonCommandDocument>",
|
|
"configure?.Invoke(command)",
|
|
"Send(AetheriaRuntimeDaemonCommandKinds"
|
|
};
|
|
var clientDocumentBuilderHits = forbiddenClientDocumentBuilderSymbols
|
|
.Where(symbol => daemonClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (clientDocumentBuilderHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon operation client still routes typed operations through mutable document builder callbacks: " +
|
|
string.Join(", ", clientDocumentBuilderHits));
|
|
}
|
|
|
|
var forbiddenSharedDaemonClientDefaults = new[]
|
|
{
|
|
"string clientId = \"unity-observer\"",
|
|
"? \"unity-observer\" :",
|
|
"? \"unity-uitoolkit\" : request.ClientId"
|
|
};
|
|
var sharedDaemonDefaultSources = new Dictionary<string, string>
|
|
{
|
|
["daemon operation client"] = daemonClient,
|
|
["daemon surface commands"] = daemonSurfaceCommands
|
|
};
|
|
var sharedDaemonDefaultHits = sharedDaemonDefaultSources
|
|
.SelectMany(source => forbiddenSharedDaemonClientDefaults
|
|
.Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{source.Key}: {symbol}"))
|
|
.ToArray();
|
|
if (sharedDaemonDefaultHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared daemon command clients still use Unity-specific fallback client ids: " +
|
|
string.Join(", ", sharedDaemonDefaultHits));
|
|
}
|
|
|
|
if (tests.Contains("AetheriaRuntimeDaemonCommandLog.", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon command tests still reach through the old command log instead of the typed command client/inbox.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("local mutation suppressed", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still treats failed daemon submission as a local-mutation fallback.");
|
|
}
|
|
|
|
var failedSubmitSuccessHits = System.Text.RegularExpressions.Regex
|
|
.Matches(
|
|
actionGameManager,
|
|
"Failed to send Aetheria daemon[\\s\\S]*?return true;",
|
|
System.Text.RegularExpressions.RegexOptions.Multiline)
|
|
.Cast<System.Text.RegularExpressions.Match>()
|
|
.Where(match => !match.Value.Contains("return false;", StringComparison.Ordinal))
|
|
.Select(match => match.Value.Split('\n').FirstOrDefault()?.Trim() ?? "daemon submit catch")
|
|
.Take(5)
|
|
.ToArray();
|
|
if (failedSubmitSuccessHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager reports failed daemon submissions as successful commands: " +
|
|
string.Join("; ", failedSubmitSuccessHits));
|
|
}
|
|
}
|
|
|
|
static void RequireUnityPublicRequestVocabulary(string root)
|
|
{
|
|
var checkedRoots = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI")
|
|
};
|
|
|
|
var commitPattern = new System.Text.RegularExpressions.Regex(
|
|
"\\bCommit[A-Z][A-Za-z0-9_]*\\b",
|
|
System.Text.RegularExpressions.RegexOptions.Compiled);
|
|
var hits = checkedRoots
|
|
.Where(Directory.Exists)
|
|
.SelectMany(path => Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories))
|
|
.Where(path => !path.Contains(Path.Combine("ServerShared", "NIH"), StringComparison.Ordinal))
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.SelectMany(line => commitPattern.Matches(line.Line)
|
|
.Cast<System.Text.RegularExpressions.Match>()
|
|
.Select(match => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {match.Value}"))
|
|
.Take(20)
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity-facing Aetheria APIs must say Request/Submit, not Commit; Unity is an input provider, not state authority: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireAuthoritySmokeUsesManagedPointers(string root)
|
|
{
|
|
var authoritySmokePath = Path.Combine(root, "Aetheria.State.AuthoritySmoke", "Program.cs");
|
|
if (!File.Exists(authoritySmokePath))
|
|
{
|
|
throw new InvalidOperationException("Authority smoke source is missing.");
|
|
}
|
|
|
|
var authoritySmoke = File.ReadAllText(authoritySmokePath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"writer.MutableDocument<AetheriaRuntimeVerseAuthorityPolicyDocument>(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy)",
|
|
"writer.MutableDocument<AetheriaRuntimeStarbridgeScenarioDocument>(AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest)",
|
|
"writer.MutableDocument<AetheriaRuntimeStarbridgeSessionDocument>(AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest)",
|
|
"ravenNode.MutableDocument<AetheriaRuntimeVerseAuthorityPolicyDocument>(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy)",
|
|
"commanderNode.MutableDocument<AetheriaRuntimeVerseAuthorityPolicyDocument>(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy)",
|
|
"node.MutableDocument<AetheriaRuntimeVerseAuthorityPolicyDocument>(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy)",
|
|
"node.MutableDocument<AetheriaRuntimeDaemonFrameDocument>(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest)",
|
|
".ReplaceAsync(policy)",
|
|
".ReplaceAsync(scenario)",
|
|
".ReplaceAsync(session)",
|
|
".ReadAsync().ConfigureAwait(false)"
|
|
};
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !authoritySmoke.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Authority smoke no longer exercises managed Verse state pointers: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"PutStarbridgeScenarioAsync(",
|
|
"PutStarbridgeSessionAsync(",
|
|
"PutVerseAuthorityPolicyAsync(",
|
|
"GetVerseAuthorityPolicyAsync(",
|
|
"GetDaemonFrameAsync(",
|
|
"writer.StarbridgeScenario()",
|
|
"writer.StarbridgeSession()",
|
|
"ravenNode.VerseAuthorityPolicy()",
|
|
"commanderNode.VerseAuthorityPolicy()",
|
|
"node.VerseAuthorityPolicy()",
|
|
"node.LatestFrame()"
|
|
};
|
|
var survivingSymbols = forbiddenSymbols
|
|
.Where(symbol => authoritySmoke.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Authority smoke still teaches compatibility helper access instead of managed Verse pointers: " +
|
|
string.Join(", ", survivingSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireAetheriaStateNodeUsesManagedPointers(string root)
|
|
{
|
|
var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs");
|
|
if (!File.Exists(stateNodePath))
|
|
{
|
|
throw new InvalidOperationException("AetheriaStateNode source is missing.");
|
|
}
|
|
|
|
var stateNode = File.ReadAllText(stateNodePath);
|
|
var requiredSymbols = new[]
|
|
{
|
|
"public static CultRecordKey WorldKey { get; }",
|
|
"public static CultRecordKey MigrationLedgerKey { get; }",
|
|
"public static CultRecordKey LegacyCatalogQuarantineKey { get; }",
|
|
"public CultMeshMutableStatePointer<TDocument> MutableDocument<TDocument>(CultRecordKey key)",
|
|
"public IReadOnlyList<TDocument> Documents<TDocument>()",
|
|
"private CultMeshMutableStatePointer<T> MutableDocumentPointer<T>(CultRecordKey key)",
|
|
"CultMesh.MutableStatePointer("
|
|
};
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !stateNode.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaStateNode no longer exposes managed typed state pointers: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"public Task<CultRecordHandle<AetheriaWorldState>> PutWorldAsync(",
|
|
"public Task<AetheriaWorldState?> GetWorldAsync(",
|
|
"public Task<CultRecordHandle<AetheriaMigrationLedger>> PutMigrationLedgerAsync(",
|
|
"public Task<AetheriaMigrationLedger?> GetMigrationLedgerAsync(",
|
|
"public Task<CultRecordHandle<AetheriaItemDefinition>> PutItemDefinitionAsync(",
|
|
"public Task<AetheriaItemDefinition?> GetItemDefinitionAsync(",
|
|
"public Task<AetheriaItemDefinition?> GetItemDefinitionByLegacyIdAsync(",
|
|
"public Task<CultRecordHandle<AetheriaCorporation>> PutCorporationAsync(",
|
|
"public Task<AetheriaCorporation?> GetCorporationAsync(",
|
|
"public Task<CultRecordHandle<AetheriaNameFile>> PutNameFileAsync(",
|
|
"public Task<AetheriaNameFile?> GetNameFileAsync(",
|
|
"public Task<CultRecordHandle<AetheriaTradeValuePolicy>> PutTradeValuePolicyAsync(",
|
|
"public Task<AetheriaTradeValuePolicy?> GetTradeValuePolicyAsync(",
|
|
"public Task<CultRecordHandle<EveSurfaceState>> PutCatalogSurfaceAsync(",
|
|
"public Task<EveSurfaceState?> GetCatalogSurfaceAsync(",
|
|
"public Task<CultRecordHandle<EveProviderAdvertisementState>> PutProviderAdvertisementAsync(",
|
|
"public Task<EveProviderAdvertisementState?> GetProviderAdvertisementAsync(",
|
|
"public Task<CultRecordHandle<AetheriaRuntimeDaemonProviderAdvertisementDocument>> PutDaemonProviderAdvertisementAsync(",
|
|
"public Task<AetheriaRuntimeDaemonProviderAdvertisementDocument?> GetDaemonProviderAdvertisementAsync(",
|
|
"public Task<CultRecordHandle<AetheriaRuntimeVerseAuthorityPolicyDocument>> PutVerseAuthorityPolicyAsync(",
|
|
"public Task<AetheriaRuntimeVerseAuthorityPolicyDocument?> GetVerseAuthorityPolicyAsync(",
|
|
"public Task<CultRecordHandle<AetheriaRuntimeDaemonFrameDocument>> PutDaemonFrameAsync(",
|
|
"public Task<AetheriaRuntimeDaemonFrameDocument?> GetDaemonFrameAsync(",
|
|
"public Task<CultRecordHandle<AetheriaRuntimeDaemonSoaViewDocument>> PutDaemonSoaViewAsync(",
|
|
"public Task<AetheriaRuntimeDaemonSoaViewDocument?> GetDaemonSoaViewAsync(",
|
|
"public Task<CultRecordHandle<AetheriaRuntimeStarbridgeScenarioDocument>> PutStarbridgeScenarioAsync(",
|
|
"public Task<AetheriaRuntimeStarbridgeScenarioDocument?> GetStarbridgeScenarioAsync(",
|
|
"public Task<CultRecordHandle<EveSurfaceState>> PutDaemonGameSurfaceAsync(",
|
|
"public Task<EveSurfaceState?> GetDaemonGameSurfaceAsync(",
|
|
"public Task<CultRecordHandle<AetheriaRuntimeSession>> PutRuntimeSessionAsync(",
|
|
"public Task<AetheriaRuntimeSession?> GetRuntimeSessionAsync(",
|
|
"public Task<CultRecordHandle<AetheriaPlayerSettings>> PutPlayerSettingsAsync(",
|
|
"public Task<AetheriaPlayerSettings?> GetPlayerSettingsAsync(",
|
|
"public Task<CultRecordHandle<AetheriaRunState>> PutRunStateAsync(",
|
|
"public Task<AetheriaRunState?> GetRunStateAsync(",
|
|
"public Task<CultRecordHandle<AetheriaEntitySnapshot>> PutEntitySnapshotAsync(",
|
|
"public Task<AetheriaEntitySnapshot?> GetEntitySnapshotAsync(",
|
|
"public Task<CultRecordHandle<AetheriaVerseHostSettings>> PutVerseHostSettingsAsync(",
|
|
"public Task<AetheriaVerseHostSettings?> GetVerseHostSettingsAsync(",
|
|
"public CultMeshMutableStatePointer<AetheriaWorldState> World()",
|
|
"public CultMeshMutableStatePointer<AetheriaMigrationLedger> MigrationLedger()",
|
|
"public CultMeshMutableStatePointer<AetheriaLegacyCatalogQuarantine> LegacyCatalogQuarantine()",
|
|
"public CultMeshMutableStatePointer<AetheriaItemDefinition> ItemDefinition(",
|
|
"public CultMeshMutableStatePointer<AetheriaItemDefinition> ItemDefinitionByLegacyId(",
|
|
"public CultMeshMutableStatePointer<AetheriaCorporation> Corporation(",
|
|
"public CultMeshMutableStatePointer<AetheriaCorporation> CorporationByLegacyId(",
|
|
"public CultMeshMutableStatePointer<AetheriaNameFile> NameFile(",
|
|
"public CultMeshMutableStatePointer<AetheriaNameFile> NameFileByLegacyId(",
|
|
"public CultMeshMutableStatePointer<AetheriaLoadoutTemplate> LoadoutTemplate(",
|
|
"public CultMeshMutableStatePointer<AetheriaRunState> RunState(",
|
|
"public CultMeshMutableStatePointer<AetheriaZoneState> ZoneState(",
|
|
"public CultMeshMutableStatePointer<AetheriaEntitySnapshot> EntitySnapshot(",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeDaemonProviderAdvertisementDocument> ProviderAdvertisement()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeDaemonHealthDocument> Health()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeDaemonCommandBoundaryDocument> CommandBoundary()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeVerseAuthorityPolicyDocument> VerseAuthorityPolicy()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeDaemonFrameDocument> LatestFrame()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeDaemonSoaViewDocument> LatestSoaView()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeStarbridgeScenarioDocument> StarbridgeScenario()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeStarbridgeSessionDocument> StarbridgeSession()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeStarbridgeSessionSummaryDocument> StarbridgeSessionSummary()",
|
|
"public CultMeshMutableStatePointer<EveSurfaceState> DaemonGameSurface()",
|
|
"public CultMeshMutableStatePointer<EveSurfaceState> DaemonGameTuiSurface()",
|
|
"public CultMeshMutableStatePointer<EveSurfaceState> DaemonEditorSurface()",
|
|
"public CultMeshMutableStatePointer<EveSurfaceState> DaemonEditorTuiSurface()",
|
|
"ReadObservedDaemonCommands(",
|
|
"ReadCommittedCommandFacts(",
|
|
"ReadAuthorityLeases(",
|
|
"ReadObservedEveCommands("
|
|
};
|
|
var survivingSymbols = forbiddenSymbols
|
|
.Where(symbol => stateNode.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaStateNode still exposes compatibility document Get/Put helpers instead of managed typed pointers: " +
|
|
string.Join(", ", survivingSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireDaemonVersePublication(string root)
|
|
{
|
|
var daemonDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonDocuments.cs");
|
|
var daemonTickRunnerPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonTickRunner.cs");
|
|
var daemonSoaDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSoaDocuments.cs");
|
|
var daemonSoaFramePublisherPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSoaFramePublisher.cs");
|
|
var eveSoaProjectionPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveEntitySoaProjection.cs");
|
|
var assetDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeAssetDocuments.cs");
|
|
var runtimeAssetsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeAssets.cs");
|
|
var runtimeGameDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeGameDocuments.cs");
|
|
var daemonRenderAssetCatalogPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonRenderAssetCatalog.cs");
|
|
var daemonIndirectRendererPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonIndirectRenderer.cs");
|
|
var daemonStateRefsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonStateRefs.cs");
|
|
var daemonGameSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonGameSurfaceBuilder.cs");
|
|
var daemonEditorSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonEditorSurfaceBuilder.cs");
|
|
var statRecipeSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStatRecipeSurfaceBuilder.cs");
|
|
var tradeValuePolicySurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeTradeValuePolicySurfaceBuilder.cs");
|
|
var runtimeCatalogStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs");
|
|
var daemonSurfaceCommandCatalogPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSurfaceCommandCatalog.cs");
|
|
var committedFactImporterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCommittedFactImporter.cs");
|
|
var daemonHostProjectPath = Path.Combine(root, "Aetheria.State.Daemon", "Aetheria.State.Daemon.csproj");
|
|
var daemonHostProgramPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs");
|
|
var daemonZoneGeneratorPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaDaemonZoneGenerator.cs");
|
|
var daemonLoadoutGeneratorPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaDaemonLoadoutGenerator.cs");
|
|
var documentRegistryPath = Path.Combine(root, "Aetheria.State", "AetheriaDocumentRegistry.cs");
|
|
var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs");
|
|
var runtimeSurfaceDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSurfaceDocuments.cs");
|
|
var providerAdvertisementPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs");
|
|
var legacyProviderAdvertisementPath = Path.Combine(root, "Aetheria.State", "Documents", "EveProviderAdvertisementState.cs");
|
|
var documentStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCultCacheDocumentStore.cs");
|
|
var boundaryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateBoundary.cs");
|
|
var testsPath = Path.Combine(root, "Assets", "Scripts", "Tests", "DaemonRuntimeDocumentTests.cs");
|
|
var smokePath = Path.Combine(root, "Aetheria.State.Smoke", "Program.cs");
|
|
var notePath = Path.Combine(root, "Aetheria.State", "docs", "verse-daemon-shape.md");
|
|
|
|
var requiredFiles = new[]
|
|
{
|
|
daemonDocumentsPath,
|
|
daemonTickRunnerPath,
|
|
daemonSoaDocumentsPath,
|
|
daemonSoaFramePublisherPath,
|
|
eveSoaProjectionPath,
|
|
assetDocumentsPath,
|
|
runtimeAssetsPath,
|
|
runtimeGameDocumentsPath,
|
|
daemonRenderAssetCatalogPath,
|
|
daemonIndirectRendererPath,
|
|
daemonStateRefsPath,
|
|
daemonGameSurfaceBuilderPath,
|
|
daemonEditorSurfaceBuilderPath,
|
|
statRecipeSurfaceBuilderPath,
|
|
tradeValuePolicySurfaceBuilderPath,
|
|
runtimeCatalogStorePath,
|
|
daemonSurfaceCommandCatalogPath,
|
|
committedFactImporterPath,
|
|
daemonHostProjectPath,
|
|
daemonHostProgramPath,
|
|
daemonZoneGeneratorPath,
|
|
daemonLoadoutGeneratorPath,
|
|
documentRegistryPath,
|
|
stateNodePath,
|
|
runtimeSurfaceDocumentsPath,
|
|
providerAdvertisementPath,
|
|
documentStorePath,
|
|
boundaryPath,
|
|
testsPath,
|
|
smokePath,
|
|
notePath
|
|
};
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria daemon Verse publication cannot be verified because required files are missing: " +
|
|
string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var daemonDocuments = File.ReadAllText(daemonDocumentsPath);
|
|
var daemonTickRunner = File.ReadAllText(daemonTickRunnerPath);
|
|
var daemonSoaDocuments = File.ReadAllText(daemonSoaDocumentsPath);
|
|
var daemonSoaFramePublisher = File.ReadAllText(daemonSoaFramePublisherPath);
|
|
var eveSoaProjection = File.ReadAllText(eveSoaProjectionPath);
|
|
var assetDocuments = File.ReadAllText(assetDocumentsPath);
|
|
var runtimeAssets = File.ReadAllText(runtimeAssetsPath);
|
|
var runtimeGameDocuments = File.ReadAllText(runtimeGameDocumentsPath);
|
|
var daemonRenderAssetCatalog = File.ReadAllText(daemonRenderAssetCatalogPath);
|
|
var daemonIndirectRenderer = File.ReadAllText(daemonIndirectRendererPath);
|
|
var daemonStateRefs = File.ReadAllText(daemonStateRefsPath);
|
|
var daemonGameSurfaceBuilder = File.ReadAllText(daemonGameSurfaceBuilderPath);
|
|
var daemonEditorSurfaceBuilder = File.ReadAllText(daemonEditorSurfaceBuilderPath);
|
|
var statRecipeSurfaceBuilder = File.ReadAllText(statRecipeSurfaceBuilderPath);
|
|
var tradeValuePolicySurfaceBuilder = File.ReadAllText(tradeValuePolicySurfaceBuilderPath);
|
|
var runtimeCatalogStore = File.ReadAllText(runtimeCatalogStorePath);
|
|
var daemonSurfaceCommandCatalog = File.ReadAllText(daemonSurfaceCommandCatalogPath);
|
|
var committedFactImporter = File.ReadAllText(committedFactImporterPath);
|
|
var daemonHostProject = File.ReadAllText(daemonHostProjectPath);
|
|
var daemonHostProgram = File.ReadAllText(daemonHostProgramPath);
|
|
var daemonZoneGenerator = File.ReadAllText(daemonZoneGeneratorPath);
|
|
var daemonLoadoutGenerator = File.ReadAllText(daemonLoadoutGeneratorPath);
|
|
var documentRegistry = File.ReadAllText(documentRegistryPath);
|
|
var stateNode = File.ReadAllText(stateNodePath);
|
|
var runtimeSurfaceDocuments = File.ReadAllText(runtimeSurfaceDocumentsPath);
|
|
var providerAdvertisement = File.ReadAllText(providerAdvertisementPath);
|
|
var documentStore = File.ReadAllText(documentStorePath);
|
|
var boundary = File.ReadAllText(boundaryPath);
|
|
var tests = File.ReadAllText(testsPath);
|
|
var smoke = File.ReadAllText(smokePath);
|
|
var note = File.ReadAllText(notePath);
|
|
|
|
var requiredDocumentSymbols = new[]
|
|
{
|
|
"public const string ProviderAdvertisement = \"gamecult.aetheria.daemon_provider_advertisement.v1\"",
|
|
"public const string Health = \"gamecult.aetheria.daemon_health.v1\"",
|
|
"public const string CommandBoundary = \"gamecult.aetheria.daemon_command_boundary.v1\"",
|
|
"public const string GameSurface = \"gamecult.aetheria.daemon_game_surface.v1\"",
|
|
"public const string EditorSurface = \"gamecult.aetheria.daemon_editor_surface.v1\"",
|
|
"public sealed class AetheriaRuntimeDaemonProviderAdvertisementDocument",
|
|
"public sealed class AetheriaRuntimeDaemonHealthDocument",
|
|
"public sealed class AetheriaRuntimeDaemonCommandBoundaryDocument",
|
|
"public sealed class AetheriaRuntimeDaemonCommandBoundaryEntry",
|
|
"AetheriaRuntimeDaemonCommandKinds Kind",
|
|
"AetheriaRuntimeCargoTransferCommand",
|
|
"[CultDocument(\"gamecult.aetheria.daemon_provider_advertisement\", \"gamecult.aetheria.daemon_provider_advertisement.v1\")]",
|
|
"[CultDocument(\"gamecult.aetheria.daemon_health\", \"gamecult.aetheria.daemon_health.v1\")]",
|
|
"[CultDocument(\"gamecult.aetheria.daemon_command_boundary\", \"gamecult.aetheria.daemon_command_boundary.v1\")]",
|
|
"[CultDocument(\"gamecult.aetheria.daemon_frame\", \"gamecult.aetheria.daemon_frame.v1\")]",
|
|
"[CultDocument(\"gamecult.aetheria.daemon_command\", \"gamecult.aetheria.daemon_command.v1\")]",
|
|
"FrameRecordRef",
|
|
"SoaViewRecordRef",
|
|
"EveGuiSurfaceRecordRef",
|
|
"EveTuiSurfaceRecordRef",
|
|
"EditorGuiSurfaceRecordRef",
|
|
"EditorTuiSurfaceRecordRef"
|
|
};
|
|
var missingDocumentSymbols = requiredDocumentSymbols
|
|
.Where(symbol => !daemonDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Verse publication documents are incomplete: " +
|
|
string.Join(", ", missingDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenDaemonProviderDocumentSymbols = new[]
|
|
{
|
|
"StateWitnessPath",
|
|
"FrameWitnessPath",
|
|
"SoaWitnessPath",
|
|
"HealthWitnessPath",
|
|
"CommandBoundaryWitnessPath",
|
|
"EveGuiSurfaceWitnessPath",
|
|
"EveTuiSurfaceWitnessPath",
|
|
"EditorGuiSurfaceWitnessPath",
|
|
"EditorTuiSurfaceWitnessPath",
|
|
"AssetManifestWitnessPath"
|
|
};
|
|
var daemonProviderDocumentHits = forbiddenDaemonProviderDocumentSymbols
|
|
.Where(symbol => daemonDocuments.Contains(symbol, StringComparison.Ordinal) ||
|
|
daemonEditorSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (daemonProviderDocumentHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon provider advertisement still exposes witness-path vocabulary instead of managed record refs: " +
|
|
string.Join(", ", daemonProviderDocumentHits));
|
|
}
|
|
|
|
if (!daemonSoaDocuments.Contains("[CultDocument(\"gamecult.aetheria.daemon_soa_view\", \"gamecult.aetheria.daemon_soa_view.v1\")]", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("Daemon SoA view is no longer a registered CultCache/CultNet document.");
|
|
}
|
|
|
|
var requiredSoaFramePublisherSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaRuntimeDaemonSoaFramePublisher : IDisposable",
|
|
"BuildCurrentZoneEntities(",
|
|
"new CultMeshFrameBodyPublisher(",
|
|
"new CultMeshBodyPublicationHandle(BodyId, publication.ProducerEpoch, publication.Sequence)",
|
|
".Validate(publication)",
|
|
"view.Buffers[0].BufferId, publication.BodyId",
|
|
"view.Columns.Any(column => !string.Equals(column.BufferId, publication.BodyId",
|
|
"ObserverWritable = false",
|
|
"AetheriaRuntimeDaemonSoaColumnKinds.EntityIndex",
|
|
"AetheriaRuntimeDaemonSoaColumnKinds.Position",
|
|
"AetheriaRuntimeDaemonSoaColumnKinds.Velocity",
|
|
"AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyRadius",
|
|
"AetheriaRuntimeDaemonSoaColumnKinds.RenderVisibility",
|
|
"AetheriaRuntimeDaemonSoaColumnKinds.RenderGroupId"
|
|
};
|
|
var missingSoaFramePublisherSymbols = requiredSoaFramePublisherSymbols
|
|
.Where(symbol => !daemonSoaFramePublisher.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSoaFramePublisherSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon SoA frame publisher no longer turns authoritative frames into observer-readable direct-memory slabs: " +
|
|
string.Join(", ", missingSoaFramePublisherSymbols));
|
|
}
|
|
var forbiddenSoaPublisherSymbols = new[]
|
|
{
|
|
"MemoryMappedFile",
|
|
"RetainedMappedBuffer",
|
|
"CreateLocation(",
|
|
"CultMeshNetworkBodyStore",
|
|
"PublishNetwork"
|
|
};
|
|
var forbiddenSoaPublisherHits = forbiddenSoaPublisherSymbols
|
|
.Where(symbol => daemonSoaFramePublisher.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenSoaPublisherHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria still owns SoA mapping location or retention instead of CultMesh: " +
|
|
string.Join(", ", forbiddenSoaPublisherHits));
|
|
}
|
|
var forbiddenProjectionSymbols = new[]
|
|
{
|
|
"CultMeshBodyDescriptor",
|
|
"TransportKind",
|
|
"CapabilityToken",
|
|
"Backend =",
|
|
"Location =",
|
|
"StableEpoch("
|
|
};
|
|
var forbiddenProjectionHits = forbiddenProjectionSymbols
|
|
.Where(symbol => eveSoaProjection.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenProjectionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve SoA layout projection still owns transport material or producer epoch: " +
|
|
string.Join(", ", forbiddenProjectionHits));
|
|
}
|
|
const string bodyPublicationWrite =
|
|
"CultMeshBodyPublicationDocument.CreateLatestRecordKey(publication.Body.BodyId)";
|
|
if (!daemonHostProgram.Contains(bodyPublicationWrite, StringComparison.Ordinal) ||
|
|
daemonHostProgram.IndexOf(bodyPublicationWrite, StringComparison.Ordinal) >
|
|
daemonHostProgram.IndexOf("MutableDocument<EveEntitySoaViewDocument>(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("CultMesh body publication must precede the Eve entity SoA layout record.");
|
|
}
|
|
if (!smoke.Contains("new CultMeshBodyPublicationHandle(", StringComparison.Ordinal) ||
|
|
!smoke.Contains("published.View.ProducerEpoch", StringComparison.Ordinal) ||
|
|
!smoke.Contains("published.View.Sequence", StringComparison.Ordinal) ||
|
|
!smoke.Contains("CreateLatestRecordKey", StringComparison.Ordinal) ||
|
|
!smoke.Contains("bodyHandle.Validate(body)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The Aetheria pipeline smoke must prove view N resolves its generation record while latest points at N+1.");
|
|
}
|
|
if (File.Exists(legacyProviderAdvertisementPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The deleted Aetheria-specific EveProviderAdvertisementState writer has returned: " +
|
|
Path.GetRelativePath(root, legacyProviderAdvertisementPath));
|
|
}
|
|
|
|
if (assetDocuments.Contains("UnityAddressable", StringComparison.Ordinal) ||
|
|
assetDocuments.Contains("unity_addressable", StringComparison.Ordinal) ||
|
|
assetDocuments.Contains("public const string Resources", StringComparison.Ordinal) ||
|
|
assetDocuments.Contains("\"resources\"", StringComparison.Ordinal) ||
|
|
daemonSoaFramePublisher.Contains("AetheriaRuntimeAssetTransports.Resources", StringComparison.Ordinal) ||
|
|
daemonSoaFramePublisher.Contains("resources://Aetheria/Daemon/EntityProxy", StringComparison.Ordinal) ||
|
|
!daemonSoaFramePublisher.Contains("AetheriaRuntimeAssetTransports.CultMesh", StringComparison.Ordinal) ||
|
|
!daemonSoaFramePublisher.Contains("cultmesh://aetheria/assets/daemon/entity_proxy/mesh", StringComparison.Ordinal) ||
|
|
!daemonSoaFramePublisher.Contains("cultmesh://aetheria/assets/daemon/entity_proxy/material", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon SoA render groups must publish CultMesh CDN asset refs, not renderer-local Resources or Unity Addressables transport.");
|
|
}
|
|
|
|
if (runtimeAssets.Contains("sourceTransport", StringComparison.Ordinal) ||
|
|
runtimeAssets.Contains("resources://", StringComparison.Ordinal) ||
|
|
runtimeGameDocuments.Contains("resources://", StringComparison.Ordinal) ||
|
|
daemonHostProgram.Contains("text.StartsWith(\"resources://\"", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria asset refs and daemon CDN requests must stay CultMesh-native; Resources paths are source resolution details, not public transport.");
|
|
}
|
|
|
|
if (daemonRenderAssetCatalog.Contains("Resources.Load", StringComparison.Ordinal) ||
|
|
daemonRenderAssetCatalog.Contains("resources://", StringComparison.Ordinal) ||
|
|
daemonRenderAssetCatalog.Contains("AetheriaRuntimeAssetTransports.Resources", StringComparison.Ordinal) ||
|
|
daemonIndirectRenderer.Contains("TryResolveMesh(renderGroup?.MeshKey", StringComparison.Ordinal) ||
|
|
daemonIndirectRenderer.Contains("TryResolveMaterial(renderGroup?.MaterialKey", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity daemon rendering must resolve explicit daemon asset refs/catalog entries, not Resources fallback paths or legacy string-key mirrors.");
|
|
}
|
|
|
|
var requiredBoundarySymbols = new[]
|
|
{
|
|
"RuntimeDaemonProviderFileSuffix",
|
|
"RuntimeDaemonHealthFileSuffix",
|
|
"RuntimeDaemonCommandBoundaryFileSuffix",
|
|
"RuntimeDaemonStarbridgeSessionSummaryFileSuffix",
|
|
"RuntimeDaemonGameSurfaceFileSuffix",
|
|
"RuntimeDaemonGameTuiSurfaceFileSuffix",
|
|
"RuntimeDaemonEditorSurfaceFileSuffix",
|
|
"RuntimeDaemonEditorTuiSurfaceFileSuffix",
|
|
"GetDaemonProviderPath(",
|
|
"GetDaemonHealthPath(",
|
|
"GetDaemonCommandBoundaryPath(",
|
|
"GetDaemonStarbridgeSessionSummaryPath(",
|
|
"GetDaemonGameSurfacePath(",
|
|
"GetDaemonGameTuiSurfacePath(",
|
|
"GetDaemonEditorSurfacePath(",
|
|
"GetDaemonEditorTuiSurfacePath("
|
|
};
|
|
var missingBoundarySymbols = requiredBoundarySymbols
|
|
.Where(symbol => !boundary.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBoundarySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Verse publication paths are incomplete: " +
|
|
string.Join(", ", missingBoundarySymbols));
|
|
}
|
|
|
|
var forbiddenPublicationStoreSymbols = new[]
|
|
{
|
|
"WriteDaemonFrame(",
|
|
"ReadDaemonFrame(",
|
|
"WriteDaemonSoaView(",
|
|
"ReadDaemonSoaView(",
|
|
"WriteDaemonProviderAdvertisement(",
|
|
"ReadDaemonProviderAdvertisement(",
|
|
"WriteDaemonHealth(",
|
|
"ReadDaemonHealth(",
|
|
"WriteVerseAuthorityPolicy(",
|
|
"ReadVerseAuthorityPolicy(",
|
|
"WriteDaemonCommandBoundary(",
|
|
"ReadDaemonCommandBoundary(",
|
|
"WriteStarbridgeSessionSummary(",
|
|
"ReadStarbridgeSessionSummary(",
|
|
"WriteDaemonGameSurface(",
|
|
"ReadDaemonGameSurface(",
|
|
"WriteDaemonEditorSurface(",
|
|
"ReadDaemonEditorSurface(",
|
|
"TryReadProviderAdvertisement(",
|
|
"TryReadHealth(",
|
|
"TryReadVerseAuthorityPolicy(",
|
|
"TryReadCommandBoundary(",
|
|
"TryReadAssetManifest(",
|
|
"TryReadStarbridgeSessionSummary(",
|
|
"TryReadGameSurface(",
|
|
"TryReadGameTuiSurface(",
|
|
"TryReadEditorSurface(",
|
|
"TryReadEditorTuiSurface("
|
|
};
|
|
var publicationStoreReaderHits = forbiddenPublicationStoreSymbols
|
|
.Where(symbol => documentStore.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (publicationStoreReaderHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon latest publication still exposes file-backed sidecar helpers instead of managed typed documents: " +
|
|
string.Join(", ", publicationStoreReaderHits));
|
|
}
|
|
|
|
var daemonFrameStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonFrameStore.cs");
|
|
if (File.Exists(daemonFrameStorePath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon latest frames must stay on managed CultMesh pointers; delete AetheriaRuntimeDaemonFrameStore.cs instead of reviving sidecar path writes.");
|
|
}
|
|
|
|
var daemonPublicationStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonPublicationStore.cs");
|
|
if (File.Exists(daemonPublicationStorePath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon latest publications must stay on managed CultMesh pointers; delete AetheriaRuntimeDaemonPublicationStore.cs instead of reviving sidecar path writes.");
|
|
}
|
|
|
|
var daemonSoaViewStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSoaViewStore.cs");
|
|
if (File.Exists(daemonSoaViewStorePath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon SoA latest-view publication must stay on managed CultMesh pointers; delete AetheriaRuntimeDaemonSoaViewStore.cs instead of reviving sidecar path writes.");
|
|
}
|
|
|
|
var requiredDaemonRegistrySymbols = new[]
|
|
{
|
|
"CultMesh.CreateCultCacheDocumentRegistry(DocumentTypes)",
|
|
"CultMesh.CreateCultNetDocumentRegistry(DocumentTypes, registry)",
|
|
"typeof(AetheriaRuntimeDaemonProviderAdvertisementDocument)",
|
|
"typeof(AetheriaRuntimeDaemonHealthDocument)",
|
|
"typeof(AetheriaRuntimeDaemonCommandBoundaryDocument)",
|
|
"typeof(AetheriaRuntimeDaemonFrameDocument)",
|
|
"typeof(AetheriaRuntimeDaemonSoaViewDocument)",
|
|
"typeof(AetheriaRuntimeDaemonCommandDocument)"
|
|
};
|
|
var missingDaemonRegistrySymbols = requiredDaemonRegistrySymbols
|
|
.Where(symbol => !documentRegistry.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonRegistrySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon API documents are not registered as CultCache/CultNet records: " +
|
|
string.Join(", ", missingDaemonRegistrySymbols));
|
|
}
|
|
|
|
var requiredDaemonNodeSymbols = new[]
|
|
{
|
|
"public string RuntimeId { get; }",
|
|
"public CultMeshDocumentHandle<TDocument> Document<TDocument>(CultRecordKey key)",
|
|
"CultMesh.Document<TDocument>(",
|
|
"Database.WatchRecord<TDocument>(key)",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> RuntimeCatalog()",
|
|
"AetheriaRuntimeCatalogStore.OpenReadOnly(StatePath)",
|
|
"public CultMeshMutableStatePointer<TDocument> MutableDocument<TDocument>(CultRecordKey key)",
|
|
"public static CultRecordKey WorldKey { get; }",
|
|
"public static CultRecordKey MigrationLedgerKey { get; }",
|
|
"public static CultRecordKey LegacyCatalogQuarantineKey { get; }",
|
|
"private CultMeshMutableStatePointer<T> MutableDocumentPointer<T>(CultRecordKey key)",
|
|
"CultMesh.MutableStatePointer(",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest"
|
|
};
|
|
var missingDaemonNodeSymbols = requiredDaemonNodeSymbols
|
|
.Where(symbol => !stateNode.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonNodeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaStateNode no longer exposes daemon Verse API records ergonomically: " +
|
|
string.Join(", ", missingDaemonNodeSymbols));
|
|
}
|
|
|
|
var forbiddenDaemonNodeSymbols = new[]
|
|
{
|
|
"public CultMeshMutableStatePointer<AetheriaWorldState> World()",
|
|
"public CultMeshMutableStatePointer<AetheriaMigrationLedger> MigrationLedger()",
|
|
"public CultMeshMutableStatePointer<AetheriaLegacyCatalogQuarantine> LegacyCatalogQuarantine()",
|
|
"public CultMeshMutableStatePointer<AetheriaItemDefinition> ItemDefinition(",
|
|
"public CultMeshMutableStatePointer<AetheriaItemDefinition> ItemDefinitionByLegacyId(",
|
|
"public CultMeshMutableStatePointer<AetheriaCorporation> Corporation(",
|
|
"public CultMeshMutableStatePointer<AetheriaCorporation> CorporationByLegacyId(",
|
|
"public CultMeshMutableStatePointer<AetheriaNameFile> NameFile(",
|
|
"public CultMeshMutableStatePointer<AetheriaNameFile> NameFileByLegacyId(",
|
|
"public CultMeshMutableStatePointer<AetheriaLoadoutTemplate> LoadoutTemplate(",
|
|
"public CultMeshMutableStatePointer<AetheriaRunState> RunState(",
|
|
"public CultMeshMutableStatePointer<AetheriaZoneState> ZoneState(",
|
|
"public CultMeshMutableStatePointer<AetheriaEntitySnapshot> EntitySnapshot(",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeDaemonProviderAdvertisementDocument> ProviderAdvertisement()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeDaemonHealthDocument> Health()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeDaemonCommandBoundaryDocument> CommandBoundary()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeVerseAuthorityPolicyDocument> VerseAuthorityPolicy()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeDaemonFrameDocument> LatestFrame()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeDaemonSoaViewDocument> LatestSoaView()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeStarbridgeScenarioDocument> StarbridgeScenario()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeStarbridgeSessionDocument> StarbridgeSession()",
|
|
"public CultMeshMutableStatePointer<AetheriaRuntimeStarbridgeSessionSummaryDocument> StarbridgeSessionSummary()",
|
|
"public CultMeshMutableStatePointer<EveSurfaceDocument> DaemonGameSurface()",
|
|
"public CultMeshMutableStatePointer<EveSurfaceDocument> DaemonGameTuiSurface()",
|
|
"public CultMeshMutableStatePointer<EveSurfaceDocument> DaemonEditorSurface()",
|
|
"public CultMeshMutableStatePointer<EveSurfaceDocument> DaemonEditorTuiSurface()"
|
|
};
|
|
var survivingDaemonNodeSymbols = forbiddenDaemonNodeSymbols
|
|
.Where(symbol => stateNode.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingDaemonNodeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaStateNode still exposes named daemon Verse record helpers instead of the generic typed document handle: " +
|
|
string.Join(", ", survivingDaemonNodeSymbols));
|
|
}
|
|
|
|
var deletedDaemonSurfaceProjectorPath = Path.Combine(root, "Aetheria.State", "AetheriaRuntimeEveSurfaceStateProjector.cs");
|
|
if (File.Exists(deletedDaemonSurfaceProjectorPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surface state lowering still lives in Aetheria.State projector chaff instead of the runtime surface documents.");
|
|
}
|
|
|
|
if (!runtimeSurfaceDocuments.Contains("public static EveUiSurfaceDocument ToPortableSurface(AetheriaRuntimeSurfaceDocument document)", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("new EveUiCommandTemplate(command.Operation)", StringComparison.Ordinal) ||
|
|
!runtimeSurfaceDocuments.Contains("new EveUiSurfaceComponent(", StringComparison.Ordinal) ||
|
|
runtimeSurfaceDocuments.Contains("GameCult.Mesh.EveSurface", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("Runtime surface documents no longer lower daemon surfaces into canonical Eve surface documents published through CultMesh.");
|
|
}
|
|
|
|
if (!daemonDocuments.Contains("AetheriaRuntimeDaemonCommandKinds", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon command boundary no longer exposes typed daemon command metadata.");
|
|
}
|
|
|
|
var forbiddenDaemonQueueSymbols = new[]
|
|
{
|
|
"public static AetheriaRuntimeDaemonCommandEnvelope QueueCommand(",
|
|
"AetheriaRuntimeDaemonCommandLog.",
|
|
"AetheriaRuntimeDaemonCommandLog.QueueCommand(",
|
|
"var envelope = AetheriaRuntimeDaemonCommandLog.QueueCommand",
|
|
".daemon.pending",
|
|
"ReadPending("
|
|
};
|
|
var daemonQueueSource = daemonDocuments + "\n" + daemonTickRunner + "\n" + tests;
|
|
var survivingDaemonQueueSymbols = forbiddenDaemonQueueSymbols
|
|
.Where(symbol => daemonQueueSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingDaemonQueueSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon command boundary still exposes mailbox queue semantics: " +
|
|
string.Join(", ", survivingDaemonQueueSymbols));
|
|
}
|
|
|
|
if (daemonDocuments.Contains("PendingBeforeApply", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon health still exposes the legacy PendingBeforeApply queue vocabulary instead of observed Verse command documents.");
|
|
}
|
|
|
|
var requiredProviderAdvertisementSymbols = new[]
|
|
{
|
|
"private const string DaemonCommandBoundaryId = \"aetheria.daemon.commands\"",
|
|
"private const string DaemonRecordTransport = \"cultmesh-record\"",
|
|
"AetheriaRuntimeDaemonSchemas.ProviderAdvertisement",
|
|
"AetheriaRuntimeDaemonSchemas.Frame",
|
|
"AetheriaRuntimeDaemonSchemas.SoaView",
|
|
"AetheriaRuntimeDaemonSchemas.Health",
|
|
"AetheriaRuntimeDaemonSchemas.CommandBoundary",
|
|
"AetheriaRuntimeDaemonSchemas.GameSurface",
|
|
"AetheriaRuntimeDaemonSchemas.EditorSurface",
|
|
"AetheriaRuntimeDaemonSchemas.Command",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement.ToString()",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString()",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonSoaViewLatest.ToString()",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonHealth.ToString()",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString()",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString()",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface.ToString()",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface.ToString()",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface.ToString()",
|
|
"public const string DaemonGameSurfaceKey = \"eve:surface:aetheria.daemon.game\"",
|
|
"public const string DaemonGameTuiSurfaceKey = \"eve:surface:aetheria.daemon.game.tui\"",
|
|
"public const string DaemonEditorSurfaceKey = \"eve:surface:aetheria.daemon.editor\"",
|
|
"public const string DaemonEditorTuiSurfaceKey = \"eve:surface:aetheria.daemon.editor.tui\""
|
|
};
|
|
var requiredDaemonProviderDocumentSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId",
|
|
"AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId",
|
|
"AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId",
|
|
"AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId"
|
|
};
|
|
var missingProviderAdvertisementSymbols = requiredProviderAdvertisementSymbols
|
|
.Where(symbol => !providerAdvertisement.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingProviderAdvertisementSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Odin-visible provider advertisement no longer points at managed daemon CultMesh records: " +
|
|
string.Join(", ", missingProviderAdvertisementSymbols));
|
|
}
|
|
var missingDaemonProviderDocumentSymbols = requiredDaemonProviderDocumentSymbols
|
|
.Where(symbol => !daemonDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonProviderDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon provider advertisement document no longer owns daemon surface ids: " +
|
|
string.Join(", ", missingDaemonProviderDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenProviderAdvertisementSymbols = new[]
|
|
{
|
|
"DaemonWitnessTransport",
|
|
"AetheriaRuntimeStateBoundary.GetDaemonProviderPath(statePath)",
|
|
"AetheriaRuntimeStateBoundary.GetDaemonFramePath(statePath)",
|
|
"AetheriaRuntimeStateBoundary.GetDaemonSoaViewPath(statePath)",
|
|
"AetheriaRuntimeStateBoundary.GetDaemonHealthPath(statePath)",
|
|
"AetheriaRuntimeStateBoundary.GetDaemonCommandBoundaryPath(statePath)",
|
|
"AetheriaRuntimeStateBoundary.GetDaemonGameSurfacePath(statePath)",
|
|
"AetheriaRuntimeStateBoundary.GetDaemonGameTuiSurfacePath(statePath)",
|
|
"AetheriaRuntimeStateBoundary.GetDaemonEditorSurfacePath(statePath)",
|
|
"AetheriaRuntimeStateBoundary.GetDaemonEditorTuiSurfacePath(statePath)"
|
|
};
|
|
var forbiddenProviderAdvertisementHits = forbiddenProviderAdvertisementSymbols
|
|
.Where(symbol => providerAdvertisement.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenProviderAdvertisementHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Odin-visible provider advertisement still advertises daemon sidecar paths instead of managed CultMesh records: " +
|
|
string.Join(", ", forbiddenProviderAdvertisementHits));
|
|
}
|
|
|
|
var requiredTickSymbols = new[]
|
|
{
|
|
"VerseId",
|
|
"CultMeshAddress",
|
|
"BuildPublications",
|
|
"AetheriaRuntimeDaemonCommandBoundaryDocument.Create",
|
|
"AetheriaRuntimeDaemonProviderAdvertisementDocument.Create",
|
|
"AetheriaRuntimeStarbridgeDocuments.SessionSummary(",
|
|
"StarbridgeScenario",
|
|
"StarbridgeSession",
|
|
"var catalog = options.Catalog ?? new AetheriaRuntimeCatalogSnapshot(",
|
|
"AetheriaRuntimeDaemonGameSurfaceBuilder.Build",
|
|
"AetheriaRuntimeStatRecipeSurfaceBuilder.BuildFromCatalog(catalog)",
|
|
"AetheriaRuntimeTradeValuePolicySurfaceBuilder.BuildFromCatalog(catalog)",
|
|
"AetheriaRuntimeDaemonEditorSurfaceBuilder.Build",
|
|
"ProviderAdvertisement = providerAdvertisement",
|
|
"Health = health",
|
|
"CommandBoundary = commandBoundary",
|
|
"StarbridgeSessionSummary = starbridgeSessionSummary",
|
|
"GameSurface = gameSurface",
|
|
"ObservedCommands",
|
|
"AccountedCommandIds",
|
|
"frame.AccountedCommandIds = accountedBeforeTick",
|
|
"ObservedCommandCount = observedCommandCount",
|
|
"PublicationSource = \"daemon-published\"",
|
|
"Transport = \"cultmesh-managed\"",
|
|
"CommandBoundaryPath = AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString()"
|
|
};
|
|
var missingTickSymbols = requiredTickSymbols
|
|
.Where(symbol => !daemonTickRunner.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTickSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon tick does not publish Verse-facing provider/health/command-boundary records: " +
|
|
string.Join(", ", missingTickSymbols));
|
|
}
|
|
|
|
var forbiddenTickPublicationSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeDaemonFrameStore.PublishFrame(",
|
|
"AetheriaRuntimeDaemonPublicationStore.PublishCommandBoundary(",
|
|
"AetheriaRuntimeDaemonPublicationStore.PublishProviderAdvertisement(",
|
|
"AetheriaRuntimeDaemonPublicationStore.PublishHealth(",
|
|
"AetheriaRuntimeDaemonPublicationStore.PublishAssetManifest(",
|
|
"AetheriaRuntimeDaemonPublicationStore.PublishStarbridgeSessionSummary(",
|
|
"AetheriaRuntimeDaemonPublicationStore.PublishGameSurface(",
|
|
"AetheriaRuntimeDaemonPublicationStore.PublishGameTuiSurface(",
|
|
"AetheriaRuntimeDaemonPublicationStore.PublishEditorSurface(",
|
|
"AetheriaRuntimeDaemonPublicationStore.PublishEditorTuiSurface(",
|
|
"AetheriaRuntimeCatalogStore.ProjectStatRecipeSurfaceDocument(catalog)",
|
|
"AetheriaRuntimeCatalogStore.ProjectTradeValuePolicySurfaceDocument(catalog)",
|
|
"AetheriaRuntimeCatalogStore.ProjectStatRecipeSurfaceDocument(stateFilePath)",
|
|
"AetheriaRuntimeCatalogStore.ProjectTradeValuePolicySurfaceDocument(stateFilePath)"
|
|
};
|
|
var forbiddenTickPublicationHits = forbiddenTickPublicationSymbols
|
|
.Where(symbol => daemonTickRunner.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenTickPublicationHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon tick runner must build typed publication documents, not write legacy witness stores: " +
|
|
string.Join(", ", forbiddenTickPublicationHits));
|
|
}
|
|
|
|
if (daemonZoneGenerator.Contains("fallbackCargo", StringComparison.Ordinal) ||
|
|
daemonZoneGenerator.Contains("scenarioCargo", StringComparison.Ordinal) ||
|
|
daemonLoadoutGenerator.Contains("scenarioCargo", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon zone generation must not restore invented scenario cargo as a second item-identity authority.");
|
|
}
|
|
|
|
if (daemonZoneGenerator.Contains("settings.TutorialPassed = true", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Generating a Terminus scenario must not award tutorial completion; progression requires its own daemon-owned completion fact.");
|
|
}
|
|
|
|
var requiredDaemonHostSymbols = new[]
|
|
{
|
|
"<OutputType>Exe</OutputType>",
|
|
"ProjectReference Include=\"..\\Aetheria.State\\Aetheria.State.csproj\"",
|
|
"AetheriaStateNode.OpenAsync(",
|
|
"startServer: true",
|
|
"node.MutableDocument<AetheriaRuntimeDaemonFrameDocument>(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest)",
|
|
"Catalog = node.RuntimeCatalog().Latest()",
|
|
"AetheriaDaemonZoneGenerator.WritePlayableRunAsync(",
|
|
"node.MutableDocument<AetheriaRuntimeVerseAuthorityPolicyDocument>(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy)",
|
|
"node.MutableDocument<AetheriaRuntimeStarbridgeScenarioDocument>(AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest)",
|
|
"MutableDocument<AetheriaRuntimeStarbridgeSessionDocument>(AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest)",
|
|
"StarbridgeScenario = starbridgeScenario",
|
|
"StarbridgeSession = starbridgeSession",
|
|
"BuildPublications = buildPublications",
|
|
"PublishDurableDaemonDocumentsAsync(node, result",
|
|
"PublishClientGameplayDocumentsAsync(",
|
|
"PublishSecondaryTopologyDocumentsAsync(node, options, result)",
|
|
"PublishHotEntityStateAsync(",
|
|
"node.MutableDocument<EveEntitySoaViewDocument>(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest)",
|
|
"node.MutableDocument<AetheriaRuntimeDaemonProviderAdvertisementDocument>(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement)",
|
|
"node.MutableDocument<AetheriaRuntimeDaemonHealthDocument>(AetheriaRuntimeVerseRecordKeys.DaemonHealth)",
|
|
"node.MutableDocument<AetheriaRuntimeDaemonCommandBoundaryDocument>(AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary)",
|
|
"node.MutableDocument<AetheriaRuntimeAssetManifestDocument>(AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest)",
|
|
"node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)",
|
|
"node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface)",
|
|
"node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface)",
|
|
"node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface)",
|
|
"node.MutableDocument<AetheriaRuntimeStarbridgeSessionSummaryDocument>(AetheriaRuntimeVerseRecordKeys.StarbridgeSessionSummary)",
|
|
".ReplaceAsync(result.Frame)",
|
|
".ReplaceAsync(result.Health)",
|
|
".ReplaceAsync(result.AssetManifest)",
|
|
".ReplaceAsync(result.StarbridgeSessionSummary)",
|
|
"InjectEveSurfaceSnapshotAsync(",
|
|
"ReadEveSurfacePublicationAsync(",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonGameSurface",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface",
|
|
"AetheriaEveCommandBridge.AcceptObservedAsync(",
|
|
"node.Documents<AetheriaRuntimeDaemonCommandDocument>()",
|
|
"currentFrame?.AccountedCommandIds ?? Array.Empty<string>()",
|
|
"AetheriaRuntimeDaemonTickRunner.Tick(",
|
|
"node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync()",
|
|
"node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey)",
|
|
"node.MutableDocument<AetheriaRuntimeSession>(AetheriaStateNode.RuntimeSessionKey(options.DaemonId))",
|
|
"node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey).ReadAsync()",
|
|
"node.MutableDocument<EveSurfaceDocument>(AetheriaStateNode.OperationsSurfaceKey)",
|
|
"node.MutableDocument<EveSurfaceDocument>(AetheriaStateNode.PlayerSettingsSurfaceKey)",
|
|
"node.MutableDocument<EveProviderAdvertisementDocument>(AetheriaStateNode.ProviderAdvertisementSurfaceKey)",
|
|
"AetheriaEveSurfaceDocuments.BuildProviderAdvertisement(verseHost, node.StatePath, updatedAtUtc)",
|
|
"AetheriaEveSurfaceDocuments.BuildOperationsSurface(eveStatus, verseHost, runtimeSession)",
|
|
"AetheriaEveSurfaceDocuments.BuildPlayerSettingsSurface(playerSettings, playerSettingsUpdatedAt)",
|
|
"Role = \"verse-daemon\"",
|
|
"Console.CancelKeyPress",
|
|
"Aetheria Verse daemon is running"
|
|
};
|
|
var daemonHostSource = daemonHostProject + "\n" + daemonHostProgram;
|
|
var missingDaemonHostSymbols = requiredDaemonHostSymbols
|
|
.Where(symbol => !daemonHostSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonHostSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria.State.Daemon no longer exposes its direct CultMesh daemon host authority: " +
|
|
string.Join(", ", missingDaemonHostSymbols));
|
|
}
|
|
|
|
var forbiddenNamedDaemonHostSymbols = new[]
|
|
{
|
|
"node.LatestFrame()",
|
|
"node.LatestSoaView()",
|
|
"node.ProviderAdvertisement()",
|
|
"node.Health()",
|
|
"node.CommandBoundary()",
|
|
"node.VerseAuthorityPolicy()",
|
|
"node.StarbridgeScenario()",
|
|
"node.StarbridgeSession()",
|
|
"node.StarbridgeSessionSummary()",
|
|
"node.DaemonGameSurface()",
|
|
"node.DaemonGameTuiSurface()",
|
|
"node.DaemonEditorSurface()",
|
|
"node.DaemonEditorTuiSurface()"
|
|
};
|
|
var survivingNamedDaemonHostSymbols = forbiddenNamedDaemonHostSymbols
|
|
.Where(symbol => daemonHostSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingNamedDaemonHostSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria.State.Daemon still uses named daemon Verse record helpers instead of generic typed document handles: " +
|
|
string.Join(", ", survivingNamedDaemonHostSymbols));
|
|
}
|
|
|
|
if (!committedFactImporter.Contains("AetheriaRuntimeCatalogSnapshot? catalog = null", StringComparison.Ordinal) ||
|
|
!committedFactImporter.Contains("Catalog = catalog ?? EmptyCatalog()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Remote committed fact import must pass the managed runtime catalog document into the tick importer instead of reopening catalog storage.");
|
|
}
|
|
|
|
var forbiddenEveProviderAdvertisementWriters = new[]
|
|
{
|
|
"EveProviderAdvertisementState",
|
|
"static Dictionary<string, object?> BuildOdinProviderAdvertisement(",
|
|
"[\"rootVerse\"]",
|
|
"[\"canonicalService\"]",
|
|
"[\"locatedService\"]",
|
|
"[\"routes\"]"
|
|
};
|
|
var survivingEveProviderAdvertisementWriters = forbiddenEveProviderAdvertisementWriters
|
|
.Where(symbol => daemonHostProgram.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingEveProviderAdvertisementWriters.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Odin publication still writes the deleted Aetheria-specific Eve provider advertisement shape: " +
|
|
string.Join(", ", survivingEveProviderAdvertisementWriters));
|
|
}
|
|
|
|
if (committedFactImporter.Contains("AetheriaRuntimeCatalogStore.OpenReadOnly", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Committed fact import must use managed catalog input or an explicit empty typed catalog; it must not reopen catalog storage from the state path.");
|
|
}
|
|
|
|
var apiPublicationStart = daemonHostSource.IndexOf(
|
|
"static async Task PublishDurableDaemonDocumentsAsync",
|
|
StringComparison.Ordinal);
|
|
var apiPublicationEnd = apiPublicationStart >= 0
|
|
? daemonHostSource.IndexOf("static async Task AcceptEveCommandsAsync", apiPublicationStart, StringComparison.Ordinal)
|
|
: -1;
|
|
var apiPublicationBlock = apiPublicationStart >= 0 && apiPublicationEnd > apiPublicationStart
|
|
? daemonHostSource.Substring(apiPublicationStart, apiPublicationEnd - apiPublicationStart)
|
|
: "";
|
|
if (apiPublicationBlock.Contains("AetheriaRuntimeDaemonPublicationStore.TryRead", StringComparison.Ordinal) ||
|
|
apiPublicationBlock.Contains("SoaViewStore.TryRead", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon API document publication must use typed tick result documents instead of reopening witness files.");
|
|
}
|
|
|
|
if (daemonTickRunner.Contains("AetheriaRuntimeDaemonPublicationStore.TryReadHealth(stateFilePath", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon tick must pass the typed health document through surface builders instead of reopening its witness file.");
|
|
}
|
|
|
|
var daemonTickStart = daemonHostSource.IndexOf(
|
|
"static async Task<AetheriaRuntimeDaemonTickResult> TickAsync(",
|
|
StringComparison.Ordinal);
|
|
var daemonTickEnd = daemonTickStart >= 0
|
|
? daemonHostSource.IndexOf("static async Task PublishCommittedCommandFactsAsync", daemonTickStart, StringComparison.Ordinal)
|
|
: -1;
|
|
var daemonTickBlock = daemonTickStart >= 0 && daemonTickEnd > daemonTickStart
|
|
? daemonHostSource.Substring(daemonTickStart, daemonTickEnd - daemonTickStart)
|
|
: "";
|
|
if (daemonTickBlock.Contains("AetheriaRuntimeDaemonFrameStore.PublishFrame(", StringComparison.Ordinal) ||
|
|
daemonTickBlock.Contains("AetheriaRuntimeDaemonPublicationStore.Publish", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon host tick publication must publish typed documents through AetheriaStateNode, not legacy witness stores.");
|
|
}
|
|
|
|
if (!daemonTickBlock.Contains("if (buildPublications)", StringComparison.Ordinal) ||
|
|
!daemonTickBlock.Contains("PublishDurableDaemonDocumentsAsync(node, result", StringComparison.Ordinal) ||
|
|
!daemonTickBlock.Contains("PublishClientGameplayDocumentsAsync(", StringComparison.Ordinal) ||
|
|
!daemonTickBlock.Contains("PublishSecondaryTopologyDocumentsAsync(node, options, result)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon ticks must publish API cadence documents through managed AetheriaStateNode pointers.");
|
|
}
|
|
|
|
var snapshotHandlerStart = daemonHostSource.IndexOf(
|
|
"server.OnCultNet<CultNetSnapshotRequestMessage>",
|
|
StringComparison.Ordinal);
|
|
var snapshotHandlerEnd = snapshotHandlerStart >= 0
|
|
? daemonHostSource.IndexOf("var factPuts = node.Documents<AetheriaRuntimeCommittedCommandFactDocument>()", snapshotHandlerStart, StringComparison.Ordinal)
|
|
: -1;
|
|
var snapshotHandler = snapshotHandlerStart >= 0 && snapshotHandlerEnd > snapshotHandlerStart
|
|
? daemonHostSource.Substring(snapshotHandlerStart, snapshotHandlerEnd - snapshotHandlerStart)
|
|
: "";
|
|
var forbiddenSnapshotStoreReads = new[]
|
|
{
|
|
"AetheriaRuntimeDaemonPublicationStore.TryReadHealth",
|
|
"AetheriaRuntimeDaemonPublicationStore.TryReadStarbridgeSessionSummary"
|
|
};
|
|
var snapshotStoreHits = forbiddenSnapshotStoreReads
|
|
.Where(symbol => snapshotHandler.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (snapshotStoreHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"RUDP snapshot responses must inject managed daemon node documents instead of reopening publication-store files: " +
|
|
string.Join(", ", snapshotStoreHits));
|
|
}
|
|
|
|
var surfaceSnapshotStart = daemonHostSource.IndexOf(
|
|
"static async Task InjectEveSurfaceSnapshotAsync",
|
|
StringComparison.Ordinal);
|
|
var surfaceSnapshotEnd = surfaceSnapshotStart >= 0
|
|
? daemonHostSource.IndexOf("static Task<EveSurfaceState?> ReadEveSurfacePublicationAsync", surfaceSnapshotStart, StringComparison.Ordinal)
|
|
: -1;
|
|
var surfaceSnapshotBlock = surfaceSnapshotStart >= 0 && surfaceSnapshotEnd > surfaceSnapshotStart
|
|
? daemonHostSource.Substring(surfaceSnapshotStart, surfaceSnapshotEnd - surfaceSnapshotStart)
|
|
: "";
|
|
if (surfaceSnapshotBlock.Contains("AetheriaRuntimeDaemonPublicationStore.TryRead", StringComparison.Ordinal) ||
|
|
surfaceSnapshotBlock.Contains("AetheriaRuntimeEveSurfaceStateProjector.ToState(surfaceDocument)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"RUDP Eve surface snapshots still reopen publication-store files instead of using managed Eve surface records.");
|
|
}
|
|
|
|
var forbiddenDaemonHostSymbols = new[]
|
|
{
|
|
".Document<AetheriaRuntimeDaemonFrameDocument>(",
|
|
"node.Document<AetheriaRuntimeVerseAuthorityPolicyDocument>(",
|
|
"node.Document<AetheriaRuntimeStarbridgeScenarioDocument>(",
|
|
"node.Document<AetheriaRuntimeStarbridgeSessionDocument>(",
|
|
"node.Document<EveSurfaceState>(",
|
|
"LatestOrDefaultAsync(",
|
|
"AetheriaRuntimeDaemonFrameStore.TryReadFrame(node.StatePath",
|
|
"Catalog = node.OpenRuntimeCatalog()",
|
|
"AetheriaRuntimeDaemonCommandLog.",
|
|
"DeleteProcessedCommands",
|
|
"new HttpListener",
|
|
"UseUrls(",
|
|
"MapGet(",
|
|
"JsonSerializer.Serialize"
|
|
};
|
|
if (daemonHostSource.Contains("KeepAccountedCommandRecords", StringComparison.Ordinal) ||
|
|
daemonHostSource.Contains("--keep-accounted-command-records", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria.State.Daemon still exposes command-retention toggles after adopting receipt-ledger command accounting.");
|
|
}
|
|
|
|
var requiredTradeValuePolicySurfaceSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeTradeValuePolicySurfaceBuilder",
|
|
"public static AetheriaRuntimeSurfaceDocument BuildFromCatalog(",
|
|
"SurfaceId = \"aetheria.tradeValuePolicy\"",
|
|
"var settings = catalog?.TradeValueSettings ?? AetheriaRuntimeTradeValueSettings.Default",
|
|
"updatedAtUtc: DateTime.UtcNow.ToString(\"O\", CultureInfo.InvariantCulture)",
|
|
"Quality Price Modifier",
|
|
"Rarity Tiers",
|
|
"AetheriaRuntimeTradeValuePolicyCommands.SetQualityMinimum",
|
|
"AetheriaRuntimeTradeValuePolicyCommands.SetTierQuality",
|
|
"BuildCommandTemplates()",
|
|
"control.text",
|
|
"curve.Evaluate(0.25)",
|
|
"ToHex(tier.Red, tier.Green, tier.Blue)"
|
|
};
|
|
var tradeValuePolicySurfaceSource = tradeValuePolicySurfaceBuilder + "\n" + documentStore;
|
|
var missingTradeValuePolicySurfaceSymbols = requiredTradeValuePolicySurfaceSymbols
|
|
.Where(symbol => !tradeValuePolicySurfaceSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTradeValuePolicySurfaceSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Trade value policy is no longer exposed as a designer-owned Eve surface: " +
|
|
string.Join(", ", missingTradeValuePolicySurfaceSymbols));
|
|
}
|
|
|
|
var requiredStatRecipeSurfaceProjectionSymbols = new[]
|
|
{
|
|
"public static AetheriaRuntimeSurfaceDocument BuildFromCatalog(",
|
|
"SelectMany(ProjectRows)",
|
|
"AetheriaRuntimeBehaviorMetadataCatalog.Get(behavior.Kind)",
|
|
"AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat",
|
|
"new AetheriaRuntimeStatRecipeState(",
|
|
"new AetheriaRuntimeStatInfluenceState("
|
|
};
|
|
var missingStatRecipeSurfaceProjectionSymbols = requiredStatRecipeSurfaceProjectionSymbols
|
|
.Where(symbol => !statRecipeSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingStatRecipeSurfaceProjectionSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Stat recipe designer surface projection must live on AetheriaRuntimeStatRecipeSurfaceBuilder: " +
|
|
string.Join(", ", missingStatRecipeSurfaceProjectionSymbols));
|
|
}
|
|
|
|
if (tradeValuePolicySurfaceBuilder.Contains("public static AetheriaRuntimeTradeValuePolicySurfaceState ProjectState(", StringComparison.Ordinal) ||
|
|
tradeValuePolicySurfaceBuilder.Contains("AetheriaRuntimeTradeValuePolicySurfaceState", StringComparison.Ordinal) ||
|
|
tradeValuePolicySurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) ||
|
|
statRecipeSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) ||
|
|
statRecipeSurfaceBuilder.Contains("public static AetheriaRuntimeStatRecipeSurfaceState ProjectState(", StringComparison.Ordinal) ||
|
|
tradeValuePolicySurfaceBuilder.Contains("Build(ProjectState(", StringComparison.Ordinal) ||
|
|
statRecipeSurfaceBuilder.Contains("Build(ProjectState(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Designer surface builders must build finished Eve documents from typed catalog input; do not re-expose ProjectState or shadow SurfaceState helpers.");
|
|
}
|
|
|
|
var forbiddenCatalogStoreSurfaceProjectionSymbols = new[]
|
|
{
|
|
"ProjectStatRecipeSurfaceState(",
|
|
"ProjectTradeValuePolicySurfaceState(",
|
|
"ProjectStatRecipeRows(",
|
|
"ProjectStatRecipeRow(",
|
|
"ProjectStatInfluence("
|
|
};
|
|
var catalogStoreSurfaceProjectionHits = forbiddenCatalogStoreSurfaceProjectionSymbols
|
|
.Where(symbol => runtimeCatalogStore.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (catalogStoreSurfaceProjectionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime catalog store still owns designer surface projection helpers that belong on typed surface builders: " +
|
|
string.Join(", ", catalogStoreSurfaceProjectionHits));
|
|
}
|
|
|
|
if (stateNode.Contains("DeleteDaemonCommandAsync(", StringComparison.Ordinal) ||
|
|
daemonHostSource.Contains("DeleteAccountedDaemonCommandsAsync", StringComparison.Ordinal) ||
|
|
daemonHostSource.Contains("DeleteDaemonCommandAsync(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon command documents are still consumed by deletion instead of accounted by frame receipts.");
|
|
}
|
|
|
|
var survivingDaemonHostSymbols = forbiddenDaemonHostSymbols
|
|
.Where(symbol => daemonHostSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingDaemonHostSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria.State.Daemon has drifted into private command-log/dashboard ownership instead of CultMesh Verse publication: " +
|
|
string.Join(", ", survivingDaemonHostSymbols));
|
|
}
|
|
|
|
var requiredGameSurfaceSymbols = new[]
|
|
{
|
|
"public const string PilotSurfaceId = \"aetheria.pilot\"",
|
|
"public const string CommanderSurfaceId = \"aetheria.starbridge.commander\"",
|
|
"public const string SurfaceId = PilotSurfaceId",
|
|
"public const string TuiSurfaceId = \"aetheria.pilot.tui\"",
|
|
"AetheriaRuntimeDaemonFrameDocument frame",
|
|
"AetheriaRuntimeDaemonHealthDocument health",
|
|
"AetheriaRuntimeDaemonCommandBoundaryDocument commandBoundary",
|
|
"AetheriaRuntimeStarbridgeSessionSummaryDocument? starbridge = null",
|
|
"AetheriaRuntimeStarbridgeDocuments.SessionSummary(frame)",
|
|
"AetheriaRuntimeSurfaceStateRefs.SourceRef(stateRef)",
|
|
"AetheriaRuntimeDaemonStateRefs.CurrentEntityName",
|
|
"AetheriaRuntimeDaemonStateRefs.CurrentTargetName",
|
|
"\"game.daemon\"",
|
|
"\"Station Stock\"",
|
|
"\"Wave Forecast\"",
|
|
"\"Runtime Roles\"",
|
|
"BuildStarbridgeStationStockCard(starbridge)",
|
|
"BuildStarbridgeWaveForecastCard(starbridge)",
|
|
"BuildStarbridgeRuntimeRolesCard(starbridge)",
|
|
"\"Typed Command Boundary\"",
|
|
"\"commandBody\"",
|
|
"nameof(AetheriaRuntimeDaemonCommandDocument)",
|
|
"AetheriaRuntimeDaemonCommandKinds.SetMoveVector"
|
|
};
|
|
var missingGameSurfaceSymbols = requiredGameSurfaceSymbols
|
|
.Where(symbol => !daemonGameSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingGameSurfaceSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon game Eve surface builder is incomplete: " +
|
|
string.Join(", ", missingGameSurfaceSymbols));
|
|
}
|
|
|
|
var requiredDaemonStateRefSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeDaemonStateRefs",
|
|
"public const string Prefix = \"aetheria.daemon\"",
|
|
"public const string CurrentEntityName = Prefix + \"/current/entityName\"",
|
|
"public const string CurrentTargetName = Prefix + \"/current/targetName\"",
|
|
"public const string FrameId = Prefix + \"/frame/frameId\"",
|
|
"public const string CommandCount = Prefix + \"/commands/count\""
|
|
};
|
|
var missingDaemonStateRefSymbols = requiredDaemonStateRefSymbols
|
|
.Where(symbol => !daemonStateRefs.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonStateRefSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surfaces no longer expose a shared typed state-ref vocabulary: " +
|
|
string.Join(", ", missingDaemonStateRefSymbols));
|
|
}
|
|
|
|
var requiredSurfaceCommandCatalogSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeDaemonSurfaceCommandCatalog",
|
|
"public const string CommandPrefix = \"aetheria.daemon.commands.\"",
|
|
"public static IReadOnlyList<AetheriaRuntimeDaemonCommandKinds> ArgumentlessCommands",
|
|
"public static bool IsArgumentlessCommand(",
|
|
"internal static bool TrySubmitArgumentless(",
|
|
"AetheriaRuntimeDaemonCommandKinds.SensorPing => client.SensorPing(frame)"
|
|
};
|
|
var missingSurfaceCommandCatalogSymbols = requiredSurfaceCommandCatalogSymbols
|
|
.Where(symbol => !daemonSurfaceCommandCatalog.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSurfaceCommandCatalogSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon surface command catalog no longer owns the argumentless CultMesh command set: " +
|
|
string.Join(", ", missingSurfaceCommandCatalogSymbols));
|
|
}
|
|
|
|
var surfaceCommandTemplateSources = new Dictionary<string, string>
|
|
{
|
|
["daemon game surface"] = daemonGameSurfaceBuilder,
|
|
["daemon editor surface"] = daemonEditorSurfaceBuilder
|
|
};
|
|
var overBroadSurfaceCommandTemplates = surfaceCommandTemplateSources
|
|
.Where(source =>
|
|
!source.Value.Contains("AetheriaRuntimeDaemonSurfaceCommandCatalog.IsArgumentlessCommand(entry.Kind)", StringComparison.Ordinal) ||
|
|
!source.Value.Contains("AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName(entry.Kind)", StringComparison.Ordinal))
|
|
.Select(source => source.Key)
|
|
.ToArray();
|
|
if (overBroadSurfaceCommandTemplates.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Eve surfaces still advertise command-boundary entries that are not directly submittable surface commands: " +
|
|
string.Join(", ", overBroadSurfaceCommandTemplates));
|
|
}
|
|
|
|
var requiredEditorSurfaceSymbols = new[]
|
|
{
|
|
"public const string SurfaceId = \"aetheria.daemon.editor\"",
|
|
"public const string TuiSurfaceId = \"aetheria.daemon.editor.tui\"",
|
|
"AetheriaRuntimeDaemonProviderAdvertisementDocument provider",
|
|
"AetheriaRuntimeDaemonHealthDocument health",
|
|
"AetheriaRuntimeDaemonCommandBoundaryDocument commandBoundary",
|
|
"\"editor.daemon\"",
|
|
"\"Verse Provider\"",
|
|
"\"Managed Records\"",
|
|
"\"aetheria.daemon.editor.records\"",
|
|
"provider.FrameRecordRef",
|
|
"provider.SoaViewRecordRef",
|
|
"provider.HealthRecordRef",
|
|
"provider.CommandBoundaryRecordRef",
|
|
"provider.EveGuiSurfaceRecordRef",
|
|
"provider.EditorGuiSurfaceRecordRef",
|
|
"\"Designer Surfaces\"",
|
|
"AetheriaRuntimeSurfaceDocument",
|
|
"\"Typed Commands\"",
|
|
"AetheriaRuntimeDaemonCommandBoundaryEntry"
|
|
};
|
|
var missingEditorSurfaceSymbols = requiredEditorSurfaceSymbols
|
|
.Where(symbol => !daemonEditorSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingEditorSurfaceSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon editor Eve surface builder is incomplete: " +
|
|
string.Join(", ", missingEditorSurfaceSymbols));
|
|
}
|
|
|
|
var requiredTestSymbols = new[]
|
|
{
|
|
"var providerAdvertisement = result.ProviderAdvertisement",
|
|
"var health = result.Health",
|
|
"var commandBoundary = result.CommandBoundary",
|
|
"var gameSurface = result.GameSurface",
|
|
"var editorSurface = result.EditorSurface",
|
|
"var gameTuiSurface = result.GameTuiSurface",
|
|
"var editorTuiSurface = result.EditorTuiSurface",
|
|
"AetheriaRuntimeDaemonSchemas.ProviderAdvertisement",
|
|
"AetheriaRuntimeDaemonSchemas.CommandBoundary",
|
|
"AetheriaRuntimeDaemonSchemas.Health",
|
|
"AetheriaRuntimeDaemonSchemas.GameSurface",
|
|
"AetheriaRuntimeDaemonSchemas.EditorSurface",
|
|
"AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId",
|
|
"AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId",
|
|
"AetheriaRuntimeStatRecipeCommands.SurfaceId",
|
|
"AetheriaRuntimeDaemonCommandKinds.TransferCargoItem",
|
|
"nameof(AetheriaRuntimeCargoTransferCommand)"
|
|
};
|
|
var missingTestSymbols = requiredTestSymbols
|
|
.Where(symbol => !tests.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTestSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Verse publication tests are incomplete: " +
|
|
string.Join(", ", missingTestSymbols));
|
|
}
|
|
|
|
var requiredSmokeSymbols = new[]
|
|
{
|
|
"node.MutableDocument<AetheriaRuntimeDaemonProviderAdvertisementDocument>(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement)",
|
|
"node.MutableDocument<AetheriaRuntimeDaemonHealthDocument>(AetheriaRuntimeVerseRecordKeys.DaemonHealth)",
|
|
"node.MutableDocument<AetheriaRuntimeDaemonCommandBoundaryDocument>(AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary)",
|
|
"AetheriaClient.OpenAsync(",
|
|
"daemonCommandClient.Control.SensorPing();",
|
|
"eveCommandClient.Ui.InputSettingsAsync(",
|
|
"AetheriaClient control submission did not appear as a typed daemon state record.",
|
|
"AetheriaClient UI submission did not appear as a typed Eve state record.",
|
|
"node.MutableDocument<AetheriaRuntimeDaemonFrameDocument>(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest)",
|
|
"node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)",
|
|
".ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(daemonGameSurface))",
|
|
"reopened.MutableDocument<AetheriaRuntimeDaemonProviderAdvertisementDocument>(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement)",
|
|
"reopened.MutableDocument<AetheriaRuntimeDaemonHealthDocument>(AetheriaRuntimeVerseRecordKeys.DaemonHealth)",
|
|
"reopened.MutableDocument<AetheriaRuntimeDaemonCommandBoundaryDocument>(AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary)",
|
|
"reopened.MutableDocument<AetheriaRuntimeDaemonFrameDocument>(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest)",
|
|
"reopened.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)",
|
|
"DaemonGameSurfaceKey",
|
|
"DaemonGameTuiSurfaceKey",
|
|
"DaemonEditorSurfaceKey",
|
|
"DaemonEditorTuiSurfaceKey"
|
|
};
|
|
var missingSmokeSymbols = requiredSmokeSymbols
|
|
.Where(symbol => !smoke.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSmokeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"State smoke no longer proves daemon Verse API records round-trip through CultCache: " +
|
|
string.Join(", ", missingSmokeSymbols));
|
|
}
|
|
|
|
var forbiddenSmokeSymbols = new[]
|
|
{
|
|
"PutWorldAsync(",
|
|
"GetWorldAsync(",
|
|
"PutLegacyItemDefinitionAsync(",
|
|
"GetItemDefinitionByLegacyIdAsync(",
|
|
"PutLegacyCorporationAsync(",
|
|
"GetCorporationByLegacyIdAsync(",
|
|
"PutLegacyNameFileAsync(",
|
|
"GetNameFileByLegacyIdAsync(",
|
|
"PutCatalogSurfaceAsync(",
|
|
"GetCatalogSurfaceAsync(",
|
|
"PutOperationsSurfaceAsync(",
|
|
"GetOperationsSurfaceAsync(",
|
|
"PutProviderAdvertisementAsync(",
|
|
"GetProviderAdvertisementAsync(",
|
|
"PutDaemonProviderAdvertisementAsync(",
|
|
"GetDaemonProviderAdvertisementAsync(",
|
|
"PutDaemonHealthAsync(",
|
|
"GetDaemonHealthAsync(",
|
|
"PutDaemonCommandBoundaryAsync(",
|
|
"GetDaemonCommandBoundaryAsync(",
|
|
"PutDaemonFrameAsync(",
|
|
"GetDaemonFrameAsync(",
|
|
"PutDaemonGameSurfaceAsync(",
|
|
"GetDaemonGameSurfaceAsync(",
|
|
"node.ProviderAdvertisement()",
|
|
"node.Health()",
|
|
"node.CommandBoundary()",
|
|
"node.LatestFrame()",
|
|
"node.DaemonGameSurface()",
|
|
"reopened.ProviderAdvertisement()",
|
|
"reopened.Health()",
|
|
"reopened.CommandBoundary()",
|
|
"reopened.LatestFrame()",
|
|
"reopened.DaemonGameSurface()",
|
|
"PutRuntimeSessionAsync(",
|
|
"GetRuntimeSessionAsync(",
|
|
"PutLoadoutTemplateAsync(",
|
|
"GetLoadoutTemplateAsync(",
|
|
"PutEntitySnapshotAsync(",
|
|
"GetEntitySnapshotAsync(",
|
|
"PutZoneStateAsync(",
|
|
"GetZoneStateAsync(",
|
|
"PutRunStateAsync(",
|
|
"GetRunStateAsync(",
|
|
"PutPlayerSettingsAsync(",
|
|
"GetPlayerSettingsAsync(",
|
|
"PutPlayerSettingsSurfaceAsync(",
|
|
"GetPlayerSettingsSurfaceAsync(",
|
|
"PutEveCommandAcceptanceStatusAsync(",
|
|
"GetEveCommandAcceptanceStatusAsync("
|
|
};
|
|
var survivingSmokeSymbols = forbiddenSmokeSymbols
|
|
.Where(symbol => smoke.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingSmokeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"State smoke still teaches compatibility helper access instead of managed typed state pointers: " +
|
|
string.Join(", ", survivingSmokeSymbols));
|
|
}
|
|
|
|
var requiredNoteSymbols = new[]
|
|
{
|
|
"Odin is the all-seer, not the owner.",
|
|
"Idunn keeps the daemon alive from daemon-published health and command-boundary records.",
|
|
"Bifrost hosts the MCP crossing for Codex and other xeno agents.",
|
|
"The Aetheria daemon remains the side-effect owner.",
|
|
"Witness-authoritative networking sharpens this rule.",
|
|
"Unity should not be a dumb terminal;",
|
|
"local projection caches, prediction state, render-native SoA views, and eventually immutable witness observations",
|
|
"Observation, prediction, consensus candidates, and committed facts are separate stages.",
|
|
"it must not mutate canonical Aetheria state or promote a local projection cache into authority.",
|
|
"gamecult.aetheria.daemon_provider_advertisement.v1",
|
|
"gamecult.aetheria.daemon_health.v1",
|
|
"gamecult.aetheria.daemon_command_boundary.v1",
|
|
"gamecult.aetheria.daemon_frame.v1",
|
|
"gamecult.aetheria.daemon_soa_view.v1",
|
|
"gamecult.aetheria.daemon_game_surface.v1",
|
|
"gamecult.aetheria.daemon_editor_surface.v1",
|
|
"gamecult.eve.provider_advertisement.v1` is the Odin-visible Eve provider card",
|
|
"gamecult.aetheria.daemon_provider_advertisement.v1` is the daemon-owned Aetheria runtime contract",
|
|
"The bridge layer must point at daemon-owned witnesses instead of becoming a second source of truth.",
|
|
"Long term, Odin should discover the daemon-owned provider advertisement and interface bindings directly",
|
|
"Queues are an implementation detail.",
|
|
"Eve commands and daemon commands are typed `gamecult.eve.command.v1` and `gamecult.aetheria.daemon_command.v1` records in the Aetheria state graph",
|
|
"`AetheriaRuntimeVerseClient` is the only shared submission boundary for typed command records.",
|
|
"Do not add command ports, cached submitters, mailboxes, or queue-like buses between clients and the typed Verse graph.",
|
|
"`CommandLog`, `Inbox`, `mailbox`, `.eve.commands`, `.daemon.commands`, `.cc.pending`, or `" + "Pending" + "CultCacheStore` in Unity-facing daemon/Eve command code",
|
|
"Do not add a private HTTP dashboard, Unity-only inspector, JSON status blob, or agent-specific daemon wrapper as canonical truth."
|
|
};
|
|
var missingNoteSymbols = requiredNoteSymbols
|
|
.Where(symbol => !note.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingNoteSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon Verse shape note no longer records the Odin/VoidBot daemon authority contract: " +
|
|
string.Join(", ", missingNoteSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireUnityRuntimeCatalogClientUsesManagedDocument(string root)
|
|
{
|
|
var clientPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaClientState.cs");
|
|
var verseClientPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeVerseClient.cs");
|
|
var mapperPath = Path.Combine(root, "Aetheria.State.Unity", "AetheriaRuntimeCatalogSnapshotMapper.cs");
|
|
var client = File.Exists(clientPath)
|
|
? File.ReadAllText(clientPath)
|
|
: throw new InvalidOperationException("Cannot verify managed Unity runtime catalog client; AetheriaClientState.cs is missing.");
|
|
var verseClient = File.Exists(verseClientPath)
|
|
? File.ReadAllText(verseClientPath)
|
|
: throw new InvalidOperationException("Cannot verify managed Unity runtime catalog client; AetheriaRuntimeVerseClient.cs is missing.");
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument;",
|
|
"CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> catalog",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> Catalog",
|
|
"public CultMeshDocumentHandle<EveSurfaceDocument>? EveSurfaceDocument(",
|
|
"AetheriaClientEveSurface.Game => GameSurface",
|
|
"AetheriaClientEveSurface.Editor => EditorSurface"
|
|
};
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !client.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime catalog client must read the managed runtime catalog document: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var requiredVerseClientSymbols = new[]
|
|
{
|
|
"BootstrapCatalogDocument(",
|
|
"Document<EveSurfaceDocument>(",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonGameSurface",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface"
|
|
};
|
|
var missingVerseClientSymbols = requiredVerseClientSymbols
|
|
.Where(symbol => !verseClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingVerseClientSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime Verse client no longer exposes managed catalog/Eve surface document handles: " +
|
|
string.Join(", ", missingVerseClientSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"EveSurfaceState",
|
|
"_node.ReadCatalog",
|
|
"_node.GetCatalogSurfaceAsync()",
|
|
"ReadCatalogSurfaceAsync(",
|
|
"AetheriaRuntimeCatalogStore.ReadEveSurfaces",
|
|
"AetheriaRuntimeCatalogStore",
|
|
"ToState(global::GameCult.Eve.Surface.EveSurfaceDocument",
|
|
"AetheriaRuntimeCatalogSnapshotMapper.FromCatalog(",
|
|
"AetheriaCatalogSnapshot"
|
|
};
|
|
var forbiddenHits = forbiddenSymbols
|
|
.Where(symbol => client.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime catalog client still maps authored catalog state instead of using the managed runtime document: " +
|
|
string.Join(", ", forbiddenHits));
|
|
}
|
|
|
|
if (File.Exists(mapperPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime catalog snapshot mapper is dead projection chaff; AetheriaStateNode.RuntimeCatalog owns this document now.");
|
|
}
|
|
|
|
Console.WriteLine("Unity runtime catalog client: catalog reads use the managed typed runtime document");
|
|
}
|
|
|
|
static void RequireCatalogSurfaceUsesManagedRuntimeCatalog(string root)
|
|
{
|
|
var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs");
|
|
var surfaceDocumentsPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs");
|
|
var bridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs");
|
|
var importPath = Path.Combine(root, "Aetheria.State.Import", "Program.cs");
|
|
var legacySnapshotPath = Path.Combine(root, "Aetheria.State", "AetheriaCatalogSnapshot.cs");
|
|
var surfaceDocuments = File.Exists(surfaceDocumentsPath)
|
|
? File.ReadAllText(surfaceDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify catalog surface document; AetheriaEveSurfaceDocuments.cs is missing.");
|
|
var bridge = File.Exists(bridgePath)
|
|
? File.ReadAllText(bridgePath)
|
|
: throw new InvalidOperationException("Cannot verify Eve command bridge catalog refresh; AetheriaEveCommandBridge.cs is missing.");
|
|
var import = File.Exists(importPath)
|
|
? File.ReadAllText(importPath)
|
|
: throw new InvalidOperationException("Cannot verify legacy catalog import; Aetheria.State.Import/Program.cs is missing.");
|
|
var stateNode = File.Exists(stateNodePath)
|
|
? File.ReadAllText(stateNodePath)
|
|
: throw new InvalidOperationException("Cannot verify runtime catalog ownership; AetheriaStateNode.cs is missing.");
|
|
|
|
var requiredStateNodeSymbols = new[]
|
|
{
|
|
"Database.GetAsync<AetheriaRuntimeCatalogSnapshot>(RuntimeCatalogKey)",
|
|
"Database.WatchRecord<AetheriaRuntimeCatalogSnapshot>(RuntimeCatalogKey)",
|
|
"public async Task<AetheriaRuntimeCatalogSnapshot> RefreshRuntimeCatalogAsync()",
|
|
"await Database.PutAsync(RuntimeCatalogKey, snapshot).ConfigureAwait(false);",
|
|
"_ => catalog.Watch()"
|
|
};
|
|
var missingStateNodeSymbols = requiredStateNodeSymbols
|
|
.Where(symbol => !stateNode.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingStateNodeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The state node must own one explicitly refreshed runtime catalog document: " +
|
|
string.Join(", ", missingStateNodeSymbols));
|
|
}
|
|
|
|
var runtimeCatalogMethod = stateNode[
|
|
stateNode.IndexOf("public CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> RuntimeCatalog()", StringComparison.Ordinal)..
|
|
stateNode.IndexOf("public CultMeshDocumentHandle<EveSurfaceDocument> CatalogSurface()", StringComparison.Ordinal)];
|
|
if (runtimeCatalogMethod.Contains("DaemonFrameLatest", StringComparison.Ordinal) ||
|
|
runtimeCatalogMethod.Contains("OpenReadOnly(StatePath)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon frames must not invalidate or recompile the runtime catalog.");
|
|
}
|
|
|
|
var requiredSurfaceDocumentSymbols = new[]
|
|
{
|
|
"BuildCatalogSurface(",
|
|
"AetheriaRuntimeCatalogSnapshot catalog",
|
|
"item.ItemKey",
|
|
"corporation.CorporationKey",
|
|
"catalog.GetManufacturer(item)",
|
|
"catalog.GetNameFile(corporation)"
|
|
};
|
|
var missingSurfaceDocumentSymbols = requiredSurfaceDocumentSymbols
|
|
.Where(symbol => !surfaceDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSurfaceDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Catalog Eve surface must build from the managed runtime catalog document: " +
|
|
string.Join(", ", missingSurfaceDocumentSymbols));
|
|
}
|
|
|
|
if (surfaceDocuments.Contains("Build(AetheriaCatalogSnapshot catalog", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Catalog Eve surface still depends on the legacy authored catalog snapshot instead of the managed runtime catalog document.");
|
|
}
|
|
|
|
if (File.Exists(legacySnapshotPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaCatalogSnapshot is dead projection chaff; AetheriaRuntimeCatalogSnapshot is the managed typed catalog document.");
|
|
}
|
|
|
|
var requiredBridgeSymbols = new[]
|
|
{
|
|
"var catalog = await node.RefreshRuntimeCatalogAsync().ConfigureAwait(false);",
|
|
"AetheriaEveSurfaceDocuments.BuildCatalogSurface(catalog, command.IssuedAtUtc)"
|
|
};
|
|
var missingBridgeSymbols = requiredBridgeSymbols
|
|
.Where(symbol => !bridge.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBridgeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve catalog refresh must read the managed runtime catalog document: " +
|
|
string.Join(", ", missingBridgeSymbols));
|
|
}
|
|
|
|
if (bridge.Contains("ReadCatalog", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve catalog refresh still rebuilds the legacy catalog snapshot instead of using AetheriaStateNode.RuntimeCatalog().");
|
|
}
|
|
|
|
var requiredImportSymbols = new[]
|
|
{
|
|
"node.MutableDocument<AetheriaLegacyCatalogQuarantine>(AetheriaStateNode.LegacyCatalogQuarantineKey)",
|
|
"node.MutableDocument<AetheriaMigrationLedger>(AetheriaStateNode.MigrationLedgerKey)",
|
|
"node.MutableDocument<AetheriaItemDefinition>(AetheriaCatalogKeys.ItemDefinitionFromLegacyId(item.LegacyId))",
|
|
"node.MutableDocument<AetheriaCorporation>(AetheriaCatalogKeys.CorporationFromLegacyId(corporation.LegacyId))",
|
|
"node.MutableDocument<AetheriaNameFile>(AetheriaCatalogKeys.NameFileFromLegacyId(nameFile.LegacyId))",
|
|
"node.MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey)",
|
|
"AetheriaRuntimeStateMapper.ToTradeValuePolicy(",
|
|
"TryReadRectangularShape(columns, out var rectangularShape)",
|
|
"ProjectCatalogRarity(ProjectStationSensorMount(ProjectDeployableWeapon(entry.ItemDefinition!)))",
|
|
"AetherDB.2021-04-14.msgpack",
|
|
"LegacyCatalogLayout.April2021",
|
|
"c4b20032-8cd3-4206-807f-97b296797425",
|
|
"bb5d9f73-612f-4060-8fb4-f72d6b93b616",
|
|
"rarity:rare",
|
|
"--merge-station-sensor",
|
|
"RefreshRuntimeCatalogAsync()",
|
|
"await node.CatalogSurface().LatestAsync().ConfigureAwait(false);"
|
|
};
|
|
var missingImportSymbols = requiredImportSymbols
|
|
.Where(symbol => !import.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingImportSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Legacy catalog import must write through managed typed document handles and materialize the managed catalog surface: " +
|
|
string.Join(", ", missingImportSymbols));
|
|
}
|
|
|
|
var forbiddenImportSymbols = new[]
|
|
{
|
|
"PutLegacyCatalogQuarantineAsync(",
|
|
"PutMigrationLedgerAsync(",
|
|
"PutLegacyItemDefinitionAsync(",
|
|
"PutLegacyCorporationAsync(",
|
|
"PutLegacyNameFileAsync(",
|
|
"PutTradeValuePolicyAsync(",
|
|
"PutCatalogSurfaceAsync("
|
|
};
|
|
var forbiddenImportHits = forbiddenImportSymbols
|
|
.Where(symbol => import.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenImportHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Legacy catalog import still uses AetheriaStateNode Put helpers instead of managed typed documents: " +
|
|
string.Join(", ", forbiddenImportHits));
|
|
}
|
|
|
|
Console.WriteLine("Catalog Eve surface: refresh path uses the managed runtime catalog document");
|
|
}
|
|
|
|
static void RequireDaemonLoadoutRarityAuthority(string root)
|
|
{
|
|
var generatorPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaDaemonLoadoutGenerator.cs");
|
|
var generator = File.ReadAllText(generatorPath);
|
|
var required = new[]
|
|
{
|
|
"private Random _random;",
|
|
"RareGenerationWeight",
|
|
"rarity:rare",
|
|
"RarityWeight(item)"
|
|
};
|
|
var missing = required.Where(symbol => !generator.Contains(symbol, StringComparison.Ordinal)).ToArray();
|
|
if (missing.Length > 0)
|
|
throw new InvalidOperationException(
|
|
"Daemon loadout generation must own one advancing CultMath stream and apply typed rarity policy: " +
|
|
string.Join(", ", missing));
|
|
if (generator.Contains("private readonly Random _random;", StringComparison.Ordinal))
|
|
throw new InvalidOperationException(
|
|
"A readonly CultMath.Random field mutates a defensive copy and restarts weighted selection on every draw.");
|
|
}
|
|
|
|
static void RequireAetheriaRuntimeVerseClientContract(string root)
|
|
{
|
|
var clientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseClient.cs");
|
|
var clientStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClientState.cs");
|
|
var surfaceStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveSurfaceState.cs");
|
|
var oldSurfaceStatePath = Path.Combine(root, "Aetheria.State", "Documents", "EveSurfaceState.cs");
|
|
var stateRefResolverPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateRefResolver.cs");
|
|
var docPath = Path.Combine(root, "docs", "aetheria-verse-client-contract.md");
|
|
|
|
var requiredFiles = new[] { clientPath, clientStatePath, stateRefResolverPath, docPath };
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime Verse client contract cannot be verified because required files are missing: " +
|
|
string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var client = File.ReadAllText(clientPath);
|
|
var clientState = File.ReadAllText(clientStatePath);
|
|
var stateRefResolver = File.ReadAllText(stateRefResolverPath);
|
|
var doc = File.ReadAllText(docPath);
|
|
|
|
if (File.Exists(oldSurfaceStatePath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria-local EveSurfaceState must not own UI surface state; clients watch the shared CultMesh EveSurfaceDocument.");
|
|
}
|
|
|
|
if (File.Exists(surfaceStatePath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeEveSurfaceState.cs is legacy surface-state cruft; daemon UI surfaces must publish GameCult.Eve.Surface.EveSurfaceDocument.");
|
|
}
|
|
|
|
var requiredClientSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaRuntimeVerseClient",
|
|
"public static class AetheriaRuntimeVerseRecordKeys",
|
|
"public static class AetheriaRuntimeVerseContractRegistry",
|
|
"AetheriaRuntimeVerseContractRegistry.CreateCultCacheRegistry()",
|
|
"AetheriaRuntimeVerseContractRegistry.CreateCultNetRegistry(registry)",
|
|
"public Observable<CultNetDatabaseChange<TDocument>> WatchRecord<TDocument>(",
|
|
"WatchRecord<AetheriaRuntimeDaemonFrameDocument>",
|
|
"public CultMeshMutableStatePointer<TDocument> MutableDocument<TDocument>(",
|
|
"CultMesh.MutableStatePointer(",
|
|
"CultMesh.CreateCultCacheDocumentRegistry(RuntimeDocumentTypes)",
|
|
"CultMesh.CreateCultNetDocumentRegistry(RuntimeDocumentTypes, registry)",
|
|
"typeof(AetheriaRuntimeVerseAuthorityPolicyDocument)",
|
|
"typeof(AetheriaRuntimeCatalogSnapshot)",
|
|
"typeof(AetheriaRuntimeLoadoutTemplatesDocument)",
|
|
"AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy",
|
|
"public static CultRecordKey StarbridgeSessionSummary",
|
|
"new CultRecordKey(\"daemon:aetheria.starbridge.session.latest.v1\")",
|
|
"Document<AetheriaRuntimeVerseAuthorityPolicyDocument>(",
|
|
"typeof(EveSurfaceDocument)",
|
|
"private AetheriaClientState? _aetheriaState",
|
|
"private CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument>? _managedDaemonFrame",
|
|
"private CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot>? _managedCatalog",
|
|
"private CultMeshReactiveDocument<AetheriaRuntimeLoadoutTemplatesDocument>? _managedLoadoutTemplates",
|
|
"private CultMeshReactiveDocument<AetheriaRuntimeStarbridgeScenarioDocument>? _managedStarbridgeScenario",
|
|
"private CultMeshReactiveDocument<AetheriaRuntimeStarbridgeSessionDocument>? _managedStarbridgeSession",
|
|
"_managedDaemonFrame?.Dispose()",
|
|
"_managedCatalog?.Dispose()",
|
|
"_managedLoadoutTemplates?.Dispose()",
|
|
"_managedStarbridgeScenario?.Dispose()",
|
|
"_managedStarbridgeSession?.Dispose()",
|
|
"return _aetheriaState ??= CreateAetheriaState();",
|
|
"AetheriaRuntimeLoadoutTemplatesDocument",
|
|
"StarbridgeSummaryAsync",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument>? managedDaemonFrame = null;",
|
|
"managedDaemonFrame ??= latestFrameDocument.Reactive();",
|
|
"managedCatalog ??= catalogDocument.Reactive();",
|
|
"managedLoadoutTemplates ??= loadoutTemplatesDocument.Reactive();",
|
|
"managedPlayerSettings = state.PlayerSettings.Reactive();",
|
|
"managedStarbridgeScenario ??= starbridgeScenarioDocument.Reactive();",
|
|
"managedStarbridgeSession ??= starbridgeSessionDocument.Reactive();",
|
|
"AetheriaRuntimeDaemonFrameDocument RequireManagedFrame()",
|
|
"RequireManagedFrame()",
|
|
"RequireManagedCatalog()",
|
|
"RequireManagedLoadoutTemplates().Templates",
|
|
"return managedStarbridgeScenario.Current;",
|
|
"return managedStarbridgeSession.Current;",
|
|
"BootstrapCatalogDocument(",
|
|
"CatalogBootstrapSource(",
|
|
"BootstrapRuntimeCatalog",
|
|
"BootstrapLoadoutTemplates",
|
|
"BootstrapPlayerSettings",
|
|
"BootstrapVerseHostSettings",
|
|
"SubmitDaemonCommandAsync(",
|
|
"SubmitEveCommandAsync(",
|
|
"AetheriaRuntimeVerseRecordKeys.DaemonCommand(command.CommandId)",
|
|
"AetheriaRuntimeVerseRecordKeys.EveCommand(command.CommandId)",
|
|
"DaemonGameTuiSurface { get; }",
|
|
"DaemonEditorTuiSurface { get; }"
|
|
};
|
|
var missingClientSymbols = requiredClientSymbols
|
|
.Where(symbol => !client.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingClientSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime Verse client is missing typed CultMesh client contract symbols: " +
|
|
string.Join(", ", missingClientSymbols));
|
|
}
|
|
|
|
var forbiddenNamedWatchHelpers = new[]
|
|
{
|
|
"WatchProviderAdvertisements(",
|
|
"WatchHealth(",
|
|
"WatchCommandBoundary(",
|
|
"WatchVerseAuthorityPolicies(",
|
|
"WatchLatestFrames(",
|
|
"WatchLatestSoaViews(",
|
|
"WatchStarbridgeScenarios(",
|
|
"WatchStarbridgeSessions(",
|
|
"WatchStarbridgePlayerSeat(",
|
|
"WatchDaemonGameSurfaces(",
|
|
"WatchDaemonGameTuiSurfaces(",
|
|
"WatchDaemonEditorSurfaces(",
|
|
"WatchDaemonEditorTuiSurfaces("
|
|
};
|
|
var survivingNamedWatchHelpers = forbiddenNamedWatchHelpers
|
|
.Where(symbol => client.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingNamedWatchHelpers.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient must expose generic WatchRecord<TDocument>(CultRecordKey) instead of named record-watch wrappers: " +
|
|
string.Join(", ", survivingNamedWatchHelpers));
|
|
}
|
|
|
|
var forbiddenNamedMutablePointers = new[]
|
|
{
|
|
"ProviderAdvertisement(",
|
|
"Health(",
|
|
"CommandBoundary(",
|
|
"VerseAuthorityPolicy(",
|
|
"LatestFrame(",
|
|
"LatestSoaView(",
|
|
"StarbridgeScenario(",
|
|
"StarbridgeSession(",
|
|
"StarbridgePlayerSeat(",
|
|
"DaemonGameSurface(",
|
|
"DaemonGameTuiSurface(",
|
|
"DaemonEditorSurface(",
|
|
"DaemonEditorTuiSurface("
|
|
};
|
|
var runtimeClientInstanceApi = client.Split(
|
|
"public AetheriaClientState Aetheria()",
|
|
StringSplitOptions.None).Last().Split(
|
|
"public CultMeshDocumentHandle<TDocument> Document<TDocument>(",
|
|
StringSplitOptions.None)[0];
|
|
var survivingNamedMutablePointers = forbiddenNamedMutablePointers
|
|
.Where(symbol => runtimeClientInstanceApi.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingNamedMutablePointers.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient must expose generic MutableDocument<TDocument>(CultRecordKey) instead of named mutable pointer wrappers: " +
|
|
string.Join(", ", survivingNamedMutablePointers));
|
|
}
|
|
|
|
if (client.Contains("ReactiveDocumentAsync<TDocument>", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient must not expose a second reactive-document alias; callers should use Document<TDocument>(key).ReactiveAsync().");
|
|
}
|
|
|
|
if (File.Exists(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeManagedClientInputs.cs")) ||
|
|
client.Contains("AetheriaRuntimeManagedClientInputs", StringComparison.Ordinal) ||
|
|
client.Contains("AetheriaRuntimeDaemonFrameSession _daemonFrame", StringComparison.Ordinal) ||
|
|
client.Contains("AetheriaRuntimeCatalogSession _catalog", StringComparison.Ordinal) ||
|
|
client.Contains("AetheriaRuntimeLoadoutTemplatesSession _loadoutTemplates", StringComparison.Ordinal) ||
|
|
client.Contains("AetheriaRuntimeStarbridgeScenarioSession _starbridgeScenario", StringComparison.Ordinal) ||
|
|
client.Contains("AetheriaRuntimeStarbridgeRunSession _starbridgeSession", StringComparison.Ordinal) ||
|
|
client.Contains("state.ObserveDaemonFrame()", StringComparison.Ordinal) ||
|
|
client.Contains("state.ObserveCatalog()", StringComparison.Ordinal) ||
|
|
client.Contains("state.ObserveLoadoutTemplates()", StringComparison.Ordinal) ||
|
|
client.Contains("state.Starbridge.ObserveScenario()", StringComparison.Ordinal) ||
|
|
client.Contains("state.Starbridge.ObserveSession()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Verse client derived documents must sample directly owned named reactive typed documents instead of an input/session wrapper.");
|
|
}
|
|
|
|
if (client.Contains("AetheriaRuntimeReactiveProjectionInputs", StringComparison.Ordinal) ||
|
|
client.Contains("private sealed class AetheriaRuntimeManagedClientInputs", StringComparison.Ordinal) ||
|
|
client.Contains("_projectionInputs", StringComparison.Ordinal) ||
|
|
client.Contains("ProjectionInputs", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient reintroduced a projection-input helper instead of owning generic reactive typed documents directly.");
|
|
}
|
|
|
|
if (client.Contains("StationRefitAsync", StringComparison.Ordinal) &&
|
|
client.Contains("loadoutTemplatesDocument.LatestAsync()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime Verse client station-refit projection still polls loadout templates instead of sampling managed reactive client inputs.");
|
|
}
|
|
|
|
if (client.Contains("StarbridgeSummaryAsync", StringComparison.Ordinal) &&
|
|
(client.Contains("starbridgeScenarioDocument.LatestAsync()", StringComparison.Ordinal) ||
|
|
client.Contains("starbridgeSessionDocument.LatestAsync()", StringComparison.Ordinal) ||
|
|
client.Contains("catalogDocument.LatestAsync()", StringComparison.Ordinal)))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime Verse client Starbridge summary document still polls sibling documents instead of sampling managed reactive client inputs.");
|
|
}
|
|
|
|
if (client.Contains("RequireFrameAsync", StringComparison.Ordinal) ||
|
|
client.Contains("Aetheria().Daemon.LatestFrame.LatestAsync()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime Verse client managed documents still bootstrap through one-shot latest-frame reads instead of the managed reactive client inputs.");
|
|
}
|
|
|
|
if (client.Contains("_fallbackCatalog", StringComparison.Ordinal) ||
|
|
client.Contains("_fallbackLoadoutTemplates", StringComparison.Ordinal) ||
|
|
client.Contains("fallbackCatalog", StringComparison.Ordinal) ||
|
|
client.Contains("fallbackLoadoutTemplates", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime Verse client projections must use managed reactive catalog/loadout documents directly instead of carrying manual fallback snapshots.");
|
|
}
|
|
|
|
var requiredClientStateSymbols = new[]
|
|
{
|
|
"public CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver()",
|
|
"public sealed class AetheriaClientState : IDisposable",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument> Inventory(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeStarbridgeSessionDocument> StarbridgeSession",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument>? _eveStateRefFrame",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeDaemonHealthDocument>? _eveStateRefHealth",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeDaemonCommandBoundaryDocument>? _eveStateRefCommandBoundary",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot>? _eveStateRefCatalog",
|
|
"() => ReadOptionalReactive(ref _eveStateRefFrame, DaemonFrame)",
|
|
"() => ReadOptionalReactive(ref _eveStateRefHealth, Health)",
|
|
"() => ReadOptionalReactive(ref _eveStateRefCommandBoundary, CommandBoundary)",
|
|
"() => ReadOptionalReactive(ref _eveStateRefCatalog, Catalog)",
|
|
"private static TDocument? ReadOptionalReactive<TDocument>(",
|
|
"reactive ??= handle.Reactive()",
|
|
"return reactive.Current",
|
|
"public void Dispose()",
|
|
"_eveStateRefFrame?.Dispose()",
|
|
"_eveStateRefHealth?.Dispose()",
|
|
"_eveStateRefCommandBoundary?.Dispose()",
|
|
"_eveStateRefCatalog?.Dispose()",
|
|
"AetheriaRuntimeStateRefResolver.CreateEveSurfaceCultMeshStateRefResolver("
|
|
};
|
|
var missingClientStateSymbols = requiredClientStateSymbols
|
|
.Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingClientStateSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must own Eve state-ref resolver creation through managed typed documents: " +
|
|
string.Join(", ", missingClientStateSymbols));
|
|
}
|
|
|
|
if (clientState.Contains("var frameTask = Daemon.LatestFrameDocumentAsync()", StringComparison.Ordinal) ||
|
|
clientState.Contains("public readonly struct AetheriaClientZone", StringComparison.Ordinal) ||
|
|
clientState.Contains("public readonly struct AetheriaClientEntity", StringComparison.Ordinal) ||
|
|
clientState.Contains("public static AetheriaClientZone Zone(", StringComparison.Ordinal) ||
|
|
clientState.Contains("public static AetheriaClientEntity Entity(", StringComparison.Ordinal) ||
|
|
clientState.Contains("Document<TDocument>(AetheriaClientZone", StringComparison.Ordinal) ||
|
|
clientState.Contains("Document<TDocument>(AetheriaClientEntity", StringComparison.Ordinal) ||
|
|
clientState.Contains("var healthTask = Daemon.LatestHealthAsync()", StringComparison.Ordinal) ||
|
|
clientState.Contains("var commandBoundaryTask = Daemon.LatestCommandBoundaryAsync()", StringComparison.Ordinal) ||
|
|
clientState.Contains("LatestAsync<AetheriaRuntimeDaemonFrameDocument>()", StringComparison.Ordinal) ||
|
|
clientState.Contains("LatestAsync<AetheriaRuntimeDaemonHealthDocument>()", StringComparison.Ordinal) ||
|
|
clientState.Contains("LatestAsync<AetheriaRuntimeDaemonCommandBoundaryDocument>()", StringComparison.Ordinal) ||
|
|
clientState.Contains("Latest<AetheriaRuntimeCatalogSnapshot>()", StringComparison.Ordinal) ||
|
|
clientState.Contains("LatestCatalog)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose native managed typed documents instead of bootstrapping one-shot latest snapshots or scoped wrapper handles.");
|
|
}
|
|
|
|
if (!client.Contains("_aetheriaState?.Dispose()", StringComparison.Ordinal) ||
|
|
!client.Contains("_aetheriaState = null", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient must dispose AetheriaClientState so cached reactive state-ref resolver documents do not leak.");
|
|
}
|
|
|
|
var requiredStateRefResolverSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeStateRefResolver",
|
|
"TryResolveDaemonStateRef(",
|
|
"TryResolveDaemonItemStatRef("
|
|
};
|
|
var missingStateRefResolverSymbols = requiredStateRefResolverSymbols
|
|
.Where(symbol => !stateRefResolver.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingStateRefResolverSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime state-ref resolver is missing typed Eve state-ref symbols: " +
|
|
string.Join(", ", missingStateRefResolverSymbols));
|
|
}
|
|
|
|
if (stateRefResolver.Contains("public static class AetheriaRuntimeStateReader", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeStateReader compatibility wrapper still exists; use AetheriaRuntimeStateRefResolver and managed AetheriaClient documents.");
|
|
}
|
|
|
|
if (client.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceCultMeshStateRefResolver", StringComparison.Ordinal) ||
|
|
client.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceStateRefResolver", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient still routes Eve state-ref resolution through the file-backed compatibility reader.");
|
|
}
|
|
|
|
if (client.Contains("public Func<string, string> CreateEveSurfaceStateRefResolver()", StringComparison.Ordinal) ||
|
|
client.Contains("public CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient still exposes Eve state-ref resolver compatibility factories; use AetheriaClientState managed documents.");
|
|
}
|
|
|
|
if (client.Contains("GetObservedDaemonStateAsync()", StringComparison.Ordinal) ||
|
|
client.Contains("GetLatestAuthoritativeRunFrameAsync()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient still exposes daemon observation compatibility helpers; use AetheriaClient managed documents instead.");
|
|
}
|
|
|
|
if (stateRefResolver.Contains("TryReadDaemonRenderView", StringComparison.Ordinal) ||
|
|
stateRefResolver.Contains("TryReadDaemonSoaView(", StringComparison.Ordinal) ||
|
|
stateRefResolver.Contains("ResolveEveSurfaceStateRef(", StringComparison.Ordinal) ||
|
|
stateRefResolver.Contains("TryResolveEveSurfaceStateRef(", StringComparison.Ordinal) ||
|
|
stateRefResolver.Contains("CreateEveSurfaceCultMeshStateRefResolver(\r\n string stateFilePath", StringComparison.Ordinal) ||
|
|
stateRefResolver.Contains("CreateEveSurfaceCultMeshStateRefResolver(\n string stateFilePath", StringComparison.Ordinal) ||
|
|
stateRefResolver.Contains("ReadEveSurface(", StringComparison.Ordinal) ||
|
|
stateRefResolver.Contains("TryReadDaemonGameSurface(", StringComparison.Ordinal) ||
|
|
stateRefResolver.Contains("TryReadDaemonGameTuiSurface(", StringComparison.Ordinal) ||
|
|
stateRefResolver.Contains("TryReadDaemonEditorSurface(", StringComparison.Ordinal) ||
|
|
stateRefResolver.Contains("TryReadDaemonEditorTuiSurface(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria runtime state-ref resolver still exposes daemon acquisition that belongs on managed AetheriaClient documents.");
|
|
}
|
|
|
|
var starbridgeSummaryStart = client.IndexOf(
|
|
"Task<AetheriaRuntimeStarbridgeSessionSummaryDocument> StarbridgeSummaryAsync",
|
|
StringComparison.Ordinal);
|
|
if (starbridgeSummaryStart < 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient no longer exposes the managed Starbridge summary document.");
|
|
}
|
|
|
|
var indexedDocumentStart = client.IndexOf("static string IndexedDocumentId", starbridgeSummaryStart, StringComparison.Ordinal);
|
|
var starbridgeSummaryBlock = indexedDocumentStart > starbridgeSummaryStart
|
|
? client.Substring(starbridgeSummaryStart, indexedDocumentStart - starbridgeSummaryStart)
|
|
: client.Substring(starbridgeSummaryStart);
|
|
if (!starbridgeSummaryBlock.Contains("RequireManagedCatalog()", StringComparison.Ordinal) ||
|
|
starbridgeSummaryBlock.Contains("catalogDocument.LatestAsync()", StringComparison.Ordinal) ||
|
|
starbridgeSummaryBlock.Contains("BootstrapRuntimeCatalog", StringComparison.Ordinal) ||
|
|
starbridgeSummaryBlock.Contains("AetheriaRuntimeCatalogStore.OpenReadOnly", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Starbridge summary document must use the managed runtime catalog document instead of reopening the catalog store.");
|
|
}
|
|
|
|
var requiredClientBootstrapSymbols = new[]
|
|
{
|
|
"BootstrapCatalogDocument(",
|
|
"CatalogBootstrapSource(",
|
|
"managed Aetheria document bootstrap seed",
|
|
"BootstrapRuntimeCatalog",
|
|
"BootstrapLoadoutTemplates",
|
|
"BootstrapPlayerSettings",
|
|
"BootstrapVerseHostSettings",
|
|
"return AetheriaRuntimeCatalogStore.OpenReadOnly(StatePath);",
|
|
"AetheriaRuntimeCatalogStore.ReadLoadoutTemplates(StatePath)",
|
|
"AetheriaRuntimeCatalogStore.ReadPlayerSettings(StatePath)",
|
|
"AetheriaRuntimeCatalogStore.ReadVerseHostSettings(StatePath)"
|
|
};
|
|
var missingClientBootstrapSymbols = requiredClientBootstrapSymbols
|
|
.Where(symbol => !client.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingClientBootstrapSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient must name catalog/settings file reads as bootstrap seeds for managed documents: " +
|
|
string.Join(", ", missingClientBootstrapSymbols));
|
|
}
|
|
|
|
var forbiddenClientCompatibilityStoreBypasses = new[]
|
|
{
|
|
"return Task.FromResult(AetheriaRuntimeCatalogStore.ReadPlayerSettings(StatePath));",
|
|
"return Task.FromResult(AetheriaRuntimeCatalogStore.ReadVerseHostSettings(StatePath));",
|
|
"public static class AetheriaRuntimeBootstrapDocuments",
|
|
"public AetheriaRuntimeCatalogSnapshot ReadRuntimeCatalog(",
|
|
"public AetheriaRuntimeLoadoutTemplatesDocument ReadLoadoutTemplates(",
|
|
"public AetheriaRuntimePlayerSettingsDocument ReadPlayerSettings(",
|
|
"public AetheriaRuntimeVerseHostSettingsDocument ReadVerseHostSettings(",
|
|
"public Task<AetheriaRuntimeCatalogSnapshot> ReadRuntimeCatalogAsync(",
|
|
"public Task<AetheriaRuntimeLoadoutTemplatesDocument> ReadLoadoutTemplatesAsync(",
|
|
"public Task<AetheriaRuntimePlayerSettingsDocument> ReadPlayerSettingsAsync(",
|
|
"public Task<AetheriaRuntimeVerseHostSettingsDocument> ReadVerseHostSettingsAsync(",
|
|
"var catalogDocument = CatalogDocument(",
|
|
"var loadoutTemplatesDocument = CatalogDocument(",
|
|
"CultMeshDocumentHandle<TDocument> CatalogDocument<TDocument>(",
|
|
"projected Aetheria catalog document",
|
|
"Aetheria typed catalog state",
|
|
"public async Task<AetheriaRuntimeDaemonProviderAdvertisementDocument?> GetProviderAdvertisementAsync()",
|
|
"public async Task<AetheriaRuntimeDaemonHealthDocument?> GetHealthAsync()",
|
|
"public async Task<AetheriaRuntimeDaemonCommandBoundaryDocument?> GetCommandBoundaryAsync()",
|
|
"public async Task<AetheriaRuntimeVerseAuthorityPolicyDocument?> GetVerseAuthorityPolicyAsync()",
|
|
"public async Task<AetheriaRuntimeDaemonFrameDocument?> GetLatestFrameAsync()",
|
|
"public async Task<AetheriaRuntimeDaemonSoaViewDocument?> GetLatestSoaViewAsync()",
|
|
"public async Task<AetheriaRuntimeStarbridgeScenarioDocument?> GetStarbridgeScenarioAsync()",
|
|
"public async Task<AetheriaRuntimeStarbridgeSessionDocument?> GetStarbridgeSessionAsync()",
|
|
"public async Task<AetheriaRuntimeStarbridgePlayerSeatDocument?> GetStarbridgePlayerSeatAsync(string seatId)",
|
|
"public async Task PutStarbridgeScenarioAsync(",
|
|
"public async Task PutStarbridgeSessionAsync(",
|
|
"public async Task PutStarbridgePlayerSeatAsync(",
|
|
"public async Task<EveSurfaceDocument?> GetDaemonGameSurfaceAsync()",
|
|
"public async Task<EveSurfaceDocument?> GetDaemonGameTuiSurfaceAsync()",
|
|
"public async Task<EveSurfaceDocument?> GetDaemonEditorSurfaceAsync()",
|
|
"public async Task<EveSurfaceDocument?> GetDaemonEditorTuiSurfaceAsync()",
|
|
"public AetheriaRuntimeCatalogSnapshot OpenRuntimeCatalog()",
|
|
"public async Task<AetheriaRuntimePlayerSettingsSnapshot?> GetPlayerSettingsAsync()",
|
|
"public async Task<AetheriaRuntimeVerseHostSettingsSnapshot?> GetVerseHostSettingsAsync()",
|
|
"() => Task.FromResult(OpenRuntimeCatalog())",
|
|
"OpenRuntimeCatalog());",
|
|
"var frame = await GetLatestFrameAsync().ConfigureAwait(false);",
|
|
"var soaView = await GetLatestSoaViewAsync().ConfigureAwait(false);",
|
|
"var frameTask = GetLatestFrameAsync();",
|
|
"var healthTask = GetHealthAsync();",
|
|
"var commandBoundaryTask = GetCommandBoundaryAsync();",
|
|
"return Database.GetAsync<AetheriaRuntimeDaemonProviderAdvertisementDocument>",
|
|
"return Database.GetAsync<AetheriaRuntimeDaemonHealthDocument>",
|
|
"return Database.GetAsync<AetheriaRuntimeDaemonCommandBoundaryDocument>",
|
|
"return Database.GetAsync<AetheriaRuntimeVerseAuthorityPolicyDocument>",
|
|
"return Database.GetAsync<AetheriaRuntimeDaemonFrameDocument>",
|
|
"return Database.GetAsync<AetheriaRuntimeDaemonSoaViewDocument>",
|
|
"return Database.GetAsync<AetheriaRuntimeStarbridgeScenarioDocument>",
|
|
"return Database.GetAsync<AetheriaRuntimeStarbridgeSessionDocument>",
|
|
"return Database.GetAsync<AetheriaRuntimeStarbridgePlayerSeatDocument>",
|
|
"return Database.GetAsync<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface);",
|
|
"return Database.GetAsync<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface);",
|
|
"return Database.GetAsync<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface);",
|
|
"return Database.GetAsync<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface);"
|
|
};
|
|
var clientCompatibilityStoreBypassHits = forbiddenClientCompatibilityStoreBypasses
|
|
.Where(symbol => client.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (clientCompatibilityStoreBypassHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient compatibility reads still bypass managed typed documents: " +
|
|
string.Join(", ", clientCompatibilityStoreBypassHits));
|
|
}
|
|
|
|
if (client.Contains("public async Task<AetheriaRuntimeDaemonCommandEnvelope> SubmitDaemonCommandAsync(", StringComparison.Ordinal) ||
|
|
client.Contains("public async Task<AetheriaRuntimeEveCommandEnvelope> SubmitEveCommandAsync(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient exposes raw command-envelope submission publicly; clients should use AetheriaClient.Control and AetheriaClient.Ui.");
|
|
}
|
|
|
|
if (client.Contains("AetheriaRuntimeVerseDocument", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeVerseClient reintroduced a private document handle; use shared CultMeshMutableStatePointer<T> instead.");
|
|
}
|
|
|
|
var deletedCommandPortPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCommandPort.cs");
|
|
if (File.Exists(deletedCommandPortPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeCommandPort.cs has returned. Submit typed commands through AetheriaRuntimeVerseClient.");
|
|
}
|
|
|
|
var requiredDocSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeVerseClient",
|
|
"WatchRecord<T>(CultRecordKey)",
|
|
"MutableDocument<T>(CultRecordKey)",
|
|
"Unity",
|
|
"Verse records",
|
|
"EveSurfaceDocument",
|
|
"same typed records"
|
|
};
|
|
var missingDocSymbols = requiredDocSymbols
|
|
.Where(symbol => !doc.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDocSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Verse client contract note is missing required architecture terms: " +
|
|
string.Join(", ", missingDocSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedEveCommandBodies(string root)
|
|
{
|
|
var eveCommandDocumentPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveCommandDocument.cs");
|
|
var eveCommandClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveCommandClient.cs");
|
|
var verseClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseClient.cs");
|
|
var aetheriaClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClient.cs");
|
|
var aetheriaUiPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaUi.cs");
|
|
var runtimeCommandPortPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCommandPort.cs");
|
|
var eveCommandBridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs");
|
|
var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs");
|
|
var documentRegistryPath = Path.Combine(root, "Aetheria.State", "AetheriaDocumentRegistry.cs");
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
var evePresenterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveSurfacePresenter.cs");
|
|
|
|
var requiredFiles = new[] { eveCommandDocumentPath, eveCommandClientPath, verseClientPath, aetheriaClientPath, aetheriaUiPath, eveCommandBridgePath, stateNodePath, documentRegistryPath, actionGameManagerPath, mainMenuPath, evePresenterPath };
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Typed Eve command bodies cannot be verified because required files are missing: " +
|
|
string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var eveCommandDocument = File.ReadAllText(eveCommandDocumentPath);
|
|
var eveCommandClient = File.ReadAllText(eveCommandClientPath);
|
|
var verseClient = File.ReadAllText(verseClientPath);
|
|
var aetheriaClient = File.ReadAllText(aetheriaClientPath);
|
|
var aetheriaUi = File.ReadAllText(aetheriaUiPath);
|
|
var normalizedEveCommandClient = eveCommandClient.Replace("\r\n", "\n", StringComparison.Ordinal);
|
|
var eveCommandBridge = File.ReadAllText(eveCommandBridgePath);
|
|
var stateNode = File.ReadAllText(stateNodePath);
|
|
var documentRegistry = File.ReadAllText(documentRegistryPath);
|
|
var actionGameManager = File.ReadAllText(actionGameManagerPath);
|
|
var evePresenter = File.ReadAllText(evePresenterPath);
|
|
|
|
var requiredDocumentSymbols = new[]
|
|
{
|
|
"public enum AetheriaRuntimeEveCommandKind",
|
|
"public sealed class AetheriaRuntimePlayerSettingsCommandBody",
|
|
"public sealed class AetheriaRuntimeInputSettingsCommandBody",
|
|
"public sealed class AetheriaRuntimeTradeValuePolicyCommandBody",
|
|
"public AetheriaRuntimeEveCommandKind Kind",
|
|
"public AetheriaRuntimePlayerSettingsCommandBody PlayerSettings",
|
|
"public AetheriaRuntimeInputSettingsCommandBody InputSettings",
|
|
"public AetheriaRuntimeLoadoutTemplateCommit? LoadoutTemplate",
|
|
"public AetheriaRuntimeTradeValuePolicyCommandBody TradeValuePolicy",
|
|
"public CultMeshOperationInvocationDescriptor Invocation",
|
|
"public CultMeshOperationPayload Payload",
|
|
"public CultMeshOperationInvocationRecord Operation",
|
|
"public Dictionary<string, string> Payload",
|
|
"SetTradeValueQualityMinimum",
|
|
"SetTradeValueTierQuality",
|
|
"[CultDocument(\"gamecult.eve.command\", \"gamecult.eve.command.v1\")]"
|
|
};
|
|
var missingDocumentSymbols = requiredDocumentSymbols
|
|
.Where(symbol => !eveCommandDocument.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve submitted command document is missing typed command bodies: " +
|
|
string.Join(", ", missingDocumentSymbols));
|
|
}
|
|
|
|
var forbiddenDocumentSymbols = new[]
|
|
{
|
|
"IReadOnlyDictionary<string, string> Payload",
|
|
"SubmitSurfaceRequest(",
|
|
"CreateSurfaceRequestCommand(",
|
|
"public string OperationSchemaId",
|
|
"public string OperationRouteKind",
|
|
"public string OperationRouteDescription",
|
|
"public string OperationIdempotencyKey",
|
|
"[Key(11)]",
|
|
"[Key(12)]",
|
|
"[Key(13)]",
|
|
"[Key(14)]",
|
|
"[Key(15)]"
|
|
};
|
|
var forbiddenDocumentHits = forbiddenDocumentSymbols
|
|
.Where(symbol => eveCommandDocument.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenDocumentHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve submitted command document still persists string payload maps: " +
|
|
string.Join(", ", forbiddenDocumentHits));
|
|
}
|
|
|
|
var requiredTypedCommandSymbols = new[]
|
|
{
|
|
"internal static class AetheriaRuntimeEveCommands",
|
|
"public static class AetheriaRuntimeEveCommandClient",
|
|
"namespace GameCult.Aetheria.State.Verse",
|
|
"SurfaceCommandAsync(",
|
|
"internal async Task<AetheriaRuntimeDaemonCommandEnvelope> SubmitDaemonCommandAsync(",
|
|
"internal async Task<AetheriaRuntimeEveCommandEnvelope> SubmitEveCommandAsync(",
|
|
"CreatePlayerSettingsCommand(",
|
|
"CreateInputSettingsCommand(",
|
|
"CreateCatalogCommand(",
|
|
"CreateOperationsCommand(",
|
|
"CreateVerseHostCommand(",
|
|
"CreateLoadoutTemplateCommand(",
|
|
"CreateTradeValuePolicyCommand(",
|
|
"ReadTradeValuePolicyBody(",
|
|
"var command = OperationIdFor(request);",
|
|
"return CommandKindForSurface(request.SurfaceId ?? \"\", OperationIdFor(request));",
|
|
"private static string OperationIdFor(EveSurfaceCommandRequest request)",
|
|
"return request?.Operation?.OperationId ?? \"\";",
|
|
"CultMeshOperationInvocationDescriptor",
|
|
"CultMeshOperationPayload",
|
|
"invocation: request.Operation",
|
|
"payload: request.Payload",
|
|
"CreateInvocation(",
|
|
"CultMesh.OperationInvocationRecord(",
|
|
"Operation = invocation",
|
|
"Operation = invocationRecord",
|
|
"NormalizeInvocationRecord(",
|
|
"Payload = envelope.Payload.ToDictionary()",
|
|
"Payload = (payload ?? CultMeshOperationPayload.Empty).ToDictionary()",
|
|
"return request.Payload.GetString(key);",
|
|
"return request.Payload.GetInt32(key, defaultValue);",
|
|
"return request.Payload.GetDouble(key, defaultValue);",
|
|
"TrySendPlayerSettingsCommand(",
|
|
"TrySendInputSettingsCommand(",
|
|
"TrySendVerseHostCommand(",
|
|
"TrySendLoadoutTemplateCommand(",
|
|
"TrySendKnownSurfaceCommand(",
|
|
"TryCreateKnownSurfaceCommand(",
|
|
"CreateVerseHostCommand(CommandKindForSurface(request), clientId)",
|
|
"CommandKindForSurface(",
|
|
"CommandText(",
|
|
"ToDocument(",
|
|
"AcceptObservedAsync(",
|
|
"switch (command.Kind)",
|
|
"node.Documents<AetheriaRuntimeEveCommandDocument>()",
|
|
"AetheriaRuntimeEveCommandClient.NormalizeDocument",
|
|
"AccountedCommandIds",
|
|
"SubmitEveCommandAsync(",
|
|
"EveCommandKey(",
|
|
"typeof(AetheriaRuntimeEveCommandDocument)",
|
|
"SubmitPlayerSettingsCommand(",
|
|
"command.PlayerSettings.PlayerName",
|
|
"command.InputSettings.ActionName",
|
|
"command.InputSettings.InputSystemPath",
|
|
"command.TradeValuePolicy.Value",
|
|
"command.TradeValuePolicy.TierIndex",
|
|
"ExecuteTradeValuePolicyCommandAsync(",
|
|
"MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey)",
|
|
"SubmitInputSettingsCommandAsync(",
|
|
"SubmitLoadoutTemplateCommandAsync(",
|
|
"SubmitKnownSurfaceCommandAsync(",
|
|
"public AetheriaUi Ui => _ui",
|
|
"public sealed class AetheriaUi",
|
|
"Task<CultMeshOperationReceipt> InputSettingsAsync(",
|
|
"Task<CultMeshOperationReceipt> SaveLoadoutTemplateAsync(",
|
|
"Task<CultMeshOperationReceipt> SurfaceCommandAsync("
|
|
};
|
|
var mainMenu = File.ReadAllText(mainMenuPath);
|
|
var typedCommandSources = eveCommandClient + "\n" + verseClient + "\n" + aetheriaClient + "\n" + aetheriaUi + "\n" + eveCommandBridge + "\n" + stateNode + "\n" + documentRegistry + "\n" + actionGameManager + "\n" + mainMenu + "\n" + evePresenter;
|
|
if (aetheriaClient.Contains("public Task<AetheriaRuntimeEveCommandEnvelope> SubmitInputSettingsCommandAsync(", StringComparison.Ordinal) ||
|
|
aetheriaClient.Contains("public Task<AetheriaRuntimeEveCommandEnvelope> SubmitLoadoutTemplateCommandAsync(", StringComparison.Ordinal) ||
|
|
aetheriaClient.Contains("public Task<AetheriaRuntimeEveCommandEnvelope> SubmitKnownSurfaceCommandAsync(", StringComparison.Ordinal) ||
|
|
verseClient.Contains("public Task<AetheriaRuntimeEveCommandEnvelope> SubmitInputSettingsCommandAsync(", StringComparison.Ordinal) ||
|
|
verseClient.Contains("public Task<AetheriaRuntimeEveCommandEnvelope> SubmitLoadoutTemplateCommandAsync(", StringComparison.Ordinal) ||
|
|
verseClient.Contains("public Task<AetheriaRuntimeEveCommandEnvelope> SubmitKnownSurfaceCommandAsync(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClient exposes Eve command envelopes publicly; UI callers should use AetheriaClient.Ui and receive CultMeshOperationReceipt.");
|
|
}
|
|
|
|
if (eveCommandClient.Contains("ParseRouteKind(", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("OperationId = envelope.Invocation.OperationId", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("OperationId = invocation.OperationId", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("OperationId = invocationRecord.OperationId", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("ApplyInvocationCompatibilityFields(", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("document.OperationId", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("document.OperationSchemaId", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("document.OperationRouteKind", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("document.OperationRouteDescription", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("document.OperationIdempotencyKey", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("Payload = new System.Collections.Generic.Dictionary<string, string>(\r\n envelope.Payload", StringComparison.Ordinal) ||
|
|
eveCommandClient.Contains("Payload = new System.Collections.Generic.Dictionary<string, string>(\n envelope.Payload", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve command persistence reintroduced local CultMesh invocation/payload flattening; use CultMesh.OperationInvocationRecord and CultMeshOperationPayload.ToDictionary.");
|
|
}
|
|
|
|
if (eveCommandClient.Contains("public static class AetheriaRuntimeEveCommands", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeEveCommands is public again; public Eve UI operations belong on AetheriaClient.Ui.");
|
|
}
|
|
|
|
if (typedCommandSources.Contains("DeleteEveCommandAsync(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve command documents are still consumed by deletion instead of accounted by acceptance receipts.");
|
|
}
|
|
|
|
if (eveCommandBridge.Contains("AcceptedPaths", StringComparison.Ordinal) ||
|
|
eveCommandBridge.Contains("RejectedPaths", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve command acceptance report still exposes path vocabulary instead of command-id receipts.");
|
|
}
|
|
|
|
if (typedCommandSources.Contains("TradeValuePolicy()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve command handling still uses the named trade-value policy helper instead of generic mutable typed document access.");
|
|
}
|
|
|
|
var missingTypedCommandSymbols = requiredTypedCommandSymbols
|
|
.Where(symbol => !typedCommandSources.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTypedCommandSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve command append/apply path is missing typed command body usage: " +
|
|
string.Join(", ", missingTypedCommandSymbols));
|
|
}
|
|
|
|
var forbiddenUnityEveCommandSubmitSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeEveCommands.TrySendInputSettingsCommand",
|
|
"AetheriaRuntimeEveCommands.TrySendLoadoutTemplateCommand",
|
|
"AetheriaRuntimeEveCommands.TrySendKnownSurfaceCommand"
|
|
};
|
|
var unityCommandSources = new Dictionary<string, string>
|
|
{
|
|
[actionGameManagerPath] = actionGameManager,
|
|
[mainMenuPath] = mainMenu,
|
|
[evePresenterPath] = evePresenter
|
|
};
|
|
var legacyUnityCommandHits = unityCommandSources
|
|
.SelectMany(entry => forbiddenUnityEveCommandSubmitSymbols
|
|
.Where(symbol => entry.Value.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, entry.Key)} -> {symbol}"))
|
|
.ToArray();
|
|
if (legacyUnityCommandHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity-facing shells still submit Eve commands through legacy helper ports instead of AetheriaRuntimeVerseClient: " +
|
|
string.Join("; ", legacyUnityCommandHits));
|
|
}
|
|
|
|
var unityNamedRuntimeSources = new Dictionary<string, string>
|
|
{
|
|
["Aetheria.State.Daemon"] = File.Exists(Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"))
|
|
? File.ReadAllText(Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"))
|
|
: "",
|
|
["Aetheria.State"] = string.Join(
|
|
"\n",
|
|
Directory.EnumerateFiles(Path.Combine(root, "Aetheria.State"), "*.cs", SearchOption.AllDirectories)
|
|
.Select(File.ReadAllText)),
|
|
["org.gamecult.aetheria.state/Runtime"] = string.Join(
|
|
"\n",
|
|
Directory.EnumerateFiles(
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime"),
|
|
"*.cs",
|
|
SearchOption.TopDirectoryOnly)
|
|
.Select(File.ReadAllText))
|
|
};
|
|
var unityNamedRuntimeHits = unityNamedRuntimeSources
|
|
.Where(source => source.Value.Contains("GameCult.Aetheria.State.Unity", StringComparison.Ordinal))
|
|
.Select(source => source.Key)
|
|
.ToArray();
|
|
if (unityNamedRuntimeHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon/shared Aetheria runtime contracts still live under a Unity-named API namespace: " +
|
|
string.Join(", ", unityNamedRuntimeHits));
|
|
}
|
|
|
|
if (File.Exists(runtimeCommandPortPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeCommandPort.cs has returned. Submit typed command documents through AetheriaRuntimeVerseClient.");
|
|
}
|
|
|
|
var forbiddenSharedCommandRuntimeIds = new[]
|
|
{
|
|
"string runtimeId = \"unity-input-provider\"",
|
|
"? \"unity-input-provider\" : runtimeId"
|
|
};
|
|
var sharedCommandRuntimeIdHits = forbiddenSharedCommandRuntimeIds
|
|
.Where(symbol => typedCommandSources.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (sharedCommandRuntimeIdHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared command runtime still uses Unity-specific fallback runtime ids: " +
|
|
string.Join(", ", sharedCommandRuntimeIdHits));
|
|
}
|
|
|
|
if (typedCommandSources.Contains("AetheriaRuntimeCommandSubmitter", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared command runtime still preserves the cached command submitter instead of routing typed clients through AetheriaRuntimeVerseClient.");
|
|
}
|
|
|
|
var forbiddenUnitySubmitterSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeCommandSubmitter.TrySubmitEveCommand(",
|
|
"AetheriaRuntimeCommandSubmitter.TrySubmitDaemonCommand(",
|
|
"AetheriaRuntimeEveCommandClient.ToDocument("
|
|
};
|
|
var unitySubmitterSources = new Dictionary<string, string>
|
|
{
|
|
[Path.GetRelativePath(root, actionGameManagerPath)] = actionGameManager,
|
|
[Path.GetRelativePath(root, mainMenuPath)] = mainMenu,
|
|
[Path.GetRelativePath(root, evePresenterPath)] = evePresenter
|
|
};
|
|
var unitySubmitterHits = unitySubmitterSources
|
|
.SelectMany(pair => forbiddenUnitySubmitterSymbols
|
|
.Where(symbol => pair.Value.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{pair.Key}: {symbol}"))
|
|
.ToArray();
|
|
if (unitySubmitterHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity-facing Eve command code still assembles transport documents instead of using typed send helpers: " +
|
|
string.Join(", ", unitySubmitterHits));
|
|
}
|
|
|
|
var forbiddenGenericSurfaceCommandSymbols = new[]
|
|
{
|
|
"SubmitSurfaceRequest(",
|
|
"CreateSurfaceRequestCommand("
|
|
};
|
|
var genericSurfaceCommandHits = forbiddenGenericSurfaceCommandSymbols
|
|
.Where(symbol => typedCommandSources.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (genericSurfaceCommandHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve command edge still exposes a generic surface-command submission back door: " +
|
|
string.Join(", ", genericSurfaceCommandHits));
|
|
}
|
|
|
|
var forbiddenStringCommandOverloads = new[]
|
|
{
|
|
"SubmitPlayerSettingsCommand(\n string stateFilePath,\n string command",
|
|
"SubmitInputSettingsCommand(\n string stateFilePath,\n string command",
|
|
"SubmitCatalogCommand(\n string stateFilePath,\n string command",
|
|
"SubmitOperationsCommand(\n string stateFilePath,\n string command",
|
|
"SubmitVerseHostCommand(\n string stateFilePath,\n string command",
|
|
"TrySendInputSettingsCommand(\n string stateFilePath,\n string command",
|
|
"TrySendVerseHostCommand(\n string stateFilePath,\n string command",
|
|
"CreateCatalogCommand(\n string command",
|
|
"CreateOperationsCommand(\n string command",
|
|
"CreateVerseHostCommand(\n string command"
|
|
};
|
|
var survivingStringCommandOverloads = forbiddenStringCommandOverloads
|
|
.Where(symbol => normalizedEveCommandClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingStringCommandOverloads.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve command client still exposes public string command overloads instead of typed command kinds: " +
|
|
string.Join(", ", survivingStringCommandOverloads));
|
|
}
|
|
|
|
var forbiddenQueueSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeEveCommandLog",
|
|
"internal static class AetheriaRuntimeEveCommandLog",
|
|
"public static AetheriaRuntimeEveCommandEnvelope QueueCommand(",
|
|
"public static AetheriaRuntimeEveCommandEnvelope QueuePlayerSettingsCommand(",
|
|
"public static AetheriaRuntimeEveCommandEnvelope QueueInputSettingsCommand(",
|
|
"public static AetheriaRuntimeEveCommandEnvelope QueueCatalogCommand(",
|
|
"public static AetheriaRuntimeEveCommandEnvelope QueueOperationsCommand(",
|
|
"public static AetheriaRuntimeEveCommandEnvelope QueueVerseHostCommand(",
|
|
"public static AetheriaRuntimeEveCommandEnvelope QueueLoadoutTemplateCommand(",
|
|
"private static AetheriaRuntimeEveCommandEnvelope QueueTypedCommand(",
|
|
"AetheriaRuntimeEveCommandLog.",
|
|
"AetheriaRuntimeEveCommandLog.Queue",
|
|
"AetheriaRuntimeEveCommandInbox",
|
|
"GetInboxDirectory(",
|
|
"Read" + "Submitted(",
|
|
".eve.commands",
|
|
"ReadPending(",
|
|
".eve.pending"
|
|
};
|
|
var forbiddenQueueSources = new Dictionary<string, string>
|
|
{
|
|
["Eve command client"] = eveCommandClient,
|
|
["Eve command bridge"] = eveCommandBridge,
|
|
["Aetheria state node"] = stateNode,
|
|
["ActionGameManager"] = actionGameManager,
|
|
["MainMenu"] = mainMenu,
|
|
["Eve presenter"] = evePresenter
|
|
};
|
|
var survivingQueueSymbols = forbiddenQueueSources
|
|
.SelectMany(source => forbiddenQueueSymbols
|
|
.Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{source.Key}: {symbol}"))
|
|
.ToArray();
|
|
if (survivingQueueSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve command edge still exposes queue-shaped public API: " +
|
|
string.Join(", ", survivingQueueSymbols));
|
|
}
|
|
|
|
var rendererLogSources = new Dictionary<string, string>
|
|
{
|
|
["ActionGameManager"] = actionGameManager,
|
|
["MainMenu"] = mainMenu,
|
|
["Eve presenter"] = evePresenter
|
|
};
|
|
var rendererLogHits = rendererLogSources
|
|
.Where(source => source.Value.Contains("AetheriaRuntimeEveCommandLog.", StringComparison.Ordinal))
|
|
.Select(source => $"{source.Key}: AetheriaRuntimeEveCommandLog.")
|
|
.ToArray();
|
|
if (rendererLogHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Renderer code still speaks to the Eve mailbox log instead of the typed Verse client path: " +
|
|
string.Join(", ", rendererLogHits));
|
|
}
|
|
|
|
if (actionGameManager.Contains("TryQueueRuntimeEveCommand(", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("new EveSurfaceCommandRequest(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still manufactures generic Eve surface command payloads instead of typed Eve command bodies.");
|
|
}
|
|
}
|
|
|
|
static void RequireMainMenuVerseHostDocumentAccess(string root)
|
|
{
|
|
var packageSnapshotPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogSnapshot.cs");
|
|
var packageStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs");
|
|
var runtimeStateRefResolverPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateRefResolver.cs");
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
var mainMenuSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeMainMenuSurfaceBuilder.cs");
|
|
var clientTargetSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetSurfaceBuilder.cs");
|
|
|
|
var requiredFiles = new[]
|
|
{
|
|
packageSnapshotPath,
|
|
packageStorePath,
|
|
runtimeStateRefResolverPath,
|
|
mainMenuPath,
|
|
mainMenuSurfaceBuilderPath,
|
|
clientTargetSurfaceBuilderPath
|
|
};
|
|
|
|
var missingFiles = requiredFiles
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Main-menu Verse document access cannot be verified because required files are missing: " +
|
|
string.Join(", ", missingFiles));
|
|
}
|
|
|
|
var packageSnapshot = File.ReadAllText(packageSnapshotPath);
|
|
var packageStore = File.ReadAllText(packageStorePath);
|
|
var runtimeStateRefResolver = File.ReadAllText(runtimeStateRefResolverPath);
|
|
var mainMenu = File.ReadAllText(mainMenuPath);
|
|
var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath);
|
|
var clientTargetSurfaceBuilder = File.ReadAllText(clientTargetSurfaceBuilderPath);
|
|
|
|
var requiredSnapshotSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaRuntimeVerseHostSettingsSnapshot",
|
|
"public string VerseId",
|
|
"public string CultMeshAddress",
|
|
"public string Visibility"
|
|
};
|
|
var missingSnapshotSymbols = requiredSnapshotSymbols
|
|
.Where(symbol => !packageSnapshot.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSnapshotSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime snapshot does not expose typed verse-host settings: " +
|
|
string.Join(", ", missingSnapshotSymbols));
|
|
}
|
|
|
|
var requiredStoreSymbols = new[]
|
|
{
|
|
"VerseHostSettingsSchema = \"aetheria.verse_host_settings\"",
|
|
"VerseHostSettingsKey = \"global:aetheria.verse_host_settings.v1\"",
|
|
"ReadVerseHostSettings(string stateFilePath)",
|
|
"ReadVerseHostSettingsPayload",
|
|
"AetheriaRuntimeVerseHostSettingsSnapshot"
|
|
};
|
|
var missingStoreSymbols = requiredStoreSymbols
|
|
.Where(symbol => !packageStore.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingStoreSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime store does not preserve typed verse-host read authority: " +
|
|
string.Join(", ", missingStoreSymbols));
|
|
}
|
|
|
|
var requiredMainMenuSymbols = new[]
|
|
{
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
".RuntimeState(stateBoot, \"unity-main-menu\")",
|
|
".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)",
|
|
".Latest()",
|
|
"ResolveMainMenuSurface("
|
|
};
|
|
var requiredBuilderSymbols = new[]
|
|
{
|
|
"public static AetheriaRuntimeSurfaceDocument BuildRoot(",
|
|
"AetheriaRuntimeStateBootReport stateBoot",
|
|
"AetheriaRuntimeVerseHostSettingsDocument verseHost",
|
|
"GravitySurface($",
|
|
"AetheriaRuntimeDaemonSchemas.RenderSplatsViewport",
|
|
"AetheriaRuntimeDaemonSchemas.GravityViewport",
|
|
"AetheriaRuntimeDaemonSchemas.ObjectsViewport",
|
|
"new AetheriaRuntimeEmbeddedDocumentSlot(",
|
|
"\"AETHERIA\"",
|
|
"\"STARBRIDGE\"",
|
|
"\"Aetheria Starbridge\""
|
|
};
|
|
var requiredClientTargetBuilderSymbols = new[]
|
|
{
|
|
"Build(",
|
|
"AetheriaRuntimeStateBootReport stateBoot",
|
|
"\"Transport\"",
|
|
"\"Target Source\"",
|
|
"\"Verse\"",
|
|
"\"Visibility\"",
|
|
"\"CultMesh\"",
|
|
"Client target edits persist through the managed target document. Visibility changes append provider-owned Eve requests against the selected Verse target."
|
|
};
|
|
var missingMainMenuSymbols = requiredMainMenuSymbols
|
|
.Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingMainMenuSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu no longer lowers daemon-owned Verse identity through its Eve shell: " +
|
|
string.Join(", ", missingMainMenuSymbols));
|
|
}
|
|
if (mainMenu.Contains(".ReactiveVerseHostSettings(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("CultMeshReactiveDocument<AetheriaRuntimeVerseHostSettingsDocument>", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu should sample daemon-owned Verse identity through the named current typed document.");
|
|
}
|
|
var missingBuilderSymbols = requiredBuilderSymbols
|
|
.Where(symbol => !mainMenuSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
var missingClientTargetBuilderSymbols = requiredClientTargetBuilderSymbols
|
|
.Where(symbol => !clientTargetSurfaceBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared main-menu surface builder no longer lowers daemon-owned Verse identity through its Eve shell: " +
|
|
string.Join(", ", missingBuilderSymbols));
|
|
}
|
|
if (missingClientTargetBuilderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Client-target surface builder no longer owns the typed Verse-host document merge: " +
|
|
string.Join(", ", missingClientTargetBuilderSymbols));
|
|
}
|
|
|
|
if (mainMenu.Contains("AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("AetheriaRuntimeClientTargetSurfaceBuilder.Build(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("AetheriaRuntimeMainMenuSurfaceBuilder.ProjectVerseSettings(", StringComparison.Ordinal) ||
|
|
mainMenuSurfaceBuilder.Contains("ProjectVerseSettings(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("new AetheriaRuntimeClientTargetSurfaceState(", StringComparison.Ordinal) ||
|
|
mainMenuSurfaceBuilder.Contains("new AetheriaRuntimeClientTargetSurfaceState(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity MainMenu must consume daemon-published main-menu surfaces instead of reintroducing menu-local client-target state shaping.");
|
|
}
|
|
}
|
|
|
|
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 runtimeStateRefResolverPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeStateRefResolver.cs");
|
|
var runtimeStateRefResolver = File.Exists(runtimeStateRefResolverPath)
|
|
? File.ReadAllText(runtimeStateRefResolverPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue entity readback; shared runtime state-ref resolver is 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 payload readback; package runtime store is missing.");
|
|
var deletedObservedDockingIndexPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedDockingIndex.cs");
|
|
if (File.Exists(deletedObservedDockingIndexPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityObservedDockingIndex is dead state-access chaff; read current typed docking/refit documents directly and adapt Unity objects at the caller edge.");
|
|
}
|
|
var currentEntityPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityCurrentEntityPresentation.cs");
|
|
var currentEntityPresentation = File.Exists(currentEntityPresentationPath)
|
|
? File.ReadAllText(currentEntityPresentationPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue entity presentation; AetheriaUnityCurrentEntityPresentation.cs is missing.");
|
|
var currentEntityBinderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityCurrentEntityBinder.cs");
|
|
var currentEntityBinder = File.Exists(currentEntityBinderPath)
|
|
? File.ReadAllText(currentEntityBinderPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue current entity binder; AetheriaUnityCurrentEntityBinder.cs is missing.");
|
|
var targetPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityTargetPresentation.cs");
|
|
var targetPresentation = File.Exists(targetPresentationPath)
|
|
? File.ReadAllText(targetPresentationPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue target presentation; AetheriaUnityTargetPresentation.cs is missing.");
|
|
var deletedObservedTargetQueryPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedTargetQuery.cs");
|
|
if (File.Exists(deletedObservedTargetQueryPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityObservedTargetQuery is dead contact-query chaff; target presentation should read typed zone contacts directly.");
|
|
}
|
|
var observedFrameApplierPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedFrameApplier.cs");
|
|
var observedFrameApplier = File.Exists(observedFrameApplierPath)
|
|
? File.ReadAllText(observedFrameApplierPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue frame application; AetheriaUnityObservedFrameApplier.cs is missing.");
|
|
var pilotFrameControllerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotFrameController.cs");
|
|
var pilotFrameController = File.Exists(pilotFrameControllerPath)
|
|
? File.ReadAllText(pilotFrameControllerPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue pilot frame controller; AetheriaUnityPilotFrameController.cs is missing.");
|
|
var legacyUnityDaemonEntitySnapshotProjectorPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityDaemonEntitySnapshotProjector.cs");
|
|
if (File.Exists(legacyUnityDaemonEntitySnapshotProjectorPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity still owns daemon entity snapshot projection; package runtime should lower typed daemon entity snapshots.");
|
|
}
|
|
|
|
var zoneRenderDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeGameViewportDocuments.cs");
|
|
var zoneRenderDocuments = File.Exists(zoneRenderDocumentsPath)
|
|
? File.ReadAllText(zoneRenderDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue entity snapshot access; AetheriaRuntimeGameViewportDocuments.cs is missing.");
|
|
var daemonEntitySnapshotProjectorPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEntitySnapshotProjector.cs");
|
|
if (File.Exists(daemonEntitySnapshotProjectorPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeEntitySnapshotProjector is dead access chaff; AetheriaRuntimeZoneRenderDocument owns managed entity snapshot access.");
|
|
}
|
|
var observedEntityRestorerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedEntityRestorer.cs");
|
|
var observedEntityRestorer = File.Exists(observedEntityRestorerPath)
|
|
? File.ReadAllText(observedEntityRestorerPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue entity projection; AetheriaUnityObservedEntityRestorer.cs is missing.");
|
|
var entityBlueprintMaterializerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityEntityBlueprintMaterializer.cs");
|
|
var entityBlueprintMaterializer = File.Exists(entityBlueprintMaterializerPath)
|
|
? File.ReadAllText(entityBlueprintMaterializerPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue entity construction materialization; AetheriaUnityEntityBlueprintMaterializer.cs is missing.");
|
|
var observedZoneContextFactoryPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedZoneContextFactory.cs");
|
|
var observedZoneContextFactory = File.Exists(observedZoneContextFactoryPath)
|
|
? File.ReadAllText(observedZoneContextFactoryPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue zone context projection; AetheriaUnityObservedZoneContextFactory.cs is missing.");
|
|
var gameplaySceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs");
|
|
var gameplaySceneWiring = File.Exists(gameplaySceneWiringPath)
|
|
? File.ReadAllText(gameplaySceneWiringPath)
|
|
: throw new InvalidOperationException("Cannot verify Continue scene wiring; AetheriaUnityGameplaySceneWiring.cs is missing.");
|
|
|
|
var requiredMenuSymbols = new[]
|
|
{
|
|
"ResolveSectorMap",
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
".RuntimeState(stateBoot, \"unity-main-menu\")",
|
|
".SectorMap",
|
|
".Latest()",
|
|
"ContinueGame()",
|
|
"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 an authoritative daemon frame: " +
|
|
string.Join(", ", missingMenuSymbols));
|
|
}
|
|
|
|
if (mainMenu.Contains("PrepareDocumentAccess(stateBoot)", StringComparison.Ordinal) ||
|
|
mainMenu.Contains(".ReactiveSectorMap(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("CultMeshReactiveDocument<AetheriaRuntimeSectorMapDocument>", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu Continue should sample the typed sector map through named current state access.");
|
|
}
|
|
|
|
if (mainMenu.Contains("AddButton(\"Continue\", null)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("MainMenu Continue regressed to a null button.");
|
|
}
|
|
|
|
var requiredGameplaySymbols = new[]
|
|
{
|
|
"ObservedFrameApplier.ApplyLatestZoneRender()",
|
|
"private AetheriaUnityGameplayLoopShell GameplayLoopShell =>",
|
|
"GameplayLoopShell.Tick(Time.deltaTime, Time.time)",
|
|
"GameplayLoopShell.LateTick()",
|
|
"ApplyLatestZoneRender = () => ObservedFrameApplier.ApplyLatestZoneRender()",
|
|
"private AetheriaUnityObservedFrameApplier ObservedFrameApplier =>",
|
|
"ObservedZoneContextFactory = new AetheriaUnityObservedZoneContextFactory(",
|
|
"boot.ObservedGalaxy",
|
|
"ObservedZoneContextFactory.ResolveGalaxyZone",
|
|
"private AetheriaUnityObservedZoneContextFactory ObservedZoneContextFactory { get; set; }",
|
|
"entity => CurrentEntityBinder.RestoreBinding(entity)",
|
|
"ResolveObservedTarget = entity => _targetPresentation.GetObservedTarget(entity)",
|
|
"TargetPresentation = _targetPresentation",
|
|
"private readonly AetheriaUnityPresentationEntityIndex _presentationEntityIndex",
|
|
"private AetheriaUnityEntityBlueprintMaterializer EntityBlueprintMaterializer =>",
|
|
"EntityBlueprintMaterializer.MaterializeObservedEntity",
|
|
"_loadoutItemFactory.CreateLoadoutItem",
|
|
"ResolveDockParent = ResolveDockParentFromCurrentDocking",
|
|
"CurrentDockingSnapshot()",
|
|
"private AetheriaClientState _gameplayState;",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-action-game-manager\")",
|
|
"GameplayState.CurrentDocking.Latest()",
|
|
"IsCurrentEntityObservedUndocked(",
|
|
"private AetheriaUnityCurrentEntityBinder CurrentEntityBinder =>",
|
|
"private readonly AetheriaUnityCurrentEntityPresentation _currentEntityPresentation",
|
|
"CurrentEntityPresentation = _currentEntityPresentation",
|
|
"private AetheriaUnityPilotFrameController PilotFrameController =>",
|
|
"PilotFrameController = PilotFrameController",
|
|
"SceneWiring.ConfigureCurrentEntityPresentation(_currentEntityPresentation, boot.RuntimeCatalog)",
|
|
"SceneWiring.ConfigureTargetPresentation("
|
|
};
|
|
|
|
if (actionGameManager.Contains("_authoritativeDaemonEntities", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity entity wrappers are observed daemon facades, not authoritative state; keep the daemon as authority.");
|
|
}
|
|
|
|
var forbiddenGameplayCatalogPolicySymbols = new[]
|
|
{
|
|
"private static AetheriaRuntimeCatalogSnapshot _runtimeCatalog",
|
|
"FindTypedRuntimeItem(",
|
|
"_runtimeCatalog?.FindItem(",
|
|
"ArticulatedWeaponBehaviorKinds",
|
|
"_targetPresentation.ResolveTypedRuntimeItem"
|
|
};
|
|
var gameplayCatalogPolicyHits = forbiddenGameplayCatalogPolicySymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (gameplayCatalogPolicyHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns runtime catalog presentation policy instead of injecting the boot catalog into Unity presentation helpers: " +
|
|
string.Join(", ", gameplayCatalogPolicyHits));
|
|
}
|
|
|
|
var requiredZoneRenderSnapshotSymbols = new[]
|
|
{
|
|
"public AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots()",
|
|
"public static AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots(",
|
|
"public static string EntityRecordKey(string runId, int zoneIndex, int entityIndex)",
|
|
"new AetheriaRuntimeEntitySnapshot(",
|
|
"entity.EntityIndex,",
|
|
"CreateEntityItemSlots(entity.Equipment)",
|
|
"CreateWeaponStates(runId, zoneIndex, entity.WeaponStates)",
|
|
"CreateBehaviorStates(entity.BehaviorStates)",
|
|
"CreateCargoBays(entity.CargoContents)",
|
|
"new AetheriaRuntimeEntityContactSnapshot("
|
|
};
|
|
var missingZoneRenderSnapshotSymbols = requiredZoneRenderSnapshotSymbols
|
|
.Where(symbol => !zoneRenderDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingZoneRenderSnapshotSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Managed daemon entity snapshot access must live on AetheriaRuntimeZoneRenderDocument instead of a projector-shaped helper: " +
|
|
string.Join(", ", missingZoneRenderSnapshotSymbols));
|
|
}
|
|
|
|
var forbiddenManagerSnapshotProjectionSymbols = new[]
|
|
{
|
|
"private static AetheriaRuntimeEntitySnapshot[] CreateDaemonEntitySnapshots",
|
|
"private static AetheriaRuntimeEntitySnapshot CreateDaemonEntitySnapshot",
|
|
"private static AetheriaRuntimeEntityItemSlotSnapshot[] CreateDaemonItemSlots",
|
|
"private static AetheriaRuntimeCargoBayLoadoutSnapshot[] CreateDaemonCargoBays",
|
|
"private static AetheriaRuntimeWeaponStateSnapshot[] CreateDaemonWeaponStates",
|
|
"private static AetheriaRuntimeBehaviorStateSnapshot[] CreateDaemonBehaviorStates",
|
|
"private static string DaemonEntityRecordKey(",
|
|
"private static int EntityIndexFromRecordKey("
|
|
};
|
|
var managerSnapshotProjectionHits = forbiddenManagerSnapshotProjectionSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerSnapshotProjectionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns daemon entity snapshot projection internals: " +
|
|
string.Join(", ", managerSnapshotProjectionHits));
|
|
}
|
|
|
|
var requiredObservedEntityRestorerSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityObservedEntityRestorer",
|
|
"public bool TryApplyInPlace(",
|
|
"public void Replace(",
|
|
"EntityConstructionBlueprintMaterializer.MaterializeObservedFromBlueprint(_itemManager, zone, blueprint)",
|
|
"entity.RestoreStatGrids(entitySnapshot.StatGrids)",
|
|
"entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia)",
|
|
"RestoreActiveConsumables(entity, entitySnapshot)",
|
|
"activeEffectIds.Add(activeConsumable.EffectId)",
|
|
"entity.RetainActiveConsumables(activeEffectIds)",
|
|
"RestoreRuntimeBehaviorState(entity, entitySnapshot, restoredEntities)",
|
|
"_entityIndex.PresentationEntitiesByRecordKey",
|
|
"ResolveRuntimeBehavior(entity, weaponState.OwnerKind, weaponState.OwnerIndex, weaponState.BehaviorIndex)",
|
|
"lockWeapon.RestoreRuntimeState(",
|
|
"drive.RestoreRuntimeState(",
|
|
"resourceScanner.RestoreRuntimeState(",
|
|
"_entityIndex.RefreshDaemonIndex();"
|
|
};
|
|
var missingObservedEntityRestorerSymbols = requiredObservedEntityRestorerSymbols
|
|
.Where(symbol => !observedEntityRestorer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingObservedEntityRestorerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed Unity entity mutation/restoration must live in AetheriaUnityObservedEntityRestorer instead of ActionGameManager: " +
|
|
string.Join(", ", missingObservedEntityRestorerSymbols));
|
|
}
|
|
|
|
var forbiddenManagerFacadeProjectionSymbols = new[]
|
|
{
|
|
"private bool CanApplyDaemonEntitySnapshotsInPlace(",
|
|
"private void ApplyDaemonEntitySnapshotsInPlace(",
|
|
"private void ReplaceObservedEntitySnapshotsFromTypedSnapshots(",
|
|
"private void RestoreActiveConsumablesFromTypedEntitySnapshot(",
|
|
"private void RestoreRuntimeBehaviorStateFromTypedSnapshot(",
|
|
"private static Behavior ResolveRuntimeBehavior(",
|
|
"private static IReadOnlyList<Behavior> ResolveRuntimeBehaviorList(",
|
|
"EntityConstructionBlueprintMaterializer.MaterializeObservedFromBlueprint(ItemManager, Zone, blueprint)",
|
|
"entity.RestoreStatGrids(entitySnapshot.StatGrids)",
|
|
"RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot)",
|
|
"RestoreRuntimeBehaviorStateFromTypedSnapshot(entity, entitySnapshot, restoredEntities)"
|
|
};
|
|
var managerFacadeProjectionHits = forbiddenManagerFacadeProjectionSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerFacadeProjectionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns observed entity mutation/restoration internals: " +
|
|
string.Join(", ", managerFacadeProjectionHits));
|
|
}
|
|
|
|
var requiredEntityBlueprintMaterializerSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityEntityBlueprintMaterializer",
|
|
"public EntityConstructionBlueprint MaterializeTemplate(AetheriaRuntimeLoadoutTemplateSnapshot template)",
|
|
"public EntityConstructionBlueprint MaterializeLoadoutEntity(AetheriaRuntimeEntityLoadoutSnapshot entity)",
|
|
"public EntityConstructionBlueprint MaterializeObservedEntity(",
|
|
"private static EntityConstructionBlueprint CreateBlueprint(string kind)",
|
|
"new OrbitalEntityConstructionBlueprint()",
|
|
"new ShipConstructionBlueprint()",
|
|
"shipBlueprint.Position = new float3((float)entity.PositionX",
|
|
"shipBlueprint.IsPlayerShip = isCurrentEntity",
|
|
"blueprint.Equipment = CreateEquippableSlots(entity.Equipment)",
|
|
"blueprint.CargoContents = CreateCargoBayContents(entity.CargoContents)",
|
|
"blueprint.Children = entity.Children",
|
|
"_loadoutItemFactory.CreateLoadoutItem(slot.Item)",
|
|
"_loadoutItemFactory.CreateLoadoutItem(item) as EquippableItem"
|
|
};
|
|
var missingEntityBlueprintMaterializerSymbols = requiredEntityBlueprintMaterializerSymbols
|
|
.Where(symbol => !entityBlueprintMaterializer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingEntityBlueprintMaterializerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity entity construction blueprint materialization must live in AetheriaUnityEntityBlueprintMaterializer instead of ActionGameManager: " +
|
|
string.Join(", ", missingEntityBlueprintMaterializerSymbols));
|
|
}
|
|
|
|
var forbiddenManagerEntityConstructionSymbols = new[]
|
|
{
|
|
"private EntityConstructionBlueprint CreateEntityConstructionBlueprint(",
|
|
"private (int2 position, EquippableItem item)[] CreateEquippableSlots(",
|
|
"private (int2 position, ItemInstance item)[][] CreateCargoBayContents(",
|
|
"private EquippableItem CreateEquippableLoadoutItem(",
|
|
"private ItemInstance CreateLoadoutItem(AetheriaRuntimeLoadoutItemSnapshot",
|
|
"new OrbitalEntityConstructionBlueprint()",
|
|
"new ShipConstructionBlueprint",
|
|
"blueprint.Children = entity.Children",
|
|
"ItemManager.CreateSimpleCommodityInstance(typedItem",
|
|
"ItemManager.CreateCraftedInstance(typedItem"
|
|
};
|
|
var managerEntityConstructionHits = forbiddenManagerEntityConstructionSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerEntityConstructionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns Unity entity construction blueprint lowering internals: " +
|
|
string.Join(", ", managerEntityConstructionHits));
|
|
}
|
|
|
|
var requiredObservedZoneContextFactorySymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityObservedZoneContextFactory",
|
|
"public Zone ResolveContext(",
|
|
"private readonly Dictionary<int, Zone> _observedZoneContextsByDaemonIndex",
|
|
"private static ZoneConstructionBlueprint CreateZoneConstructionBlueprint(",
|
|
"private static BodyConstructionData CreateBodyConstructionData(",
|
|
"private static GasGiantConstructionData CreateGasGiantConstructionData(",
|
|
"private static SunConstructionData CreateSunConstructionData(",
|
|
"private static void PopulateBodyConstructionData(",
|
|
"new Zone(_itemManager, _planetSettings, constructionBlueprint, galaxyZone, _observedGalaxy)",
|
|
"_playMusic(MusicType.Overworld);"
|
|
};
|
|
var missingObservedZoneContextFactorySymbols = requiredObservedZoneContextFactorySymbols
|
|
.Where(symbol => !observedZoneContextFactory.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingObservedZoneContextFactorySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed Unity zone context lowering must live in AetheriaUnityObservedZoneContextFactory instead of ActionGameManager: " +
|
|
string.Join(", ", missingObservedZoneContextFactorySymbols));
|
|
}
|
|
|
|
var forbiddenManagerZoneContextProjectionSymbols = new[]
|
|
{
|
|
"private void PrepareObservedDaemonZoneContext(",
|
|
"private static ZoneConstructionBlueprint CreateDaemonZoneConstructionBlueprint(",
|
|
"private static BodyConstructionData CreateDaemonBodyConstructionData(",
|
|
"private static GasGiantConstructionData CreateDaemonGasGiantConstructionData(",
|
|
"private static SunConstructionData CreateDaemonSunConstructionData(",
|
|
"private static void PopulateDaemonBodyConstructionData(",
|
|
"new Zone(ItemManager, Settings.PlanetSettings",
|
|
"private readonly Dictionary<int, Zone> _observedZoneContextsByDaemonIndex"
|
|
};
|
|
var managerZoneContextProjectionHits = forbiddenManagerZoneContextProjectionSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerZoneContextProjectionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns observed zone/body context lowering internals: " +
|
|
string.Join(", ", managerZoneContextProjectionHits));
|
|
}
|
|
|
|
var requiredObservedFrameApplierSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityObservedFrameApplier",
|
|
"public bool ApplyLatestZoneRender()",
|
|
"observer.LastRenderView?.ZoneRender",
|
|
"private bool TryRestoreEntityGraphFromZoneRender(",
|
|
"if (string.IsNullOrWhiteSpace(render.RunId))",
|
|
"Aetheria zone-render feed does not identify a run id.",
|
|
"render.CreateEntitySnapshots()",
|
|
".OrderBy(entity => entity.EntityIndex)",
|
|
"_entityRestorer.TryApplyInPlace(",
|
|
"zoneRenderer?.ApplyZoneRender(render)",
|
|
"_zoneContextFactory.ResolveContext(targetZone, render)",
|
|
"_entityRestorer.Replace(entitySnapshots, currentEntityKey, _getZone())",
|
|
"_restoreCurrentEntityBinding(currentEntity)",
|
|
"_entityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out var currentEntity)",
|
|
"_entityIndex.PresentationEntitiesByDaemonIndex",
|
|
"zoneRenderer?.LoadDaemonZoneView(_entityIndex.PresentationEntitiesByDaemonIndex, render)",
|
|
"zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render)"
|
|
};
|
|
var missingObservedFrameApplierSymbols = requiredObservedFrameApplierSymbols
|
|
.Where(symbol => !observedFrameApplier.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingObservedFrameApplierSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed Unity frame application must live in AetheriaUnityObservedFrameApplier instead of ActionGameManager: " +
|
|
string.Join(", ", missingObservedFrameApplierSymbols));
|
|
}
|
|
|
|
var forbiddenManagerFrameApplicationSymbols = new[]
|
|
{
|
|
"_lastAppliedAuthoritativeDaemonFrameId",
|
|
"_lastAppliedAuthoritativeDaemonFramePath",
|
|
"_lastAppliedAuthoritativeDaemonRunId",
|
|
"_lastAppliedAuthoritativeDaemonZoneIndex",
|
|
"private bool TryRestoreEntityGraphFromZoneRender(",
|
|
"AetheriaRuntimeGameDocuments.ZoneRender(observed.Frame)",
|
|
"AetheriaRuntimeEntitySnapshotProjector.CreateSnapshots(runId, daemonZone)",
|
|
"ObservedEntityRestorer.TryApplyInPlace(",
|
|
"ObservedEntityRestorer.Replace(entitySnapshots, currentEntityKey, Zone)",
|
|
"ZoneRenderer?.LoadDaemonZoneView(_presentationEntityIndex.PresentationEntitiesByDaemonIndex, render)",
|
|
"ZoneRenderer?.RestoreDroppedPickupsFromDaemonZoneState(daemonZone)",
|
|
"render.ActionBarBindings",
|
|
"private static AetheriaRuntimeActionBarBindingSnapshot ToActionBarBindingSnapshot("
|
|
};
|
|
var managerFrameApplicationHits = forbiddenManagerFrameApplicationSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerFrameApplicationHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns authoritative daemon frame application internals: " +
|
|
string.Join(", ", managerFrameApplicationHits));
|
|
}
|
|
|
|
var requiredTargetContactQuerySymbols = new[]
|
|
{
|
|
"public Func<AetheriaRuntimeZoneContactsDocument> ResolveZoneContacts { get; set; }",
|
|
"public Entity GetObservedTarget(Entity observer)",
|
|
"private float GetObservedInfoGathered(Entity observer, Entity target)",
|
|
"private bool IsObservedHostileContact(Entity observer, Entity target)",
|
|
"private AetheriaRuntimeZoneContactRow[] GetObservedVisibleContacts(",
|
|
"private bool TryQueryEntityContact(",
|
|
"private bool TryQueryEntityTarget(",
|
|
"AetheriaRuntimeZoneContactRow",
|
|
"AetheriaRuntimeZoneTargetRow",
|
|
"ResolveZoneContacts?.Invoke()",
|
|
"ResolveEntity?.Invoke(targetEntityIndex)"
|
|
};
|
|
var missingTargetContactQuerySymbols = requiredTargetContactQuerySymbols
|
|
.Where(symbol => !targetPresentation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTargetContactQuerySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed Unity target/contact query lowering must live in AetheriaUnityTargetPresentation instead of ActionGameManager: " +
|
|
string.Join(", ", missingTargetContactQuerySymbols));
|
|
}
|
|
|
|
|
|
if (observedEntityRestorer.Contains("case \"active_consumable\":", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity still restores daemon active-consumable behavior state by transient list index.");
|
|
}
|
|
|
|
if (targetPresentation.Contains("CultMeshReactiveDocument<AetheriaRuntimeZoneContactsDocument>", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains(".ReactiveZoneContacts(\"unity-observed-target-query\")", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains("_zoneContacts?.Dispose()", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains("_zoneContacts?.Current", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains("AetheriaRuntimeZoneContactsSession _zoneContacts", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains(".ObserveZoneContacts()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed Unity target/contact query still routes zone contacts through legacy session/reactive wrapper state instead of named current typed state.");
|
|
}
|
|
|
|
var forbiddenManagerTargetQuerySymbols = new[]
|
|
{
|
|
"private bool TryQueryDaemonEntityContact(",
|
|
"private bool TryQueryDaemonEntityTarget(",
|
|
"private Entity GetObservedTarget(",
|
|
"private float GetObservedInfoGathered(",
|
|
"private bool IsObservedHostileContact(",
|
|
"AetheriaRuntimeDaemonRenderQueries.TryQueryEntityContact(",
|
|
"AetheriaRuntimeDaemonRenderQueries.TryQueryEntityTarget("
|
|
};
|
|
var managerTargetQueryHits = forbiddenManagerTargetQuerySymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerTargetQueryHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns observed daemon target/contact query internals: " +
|
|
string.Join(", ", managerTargetQueryHits));
|
|
}
|
|
|
|
var requiredCurrentEntityBinderSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityCurrentEntityBinder",
|
|
"public void RestoreBinding(",
|
|
"public void ClearBinding()",
|
|
"private void BindUndocked(",
|
|
"public Func<Entity, Entity> ResolveDockParent { get; set; }",
|
|
"var dockParent = ResolveDockParent?.Invoke(currentEntity)",
|
|
"CurrentEntityPresentation?.BindDocked(",
|
|
"CurrentEntityPresentation?.BindUndocked(",
|
|
"TargetPresentation?.ClearIndicators();",
|
|
"CurrentEntityPresentation?.ClearBinding();",
|
|
"DisablePlayerInput?.Invoke();",
|
|
"ApplyActionBarBindings?.Invoke(Array.Empty<AetheriaUnityActionBarBinding>())",
|
|
"TargetPresentation?.ReconcileVisibleTargetIndicators(GetCurrentEntity?.Invoke())",
|
|
"SetViewDirection?.Invoke((float3)AetheriaMath.ToUnityXZ(entity.CultDirection))"
|
|
};
|
|
var missingCurrentEntityBinderSymbols = requiredCurrentEntityBinderSymbols
|
|
.Where(symbol => !currentEntityBinder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingCurrentEntityBinderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Current-entity binding orchestration must live in AetheriaUnityCurrentEntityBinder instead of ActionGameManager: " +
|
|
string.Join(", ", missingCurrentEntityBinderSymbols));
|
|
}
|
|
|
|
var forbiddenManagerCurrentEntityBinderSymbols = new[]
|
|
{
|
|
"if (ObservedDocking.TryResolveDockingBay(currentEntity, out var dockParent, out _))",
|
|
"private AetheriaUnityObservedDockingIndex ObservedDocking =>",
|
|
"ObservedDocking.IsEntityUndocked(CurrentEntity)",
|
|
"_currentEntityPresentation.BindDocked(",
|
|
"_currentEntityPresentation.BindUndocked(",
|
|
"_targetPresentation.ClearIndicators();",
|
|
"_currentEntityPresentation.ClearBinding();",
|
|
"private void BindToEntity(",
|
|
"DeathPost.weight = 0;",
|
|
"ZoneRenderer.TryGetEntityInstance(entity, out var entityInstance)",
|
|
"UpdateTowingStation"
|
|
};
|
|
var managerCurrentEntityBinderHits = forbiddenManagerCurrentEntityBinderSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerCurrentEntityBinderHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns current-entity bind orchestration instead of delegating to AetheriaUnityCurrentEntityBinder: " +
|
|
string.Join(", ", managerCurrentEntityBinderHits));
|
|
}
|
|
|
|
var requiredTargetPresentationSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityTargetPresentation",
|
|
"public Func<AetheriaRuntimeZoneContactsDocument> ResolveZoneContacts { get; set; }",
|
|
"ResolveZoneContacts?.Invoke()",
|
|
"public Entity GetObservedTarget(Entity observer)",
|
|
"public Entity Tick(Entity currentEntity, float time)",
|
|
"public void ReconcileVisibleTargetIndicators(Entity currentEntity)",
|
|
"public void UpdateTargetIndicators(",
|
|
"renderSettings.NormalizeDetectionProgress(",
|
|
"renderSettings.ResolveTargetSpottedFillEnabled(",
|
|
"renderSettings.NormalizeTargetVisibilityFill(",
|
|
"renderSettings.NormalizeVisibilityToTargetFill(",
|
|
"renderSettings.NormalizeTargetStatusFill(",
|
|
"renderSettings.ResolveLockIndicatorNoiseAmplitude(",
|
|
"renderSettings.ResolveLockIndicatorNoiseFrequency(",
|
|
"renderSettings.ResolveLockSpinSpeed(",
|
|
"GetObservedInfoGathered(currentEntity, target)",
|
|
"IsObservedHostileContact(currentEntity, observedTarget)",
|
|
"public AetheriaRuntimeCatalogSnapshot RuntimeCatalog { get; set; }",
|
|
"RuntimeCatalog?.FindItem(target.Hull, x => x.ItemKey)"
|
|
};
|
|
var missingTargetPresentationSymbols = requiredTargetPresentationSymbols
|
|
.Where(symbol => !targetPresentation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTargetPresentationSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon-observed target HUD presentation must live in AetheriaUnityTargetPresentation instead of ActionGameManager: " +
|
|
string.Join(", ", missingTargetPresentationSymbols));
|
|
}
|
|
|
|
var forbiddenTargetPresentationSymbols = new[]
|
|
{
|
|
"Func<AetheriaClient>",
|
|
"ResolveClient",
|
|
"ResolveInfoGathered",
|
|
"ResolveHostileContact",
|
|
"ResolveVisibleContacts"
|
|
};
|
|
var targetPresentationAccessHits = forbiddenTargetPresentationSymbols
|
|
.Where(symbol => targetPresentation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (targetPresentationAccessHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityTargetPresentation must consume observed typed target data, not hold a runtime client resolver: " +
|
|
string.Join(", ", targetPresentationAccessHits));
|
|
}
|
|
|
|
var requiredPilotFrameControllerSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityPilotFrameController",
|
|
"public void Tick(Entity currentEntity, float deltaTime, float timeSeconds)",
|
|
"TargetPresentation?.Tick(currentEntity, timeSeconds)",
|
|
"Input.Player.Look.ReadValue<Vector2>()",
|
|
"PilotCommands.RequestLookDirection(viewDirection)",
|
|
"renderSettings.NormalizeHeatstrokePost(currentEntity.Heatstroke)",
|
|
"renderSettings.ResolveSevereHeatstrokePostWeight(currentEntity.Heatstroke, timeSeconds)",
|
|
"Input.Player.Move.ReadValue<Vector2>()",
|
|
"PilotCommands.RequestMoveVector(movement)",
|
|
"Input.Player.TractorBeam.ReadValue<float>()",
|
|
"PilotCommands.RequestTractorPower(Saturate("
|
|
};
|
|
var missingPilotFrameControllerSymbols = requiredPilotFrameControllerSymbols
|
|
.Where(symbol => !pilotFrameController.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingPilotFrameControllerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Undocked pilot-frame input/presentation policy must live in AetheriaUnityPilotFrameController instead of ActionGameManager: " +
|
|
string.Join(", ", missingPilotFrameControllerSymbols));
|
|
}
|
|
|
|
var forbiddenManagerPilotFrameSymbols = new[]
|
|
{
|
|
"Input.Player.Look.ReadValue<Vector2>()",
|
|
"Input.Player.Move.ReadValue<Vector2>()",
|
|
"Input.Player.TractorBeam.ReadValue<float>()",
|
|
"PilotCommands.RequestLookDirection(_viewDirection)",
|
|
"PilotCommands.RequestMoveVector(movement)",
|
|
"PilotCommands.RequestTractorPower(Saturate(",
|
|
"HeatstrokePost.weight = (float)renderSettings.NormalizeHeatstrokePost(",
|
|
"SevereHeatstrokePost.weight ="
|
|
};
|
|
var managerPilotFrameHits = forbiddenManagerPilotFrameSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerPilotFrameHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns undocked pilot-frame input/presentation internals instead of delegating to AetheriaUnityPilotFrameController: " +
|
|
string.Join(", ", managerPilotFrameHits));
|
|
}
|
|
|
|
var forbiddenManagerDockingScanSymbols = new[]
|
|
{
|
|
"TryGetDaemonParentSnapshot(",
|
|
"TryResolveDaemonDockingBay(",
|
|
"parentSnapshot.DockingBayAssignments"
|
|
};
|
|
var managerDockingScanHits = forbiddenManagerDockingScanSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerDockingScanHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still scans daemon docking relationships instead of reading current typed docking state at the caller edge: " +
|
|
string.Join(", ", managerDockingScanHits));
|
|
}
|
|
|
|
var requiredCurrentEntityPresentationSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityCurrentEntityPresentation",
|
|
"public void BindUndocked(",
|
|
"public void BindDocked(",
|
|
"public void ClearBinding()",
|
|
"public void Tick(float deltaTime)",
|
|
"private readonly List<IDisposable> _shipSubscriptions",
|
|
"private readonly List<IDisposable> _targetSubscriptions",
|
|
"public (HardpointData[] hardpoints, Transform[] barrels, PlaceUIElementWorldspace crosshair)[] ArticulationGroups",
|
|
"public (LockWeapon targetLock, PlaceUIElementWorldspace indicator, Rotate spin)[] LockingIndicators",
|
|
"entity.TargetedByCount.Subscribe",
|
|
"entity.Target.Subscribe",
|
|
"target.IncomingHit.Where",
|
|
"_hitMarkerTime = HitMarkerDuration",
|
|
"LockIndicator.Instantiate<PlaceUIElementWorldspace>()",
|
|
"TradeMenu.Inventory = entity.CargoBays.First()",
|
|
"DockCamera.enabled = true",
|
|
"FollowCamera.enabled = false",
|
|
"DockCamera.Follow = orbitalInstance.transform",
|
|
"ZoneRenderer.TryGetBodyView(parentOrbitPlanetBodyKey, out var parentBodyView)",
|
|
"Menu.ShowTab(MenuTab.Inventory)"
|
|
};
|
|
var missingCurrentEntityPresentationSymbols = requiredCurrentEntityPresentationSymbols
|
|
.Where(symbol => !currentEntityPresentation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingCurrentEntityPresentationSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Current-entity Unity presentation binding must live in AetheriaUnityCurrentEntityPresentation instead of ActionGameManager: " +
|
|
string.Join(", ", missingCurrentEntityPresentationSymbols));
|
|
}
|
|
|
|
var forbiddenManagerCurrentPresentationSymbols = new[]
|
|
{
|
|
"private List<IDisposable> _shipSubscriptions",
|
|
"private List<IDisposable> _targetSubscriptions",
|
|
"private (HardpointData[] hardpoints, Transform[] barrels, PlaceUIElementWorldspace crosshair)[] _articulationGroups",
|
|
"private (LockWeapon targetLock, PlaceUIElementWorldspace indicator, Rotate spin)[] _lockingIndicators",
|
|
"private float _hitMarkerTime",
|
|
"private void DoDock(",
|
|
"TradeMenu.Inventory =",
|
|
"DockCamera.Follow =",
|
|
"DockCamera.LookAt =",
|
|
"DockCamera.enabled = true",
|
|
"FollowCamera.enabled = false",
|
|
"ZoneRenderer.TryGetBodyView(parentOrbitPlanetBodyKey, out var parentBodyView)",
|
|
"Menu.ShowTab(MenuTab.Inventory)",
|
|
"CurrentEntity.TargetedByCount.Subscribe",
|
|
"CurrentEntity.Target.Subscribe",
|
|
"target.IncomingHit.Where",
|
|
"LockIndicator.Instantiate<PlaceUIElementWorldspace>()"
|
|
};
|
|
var managerCurrentPresentationHits = forbiddenManagerCurrentPresentationSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerCurrentPresentationHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns bind-time current-entity presentation state instead of delegating to AetheriaUnityCurrentEntityPresentation: " +
|
|
string.Join(", ", managerCurrentPresentationHits));
|
|
}
|
|
|
|
if (actionGameManager.Contains("ItemManager.GetRuntimeItem", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still resolves item metadata through Unity ItemManager instead of the runtime catalog.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("public bool TryGetObservedCurrentEntity(out Entity entity)", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public bool TryGetObservedDockingBay(out EquippedDockingBay dockingBay)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must not expose broad observed current-entity or docking-bay read shims; clients must use managed typed current/docking documents and narrow Unity object adapters.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("public EquippedDockingBay DockingBay", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public Entity DockedEntity", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must not expose renderer-local docked entity or docking bay state as public gameplay truth; clients must use the managed current-docking document.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("public Entity TowingStation", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("AvailableCargoBays()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must not expose renderer-local towing context or available cargo bay enumeration as public gameplay truth; clients must use typed documents and daemon operations.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("public int Credits", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("Credits = render.Credits", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must not mirror run credits as manager-global gameplay state; clients must read credits through typed documents such as StationRefitAsync.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("public static AetheriaRuntimeCatalogSnapshot RuntimeCatalog", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public AetheriaRuntimeCatalogSnapshot RuntimeCatalog", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must not expose the typed runtime catalog as manager-global gameplay state; clients must open typed catalog views through AetheriaClient.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("public AetheriaRuntimeDaemonRenderSettings ObservedDaemonRenderSettings()", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("ObservedDaemonRenderSettings(", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("new AetheriaRuntimeDaemonRenderSettings(", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("ConfigureSchematicDisplayRenderSettings()", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("SchematicDisplay?.SetRenderSettings(", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("TargetSchematicDisplay?.SetRenderSettings(", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("private void UpdatePlayerPanel()", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("private void UpdateTargetPanel(Entity target)", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public EntityConstructionBlueprint CreateEntityConstructionBlueprint(AetheriaRuntimeLoadoutTemplateSnapshot template)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must delegate daemon render-settings and loadout-template lowering to focused renderer/bootstrap adapters, not expose them as client gameplay APIs.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("ObservedLoadoutTemplates", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("_observedLoadoutTemplates", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("LoadRuntimeLoadoutTemplates", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must not cache or expose loadout templates as manager-global observed state; clients must read typed loadout templates directly.");
|
|
}
|
|
|
|
var missingGameplaySymbols = requiredGameplaySymbols
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingGameplaySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager no longer has the daemon-frame Continue boot path: " +
|
|
string.Join(", ", missingGameplaySymbols));
|
|
}
|
|
|
|
var requiredGameplaySceneWiringSymbols = new[]
|
|
{
|
|
"public void ConfigureCurrentEntityPresentation(",
|
|
"presentation.RuntimeCatalog = runtimeCatalog",
|
|
"public void ConfigureTargetPresentation(",
|
|
"presentation.ResolveZoneContacts = ReadZoneContacts",
|
|
"private AetheriaClientState _targetPresentationState;",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-target-presentation\")",
|
|
"TargetPresentationState.ZoneContacts.Latest()",
|
|
"presentation.RuntimeCatalog = runtimeCatalog"
|
|
};
|
|
|
|
var missingGameplaySceneWiringSymbols = requiredGameplaySceneWiringSymbols
|
|
.Where(symbol => !gameplaySceneWiring.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingGameplaySceneWiringSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityGameplaySceneWiring no longer has the daemon-frame Continue presentation boot path: " +
|
|
string.Join(", ", missingGameplaySceneWiringSymbols));
|
|
}
|
|
|
|
var forbiddenGameplaySceneWiringSymbols = new[]
|
|
{
|
|
"Func<AetheriaClient> resolveClient",
|
|
"presentation.ResolveClient = resolveClient"
|
|
};
|
|
var gameplaySceneWiringAccessHits = forbiddenGameplaySceneWiringSymbols
|
|
.Where(symbol => gameplaySceneWiring.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (gameplaySceneWiringAccessHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityGameplaySceneWiring must wire typed observed target query delegates instead of passing runtime client resolvers: " +
|
|
string.Join(", ", gameplaySceneWiringAccessHits));
|
|
}
|
|
|
|
var forbiddenGameplaySymbols = new[]
|
|
{
|
|
"if (run == null ||\r\n run.CurrentZoneIndex < 0 ||\r\n run.CurrentZoneEntityIndex < 0)",
|
|
"var currentEntityKey = $\"{zoneEntityKeyPrefix}{run.CurrentZoneEntityIndex}.v1\"",
|
|
"TargetVisibilityFill.fillAmount = Mathf.Lerp(.25f, .75f, (CurrentEntity.EntityInfoGathered[target] - threshold) / (1 - threshold));",
|
|
"VisibilityToTargetFill.fillAmount = Mathf.Lerp(.25f, .75f, target.EntityInfoGathered[CurrentEntity] / threshold);",
|
|
"indicator.Key.EntityInfoGathered[CurrentEntity]",
|
|
"CurrentEntity.Target.Value.IsHostileTo(CurrentEntity)",
|
|
"CurrentEntity.VisibleEnemies.ObserveAdd()",
|
|
"CurrentEntity.VisibleEnemies.ObserveRemove()",
|
|
"CurrentEntity.VisibleFriendlies.ObserveAdd()",
|
|
"CurrentEntity.VisibleFriendlies.ObserveRemove()",
|
|
"_currentEntityPresentation.Tick(Time.deltaTime);",
|
|
"PilotFrameController.Tick(CurrentEntity, Time.deltaTime, Time.time)",
|
|
"_targetPresentation.UpdateTargetIndicators(",
|
|
"foreach (var hostile in CurrentEntity.VisibleEnemies)",
|
|
"foreach (var friendly in CurrentEntity.VisibleFriendlies)",
|
|
"dockedEntitySnapshot.ChildEntityIndices",
|
|
"UpdateTargetPanel(CurrentEntity.Target.Value)",
|
|
"var target = CurrentEntity.Target.Value;",
|
|
"TargetIndicator.Target = CurrentEntity.Target.Value.Position",
|
|
"indicator.Target = CurrentEntity.Target.Value.Position",
|
|
"foreach (var bay in CurrentEntity.Parent.DockingBays)",
|
|
"foreach (var entity in DockedEntity.Children)",
|
|
"CurrentEntity != null && CurrentEntity.Parent == null",
|
|
"CurrentEntity !=null && CurrentEntity.Parent==null",
|
|
"CurrentEntity == null || CurrentEntity.Parent != null",
|
|
"return observer?.Target.Value;",
|
|
"observer.EntityInfoGathered.TryGetValue(target, out var infoGathered)",
|
|
"return target != null && target.IsHostileTo(observer);",
|
|
"RestoreEntityContactsFromTypedSnapshot",
|
|
"entity.Target.Value = target",
|
|
"entity.EntityInfoGathered[target]",
|
|
"entity.EntityHostility[target]",
|
|
"entity.VisibleEntities.Add(target)",
|
|
"entity.VisibleEnemies.Add(target)",
|
|
"entity.VisibleFriendlies.Add(target)",
|
|
"currentEntity.Parent.DockingBays",
|
|
"currentEntity.Parent is OrbitalEntity",
|
|
"DoDock(currentEntity.Parent, dockingBay)",
|
|
"RestoreChildAndDockingRelationships",
|
|
"child.SetParent(parent)",
|
|
"ship.SetParent(parent)",
|
|
"dockingBay.DockedShip = ship",
|
|
"Zone.Entities.Contains(ship)"
|
|
};
|
|
|
|
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 snapshotDocumentPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs");
|
|
var snapshotDocument = File.Exists(snapshotDocumentPath)
|
|
? File.ReadAllText(snapshotDocumentPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon snapshot authority; AetheriaRuntimeSnapshotDocuments.cs is missing.");
|
|
if (snapshotDocument.Contains("CurrentZoneEntityIndex", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon runtime snapshot transport still exposes integer current-entity slot authority.");
|
|
}
|
|
|
|
var forbiddenLegacyCommitSymbols = new[]
|
|
{
|
|
"public enum AetheriaRuntimeCommitKind",
|
|
"public sealed class AetheriaRuntimeCommitEnvelope",
|
|
"public sealed class AetheriaRuntimeStateCommitDocument",
|
|
"internal enum AetheriaRuntimeCommitKind",
|
|
"internal sealed class AetheriaRuntimeCommitEnvelope",
|
|
"internal sealed class AetheriaRuntimeStateCommitDocument"
|
|
};
|
|
var legacyCommitHits = forbiddenLegacyCommitSymbols
|
|
.Where(symbol => snapshotDocument.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (legacyCommitHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Legacy runtime commit envelope leaked back into the daemon snapshot contract: " +
|
|
string.Join(", ", legacyCommitHits));
|
|
}
|
|
|
|
var forbiddenLegacyFiles = new[]
|
|
{
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateCommitDocument.cs"),
|
|
Path.Combine(root, "Aetheria.State", "AetheriaLegacyRuntimeSnapshotImporter.cs")
|
|
};
|
|
var existingLegacyFiles = forbiddenLegacyFiles
|
|
.Where(File.Exists)
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (existingLegacyFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Legacy runtime commit compatibility files must stay deleted: " +
|
|
string.Join(", ", existingLegacyFiles));
|
|
}
|
|
|
|
var requiredPackageSymbols = new[]
|
|
{
|
|
"public string RecordKey"
|
|
};
|
|
|
|
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",
|
|
"FindActiveConsumableByEffectId",
|
|
"RetainActiveConsumables",
|
|
"RestoreStatGrids",
|
|
"RestoreHullConductivityGrid",
|
|
"new ConsumableItemEffect(item, this, effectId, 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 RequireUnityObserverDoesNotTickLocalSimulation(string root)
|
|
{
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
var galaxyPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Galaxy.cs");
|
|
var daemonDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonDocuments.cs");
|
|
var daemonOperationClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationClient.cs");
|
|
var daemonRuntimeOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs");
|
|
var daemonRuntimeSimulationPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSimulation.cs");
|
|
var daemonIntentPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonIntentState.cs");
|
|
var zoneRenderDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeGameViewportDocuments.cs");
|
|
var daemonObserverPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonObserver.cs");
|
|
var pilotCommandSenderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotCommandSender.cs");
|
|
var pilotFrameControllerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotFrameController.cs");
|
|
var pilotOperationControllerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotOperationController.cs");
|
|
var legacyPilotFrameAdapterPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotFrameAdapter.cs");
|
|
var legacyPilotOperationAdapterPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotOperationAdapter.cs");
|
|
var targetPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityTargetPresentation.cs");
|
|
var deletedObservedTargetQueryPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedTargetQuery.cs");
|
|
var observedFrameApplierPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedFrameApplier.cs");
|
|
var legacyUnityDaemonEntitySnapshotProjectorPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityDaemonEntitySnapshotProjector.cs");
|
|
var daemonEntitySnapshotProjectorPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEntitySnapshotProjector.cs");
|
|
var observedEntityRestorerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedEntityRestorer.cs");
|
|
var entityBlueprintMaterializerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityEntityBlueprintMaterializer.cs");
|
|
var observedZoneContextFactoryPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedZoneContextFactory.cs");
|
|
var currentEntityBinderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityCurrentEntityBinder.cs");
|
|
var gameplaySceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs");
|
|
var daemonGameplayOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationsClient.cs");
|
|
var legacyDaemonGameplayOperationsPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonOperations.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; ActionGameManager.cs is missing.");
|
|
var mainMenu = File.Exists(mainMenuPath)
|
|
? File.ReadAllText(mainMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; MainMenu.cs is missing.");
|
|
var galaxy = File.Exists(galaxyPath)
|
|
? File.ReadAllText(galaxyPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; Galaxy.cs is missing.");
|
|
var daemonDocuments = File.Exists(daemonDocumentsPath)
|
|
? File.ReadAllText(daemonDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; daemon command documents are missing.");
|
|
var daemonOperationClient = File.Exists(daemonOperationClientPath)
|
|
? File.ReadAllText(daemonOperationClientPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; daemon operation client is missing.");
|
|
var daemonOperationsSource = File.Exists(daemonRuntimeOperationsPath)
|
|
? File.ReadAllText(daemonRuntimeOperationsPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; daemon operations is missing.");
|
|
var daemonSimulationSource = File.Exists(daemonRuntimeSimulationPath)
|
|
? File.ReadAllText(daemonRuntimeSimulationPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; daemon simulation is missing.");
|
|
var daemonIntent = File.Exists(daemonIntentPath)
|
|
? File.ReadAllText(daemonIntentPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; daemon intent state is missing.");
|
|
var daemonObserver = File.Exists(daemonObserverPath)
|
|
? File.ReadAllText(daemonObserverPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaDaemonObserver.cs is missing.");
|
|
var pilotCommandSender = File.Exists(pilotCommandSenderPath)
|
|
? File.ReadAllText(pilotCommandSenderPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityPilotCommandSender.cs is missing.");
|
|
var pilotFrameController = File.Exists(pilotFrameControllerPath)
|
|
? File.ReadAllText(pilotFrameControllerPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityPilotFrameController.cs is missing.");
|
|
var pilotOperationController = File.Exists(pilotOperationControllerPath)
|
|
? File.ReadAllText(pilotOperationControllerPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityPilotOperationController.cs is missing.");
|
|
if (File.Exists(legacyPilotFrameAdapterPath) ||
|
|
File.Exists(legacyPilotOperationAdapterPath) ||
|
|
actionGameManager.Contains("AetheriaUnityPilotFrameAdapter", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("AetheriaUnityPilotOperationAdapter", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity pilot control must be owned by named controllers; adapter-shaped pilot access is obsolete.");
|
|
}
|
|
if (File.Exists(deletedObservedTargetQueryPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityObservedTargetQuery is dead contact-query chaff; target presentation should read typed zone contacts directly.");
|
|
}
|
|
var targetPresentation = File.Exists(targetPresentationPath)
|
|
? File.ReadAllText(targetPresentationPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityTargetPresentation.cs is missing.");
|
|
var observedFrameApplier = File.Exists(observedFrameApplierPath)
|
|
? File.ReadAllText(observedFrameApplierPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityObservedFrameApplier.cs is missing.");
|
|
if (File.Exists(legacyUnityDaemonEntitySnapshotProjectorPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity still owns daemon entity snapshot projection; package runtime should lower typed daemon entity snapshots.");
|
|
}
|
|
|
|
var zoneRenderDocuments = File.Exists(zoneRenderDocumentsPath)
|
|
? File.ReadAllText(zoneRenderDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaRuntimeGameViewportDocuments.cs is missing.");
|
|
if (File.Exists(daemonEntitySnapshotProjectorPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeEntitySnapshotProjector is dead access chaff; AetheriaRuntimeZoneRenderDocument owns managed entity snapshot access.");
|
|
}
|
|
var observedEntityRestorer = File.Exists(observedEntityRestorerPath)
|
|
? File.ReadAllText(observedEntityRestorerPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityObservedEntityRestorer.cs is missing.");
|
|
var entityBlueprintMaterializer = File.Exists(entityBlueprintMaterializerPath)
|
|
? File.ReadAllText(entityBlueprintMaterializerPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityEntityBlueprintMaterializer.cs is missing.");
|
|
var observedZoneContextFactory = File.Exists(observedZoneContextFactoryPath)
|
|
? File.ReadAllText(observedZoneContextFactoryPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityObservedZoneContextFactory.cs is missing.");
|
|
var currentEntityBinder = File.Exists(currentEntityBinderPath)
|
|
? File.ReadAllText(currentEntityBinderPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityCurrentEntityBinder.cs is missing.");
|
|
var gameplaySceneWiring = File.Exists(gameplaySceneWiringPath)
|
|
? File.ReadAllText(gameplaySceneWiringPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityGameplaySceneWiring.cs is missing.");
|
|
var daemonOperations = File.Exists(daemonGameplayOperationsPath)
|
|
? File.ReadAllText(daemonGameplayOperationsPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; shared daemon operations client is missing.");
|
|
|
|
if (File.Exists(legacyDaemonGameplayOperationsPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity gameplay still owns AetheriaDaemonOperations; shared daemon operation access belongs in the runtime package.");
|
|
}
|
|
|
|
var forbiddenGameplaySymbols = new[]
|
|
{
|
|
"CurrentGalaxy",
|
|
"Zone.Update(Time.deltaTime)",
|
|
"Zone.Update(",
|
|
"CurrentEntity.Update(",
|
|
".Update(Time.deltaTime)",
|
|
"Death.Subscribe(Die)",
|
|
"private void Die(CauseOfDeath",
|
|
"ZoneGenerator.GenerateZone",
|
|
"TryDock(",
|
|
"TryUndock(",
|
|
"IntroCutscene(",
|
|
"IntroDuration",
|
|
"ship.CultPositionXZ =",
|
|
"ship.CultDirection =",
|
|
"ship.CultVelocity =",
|
|
"CurrentEntity.Zone.Entities.Remove(CurrentEntity)",
|
|
"CurrentEntity.Zone = Zone",
|
|
"Zone.Entities.Add(CurrentEntity)",
|
|
"CurrentEntity.CultPositionXZ = TowingStation.CultPositionXZ",
|
|
"PopulateLevel(TowingStation.Zone.GalaxyZone)",
|
|
"TowingStation.Zone?.GalaxyZone",
|
|
"Zone.PlanetInstances.Values.FirstOrDefault",
|
|
"Zone.TryGetOrbit(orbital.OrbitKey",
|
|
"ZoneRenderer.Planets",
|
|
"RestoreDaemonAsteroidRuntimeState",
|
|
"belt.Damage.Clear()",
|
|
"belt.RespawnTimers.Clear()",
|
|
"AetheriaRuntimeStateCommitLog.QueueRunCheckpoint",
|
|
"AetheriaRuntimeStateCommitLog.QueuePlayerSettings",
|
|
"AetheriaRuntimeStateCommitLog.QueueLoadoutTemplate"
|
|
};
|
|
|
|
var forbiddenGameplayHits = forbiddenGameplaySymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (forbiddenGameplayHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity gameplay still owns local simulation or legacy write fallback instead of daemon observation: " +
|
|
string.Join(", ", forbiddenGameplayHits));
|
|
}
|
|
|
|
if (daemonObserver.Contains("AetheriaRuntimeCommandSubmitter.TrySubmitDaemonCommand(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity daemon observer still calls the generic command submitter instead of the typed daemon operation client.");
|
|
}
|
|
|
|
var requiredGameplaySymbols = new[]
|
|
{
|
|
"ObservedFrameApplier.ApplyLatestZoneRender()",
|
|
"private AetheriaUnityGameplayLoopShell GameplayLoopShell =>",
|
|
"GameplayLoopShell.Tick(Time.deltaTime, Time.time)",
|
|
"GameplayLoopShell.LateTick()",
|
|
"ApplyLatestZoneRender = () => ObservedFrameApplier.ApplyLatestZoneRender()",
|
|
"ResolveDaemonObserver()",
|
|
"private AetheriaUnityObservedFrameApplier ObservedFrameApplier =>",
|
|
"ObservedZoneContextFactory = new AetheriaUnityObservedZoneContextFactory(",
|
|
"boot.ObservedGalaxy",
|
|
"ObservedZoneContextFactory.ResolveGalaxyZone",
|
|
"private AetheriaUnityObservedZoneContextFactory ObservedZoneContextFactory { get; set; }",
|
|
"entity => CurrentEntityBinder.RestoreBinding(entity)",
|
|
"private AetheriaUnityEntityBlueprintMaterializer EntityBlueprintMaterializer =>",
|
|
"EntityBlueprintMaterializer.MaterializeObservedEntity",
|
|
"_loadoutItemFactory.CreateLoadoutItem",
|
|
"private AetheriaUnityPilotCommandSender PilotCommands =>",
|
|
"private AetheriaUnityPilotFrameController PilotFrameController =>",
|
|
"PilotFrameController = PilotFrameController",
|
|
"private AetheriaUnityPilotOperationController PilotOperationController =>",
|
|
"SceneWiring.ConfigureTargetPresentation("
|
|
};
|
|
|
|
var missingGameplaySymbols = requiredGameplaySymbols
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingGameplaySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity gameplay no longer behaves as a daemon-frame observer and command lowerer: " +
|
|
string.Join(", ", missingGameplaySymbols));
|
|
}
|
|
|
|
var requiredSceneWiringObserverSymbols = new[]
|
|
{
|
|
"presentation.ResolveZoneContacts = ReadZoneContacts",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-target-presentation\")",
|
|
"TargetPresentationState.ZoneContacts.Latest()"
|
|
};
|
|
|
|
var missingSceneWiringObserverSymbols = requiredSceneWiringObserverSymbols
|
|
.Where(symbol => !gameplaySceneWiring.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSceneWiringObserverSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity scene wiring no longer connects target presentation to observed daemon target queries: " +
|
|
string.Join(", ", missingSceneWiringObserverSymbols));
|
|
}
|
|
|
|
if (!targetPresentation.Contains("public Entity GetObservedTarget(Entity observer)", StringComparison.Ordinal) ||
|
|
!targetPresentation.Contains("ResolveZoneContacts?.Invoke()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity target presentation must query target/contact HUD state from typed zone contacts directly.");
|
|
}
|
|
|
|
if (!zoneRenderDocuments.Contains("new AetheriaRuntimeEntitySnapshot(", StringComparison.Ordinal) ||
|
|
!zoneRenderDocuments.Contains("CreateWeaponStates(runId, zoneIndex, entity.WeaponStates)", StringComparison.Ordinal) ||
|
|
!zoneRenderDocuments.Contains("entity.EntityIndex,", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity observer entity snapshot access no longer carries typed daemon entity indices through AetheriaRuntimeZoneRenderDocument.");
|
|
}
|
|
|
|
if (zoneRenderDocuments.Contains("EntityIndexFromRecordKey", StringComparison.Ordinal) ||
|
|
observedFrameApplier.Contains("EntityIndexFromRecordKey", StringComparison.Ordinal) ||
|
|
observedEntityRestorer.Contains("EntityIndexFromRecordKey", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity observer entity projection must use typed daemon entity indices instead of parsing synthetic record keys.");
|
|
}
|
|
|
|
if (!observedEntityRestorer.Contains("public bool TryApplyInPlace(", StringComparison.Ordinal) ||
|
|
!observedEntityRestorer.Contains("public void Replace(", StringComparison.Ordinal) ||
|
|
!observedEntityRestorer.Contains("EntityConstructionBlueprintMaterializer.MaterializeObservedFromBlueprint(_itemManager, zone, blueprint)", StringComparison.Ordinal) ||
|
|
!observedEntityRestorer.Contains("RestoreRuntimeBehaviorState(entity, entitySnapshot, restoredEntities)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity observer facade restoration no longer flows through the dedicated observed entity restorer.");
|
|
}
|
|
|
|
var requiredEntityBlueprintMaterializerSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityEntityBlueprintMaterializer",
|
|
"public EntityConstructionBlueprint MaterializeTemplate(AetheriaRuntimeLoadoutTemplateSnapshot template)",
|
|
"public EntityConstructionBlueprint MaterializeLoadoutEntity(AetheriaRuntimeEntityLoadoutSnapshot entity)",
|
|
"public EntityConstructionBlueprint MaterializeObservedEntity(",
|
|
"private static EntityConstructionBlueprint CreateBlueprint(string kind)",
|
|
"new OrbitalEntityConstructionBlueprint()",
|
|
"new ShipConstructionBlueprint()",
|
|
"shipBlueprint.Position = new float3((float)entity.PositionX",
|
|
"shipBlueprint.Direction = new float2((float)entity.DirectionX",
|
|
"shipBlueprint.IsPlayerShip = isCurrentEntity",
|
|
"blueprint.Equipment = CreateEquippableSlots(entity.Equipment)",
|
|
"blueprint.CargoContents = CreateCargoBayContents(entity.CargoContents)",
|
|
"blueprint.Children = entity.Children",
|
|
"_loadoutItemFactory.CreateLoadoutItem(slot.Item)",
|
|
"_loadoutItemFactory.CreateLoadoutItem(item) as EquippableItem"
|
|
};
|
|
var missingEntityBlueprintMaterializerSymbols = requiredEntityBlueprintMaterializerSymbols
|
|
.Where(symbol => !entityBlueprintMaterializer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingEntityBlueprintMaterializerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity observer entity construction materialization must live in AetheriaUnityEntityBlueprintMaterializer instead of ActionGameManager: " +
|
|
string.Join(", ", missingEntityBlueprintMaterializerSymbols));
|
|
}
|
|
|
|
var forbiddenManagerEntityConstructionSymbols = new[]
|
|
{
|
|
"private EntityConstructionBlueprint CreateEntityConstructionBlueprint(",
|
|
"private (int2 position, EquippableItem item)[] CreateEquippableSlots(",
|
|
"private (int2 position, ItemInstance item)[][] CreateCargoBayContents(",
|
|
"private EquippableItem CreateEquippableLoadoutItem(",
|
|
"private ItemInstance CreateLoadoutItem(AetheriaRuntimeLoadoutItemSnapshot",
|
|
"new OrbitalEntityConstructionBlueprint()",
|
|
"new ShipConstructionBlueprint",
|
|
"blueprint.Children = entity.Children",
|
|
"ItemManager.CreateSimpleCommodityInstance(typedItem",
|
|
"ItemManager.CreateCraftedInstance(typedItem"
|
|
};
|
|
var managerEntityConstructionHits = forbiddenManagerEntityConstructionSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerEntityConstructionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns Unity entity construction blueprint lowering internals: " +
|
|
string.Join(", ", managerEntityConstructionHits));
|
|
}
|
|
|
|
if (!observedZoneContextFactory.Contains("public Zone ResolveContext(", StringComparison.Ordinal) ||
|
|
!observedZoneContextFactory.Contains("private static ZoneConstructionBlueprint CreateZoneConstructionBlueprint(", StringComparison.Ordinal) ||
|
|
!observedZoneContextFactory.Contains("private static BodyConstructionData CreateBodyConstructionData(", StringComparison.Ordinal) ||
|
|
!observedZoneContextFactory.Contains("new Zone(_itemManager, _planetSettings, constructionBlueprint, galaxyZone, _observedGalaxy)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity observer zone context lowering no longer flows through the dedicated observed zone context factory.");
|
|
}
|
|
|
|
var requiredZoneRenderSnapshotSymbols = new[]
|
|
{
|
|
"public AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots()",
|
|
"public static AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots(",
|
|
"public static string EntityRecordKey(string runId, int zoneIndex, int entityIndex)",
|
|
"new AetheriaRuntimeEntitySnapshot(",
|
|
"CreateEntityItemSlots(entity.Equipment)",
|
|
"CreateWeaponStates(runId, zoneIndex, entity.WeaponStates)",
|
|
"CreateBehaviorStates(entity.BehaviorStates)",
|
|
"CreateCargoBays(entity.CargoContents)",
|
|
"new AetheriaRuntimeEntityContactSnapshot("
|
|
};
|
|
var missingZoneRenderSnapshotSymbols = requiredZoneRenderSnapshotSymbols
|
|
.Where(symbol => !zoneRenderDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingZoneRenderSnapshotSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Managed daemon entity snapshot access must live on AetheriaRuntimeZoneRenderDocument instead of a projector-shaped helper: " +
|
|
string.Join(", ", missingZoneRenderSnapshotSymbols));
|
|
}
|
|
|
|
var requiredObservedFrameApplierSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityObservedFrameApplier",
|
|
"public bool ApplyLatestZoneRender()",
|
|
"observer.LastRenderView?.ZoneRender",
|
|
"private bool TryRestoreEntityGraphFromZoneRender(",
|
|
"render.CreateEntitySnapshots()",
|
|
".OrderBy(entity => entity.EntityIndex)",
|
|
"_entityRestorer.Replace(entitySnapshots, currentEntityKey, _getZone())",
|
|
"_entityRestorer.TryApplyInPlace(",
|
|
"_entityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out var currentEntity)",
|
|
"_entityIndex.PresentationEntitiesByDaemonIndex",
|
|
"_zoneContextFactory.ResolveContext(targetZone, render)",
|
|
"zoneRenderer?.LoadDaemonZoneView(_entityIndex.PresentationEntitiesByDaemonIndex, render)",
|
|
"zoneRenderer?.ApplyZoneRender(render)",
|
|
"zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render)",
|
|
"_restoreCurrentEntityBinding(currentEntity)"
|
|
};
|
|
var missingObservedFrameApplierSymbols = requiredObservedFrameApplierSymbols
|
|
.Where(symbol => !observedFrameApplier.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingObservedFrameApplierSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity observer frame restoration must live in AetheriaUnityObservedFrameApplier instead of ActionGameManager: " +
|
|
string.Join(", ", missingObservedFrameApplierSymbols));
|
|
}
|
|
|
|
var forbiddenManagerFrameApplicationSymbols = new[]
|
|
{
|
|
"_lastAppliedAuthoritativeDaemonFrameId",
|
|
"_lastAppliedAuthoritativeDaemonFramePath",
|
|
"_lastAppliedAuthoritativeDaemonRunId",
|
|
"_lastAppliedAuthoritativeDaemonZoneIndex",
|
|
"private bool TryRestoreEntityGraphFromZoneRender(",
|
|
"AetheriaRuntimeGameDocuments.ZoneRender(observed.Frame)",
|
|
"AetheriaRuntimeEntitySnapshotProjector.CreateSnapshots(runId, daemonZone)",
|
|
"ObservedEntityRestorer.Replace(entitySnapshots, currentEntityKey, Zone)",
|
|
"ObservedEntityRestorer.TryApplyInPlace(",
|
|
"ZoneRenderer?.LoadDaemonZoneView(_presentationEntityIndex.PresentationEntitiesByDaemonIndex, render)",
|
|
"ZoneRenderer?.ApplyZoneRender(render)",
|
|
"ZoneRenderer?.RestoreDroppedPickupsFromDaemonZoneState(daemonZone)",
|
|
"render.ActionBarBindings",
|
|
"private static AetheriaRuntimeActionBarBindingSnapshot ToActionBarBindingSnapshot("
|
|
};
|
|
var managerFrameApplicationHits = forbiddenManagerFrameApplicationSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerFrameApplicationHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns authoritative daemon frame restoration instead of delegating to AetheriaUnityObservedFrameApplier: " +
|
|
string.Join(", ", managerFrameApplicationHits));
|
|
}
|
|
|
|
var requiredPilotFrameControllerSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityPilotFrameController",
|
|
"public void Tick(Entity currentEntity, float deltaTime, float timeSeconds)",
|
|
"TargetPresentation?.Tick(currentEntity, timeSeconds)",
|
|
"Input.Player.Look.ReadValue<Vector2>()",
|
|
"PilotCommands.RequestLookDirection(viewDirection)",
|
|
"renderSettings.NormalizeHeatstrokePost(currentEntity.Heatstroke)",
|
|
"renderSettings.ResolveSevereHeatstrokePostWeight(currentEntity.Heatstroke, timeSeconds)",
|
|
"Input.Player.Move.ReadValue<Vector2>()",
|
|
"PilotCommands.RequestMoveVector(movement)",
|
|
"Input.Player.TractorBeam.ReadValue<float>()",
|
|
"PilotCommands.RequestTractorPower(Saturate("
|
|
};
|
|
var missingPilotFrameControllerSymbols = requiredPilotFrameControllerSymbols
|
|
.Where(symbol => !pilotFrameController.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingPilotFrameControllerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity observer pilot-frame input and presentation policy must live in AetheriaUnityPilotFrameController instead of ActionGameManager: " +
|
|
string.Join(", ", missingPilotFrameControllerSymbols));
|
|
}
|
|
|
|
var forbiddenManagerPilotFrameSymbols = new[]
|
|
{
|
|
"Input.Player.Look.ReadValue<Vector2>()",
|
|
"Input.Player.Move.ReadValue<Vector2>()",
|
|
"Input.Player.TractorBeam.ReadValue<float>()",
|
|
"PilotCommands.RequestLookDirection(_viewDirection)",
|
|
"PilotCommands.RequestMoveVector(movement)",
|
|
"PilotCommands.RequestTractorPower(Saturate(",
|
|
"HeatstrokePost.weight = (float)renderSettings.NormalizeHeatstrokePost(",
|
|
"SevereHeatstrokePost.weight ="
|
|
};
|
|
var managerPilotFrameHits = forbiddenManagerPilotFrameSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerPilotFrameHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns undocked pilot-frame input/presentation internals: " +
|
|
string.Join(", ", managerPilotFrameHits));
|
|
}
|
|
|
|
var requiredPilotOperationControllerSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityPilotOperationController",
|
|
"public void RequestInteract()",
|
|
"public void RequestTargetSelection(Entity target)",
|
|
"public void RequestTargetReticle()",
|
|
"public void RequestDock()",
|
|
"public void RequestUndock()",
|
|
"_presentationEntityIndex.TryGetRecordKeyForPresentationEntity(target, out var targetEntityKey)",
|
|
"Submit(operations => operations.Interact(), \"interact\")",
|
|
"Submit(operations => operations.ClearTarget(), \"target clear\")",
|
|
"operations => operations.SetTarget(targetEntityKey)",
|
|
"operations => operations.TargetNearest()",
|
|
"operations => operations.TargetNext()",
|
|
"operations => operations.TargetPrevious()",
|
|
"operations => operations.TargetReticle(",
|
|
"operations => operations.SetOverrideShutdown(enabled)",
|
|
"operations => operations.SensorPing()",
|
|
"operations => operations.SetHeatsinksEnabled(enabled)",
|
|
"operations => operations.ToggleShieldEnabled()",
|
|
"operations => operations.DockNearest()",
|
|
"operations => operations.Undock()",
|
|
"_resolvePilotCommands()?.TrySubmit(submit, label)"
|
|
};
|
|
var missingPilotOperationControllerSymbols = requiredPilotOperationControllerSymbols
|
|
.Where(symbol => !pilotOperationController.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingPilotOperationControllerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity pilot operation lowering must live in AetheriaUnityPilotOperationController instead of ActionGameManager: " +
|
|
string.Join(", ", missingPilotOperationControllerSymbols));
|
|
}
|
|
|
|
var forbiddenManagerOperationLoweringSymbols = new[]
|
|
{
|
|
"PilotCommands.TrySubmit(",
|
|
"operations => operations.Interact()",
|
|
"operations => operations.ClearTarget()",
|
|
"operations => operations.SetTarget(",
|
|
"operations => operations.TargetNearest()",
|
|
"operations => operations.TargetNext()",
|
|
"operations => operations.TargetPrevious()",
|
|
"operations => operations.TargetReticle(",
|
|
"operations => operations.SetOverrideShutdown(",
|
|
"operations => operations.SensorPing()",
|
|
"operations => operations.SetHeatsinksEnabled(",
|
|
"operations => operations.ToggleShieldEnabled()",
|
|
"operations => operations.DockNearest()",
|
|
"operations => operations.Undock()"
|
|
};
|
|
var managerOperationLoweringHits = forbiddenManagerOperationLoweringSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerOperationLoweringHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns typed daemon operation lowering instead of delegating to AetheriaUnityPilotOperationController: " +
|
|
string.Join(", ", managerOperationLoweringHits));
|
|
}
|
|
|
|
var requiredTargetContactQuerySymbols = new[]
|
|
{
|
|
"public Func<AetheriaRuntimeZoneContactsDocument> ResolveZoneContacts { get; set; }",
|
|
"public Entity GetObservedTarget(Entity observer)",
|
|
"private float GetObservedInfoGathered(Entity observer, Entity target)",
|
|
"private bool IsObservedHostileContact(Entity observer, Entity target)",
|
|
"private AetheriaRuntimeZoneContactRow[] GetObservedVisibleContacts(",
|
|
"private bool TryQueryEntityContact(",
|
|
"private bool TryQueryEntityTarget(",
|
|
"AetheriaRuntimeZoneContactRow",
|
|
"AetheriaRuntimeZoneTargetRow",
|
|
"ResolveZoneContacts?.Invoke()",
|
|
"ResolveEntity?.Invoke(targetEntityIndex)"
|
|
};
|
|
var missingTargetContactQuerySymbols = requiredTargetContactQuerySymbols
|
|
.Where(symbol => !targetPresentation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTargetContactQuerySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity observer target/contact projection must live in AetheriaUnityTargetPresentation instead of ActionGameManager: " +
|
|
string.Join(", ", missingTargetContactQuerySymbols));
|
|
}
|
|
|
|
if (targetPresentation.Contains("CultMeshReactiveDocument<AetheriaRuntimeZoneContactsDocument>", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains(".ReactiveZoneContacts(\"unity-observed-target-query\")", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains("_zoneContacts?.Dispose()", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains("_zoneContacts?.Current", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains("AetheriaRuntimeZoneContactsSession _zoneContacts", StringComparison.Ordinal) ||
|
|
targetPresentation.Contains(".ObserveZoneContacts()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity observer target/contact projection still routes zone contacts through legacy session/reactive wrapper state instead of named current typed state.");
|
|
}
|
|
|
|
var forbiddenManagerTargetQuerySymbols = new[]
|
|
{
|
|
"private bool TryQueryDaemonEntityContact(",
|
|
"private bool TryQueryDaemonEntityTarget(",
|
|
"private Entity GetObservedTarget(",
|
|
"private float GetObservedInfoGathered(",
|
|
"private bool IsObservedHostileContact(",
|
|
"AetheriaRuntimeDaemonRenderQueries.TryQueryEntityContact(",
|
|
"AetheriaRuntimeDaemonRenderQueries.TryQueryEntityTarget("
|
|
};
|
|
var managerTargetQueryHits = forbiddenManagerTargetQuerySymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerTargetQueryHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns observed daemon target/contact query internals: " +
|
|
string.Join(", ", managerTargetQueryHits));
|
|
}
|
|
|
|
if (!currentEntityBinder.Contains("public void RestoreBinding(", StringComparison.Ordinal) ||
|
|
!currentEntityBinder.Contains("var dockParent = ResolveDockParent?.Invoke(currentEntity)", StringComparison.Ordinal) ||
|
|
!currentEntityBinder.Contains("CurrentEntityPresentation?.BindDocked(", StringComparison.Ordinal) ||
|
|
!currentEntityBinder.Contains("CurrentEntityPresentation?.BindUndocked(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity observer current-entity binding no longer flows through the dedicated current entity binder.");
|
|
}
|
|
|
|
var forbiddenObservedReadShims = new[]
|
|
{
|
|
"TryGetObservedGalaxy(out Galaxy galaxy)",
|
|
"TryGetObservedZoneSnapshot(",
|
|
"TryGetObservedRunZone("
|
|
};
|
|
|
|
var observedReadShimHits = forbiddenObservedReadShims
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (observedReadShimHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must not expose broad observed galaxy/zone read shims; clients read typed documents through AetheriaClient: " +
|
|
string.Join(", ", observedReadShimHits));
|
|
}
|
|
|
|
if (actionGameManager.Contains("private Galaxy ObservedGalaxy", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("ObservedGalaxy = boot.ObservedGalaxy", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("() => ObservedGalaxy", StringComparison.Ordinal) ||
|
|
observedZoneContextFactory.Contains("Func<Galaxy> resolveObservedGalaxy", StringComparison.Ordinal) ||
|
|
observedZoneContextFactory.Contains("_resolveObservedGalaxy", StringComparison.Ordinal) ||
|
|
observedZoneContextFactory.Contains("_observedGalaxy ??=", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must not own the raw observed galaxy projection; gameplay boot may pass it once into the legacy Zone construction adapter only.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("public static void ProjectObservedDaemonRun(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("ActionGameManager.ProjectObservedDaemonRun(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon-run projection must not be routed through ActionGameManager; use the explicit observed-run projection holder.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("public static bool IsTutorial", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("ActionGameManager.IsTutorial", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must not mirror tutorial mode as public manager-global gameplay state; tutorial/run mode belongs to managed daemon run documents.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("public static DirectoryInfo GameDataDirectory", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public static string RuntimeStateFilePath", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("ActionGameManager.GameDataDirectory", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("ActionGameManager.RuntimeStateFilePath", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime path access must live in AetheriaUnityRuntimePaths, not ActionGameManager gameplay state.");
|
|
}
|
|
|
|
var volumeSamplingPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "VolumeSampling.cs");
|
|
var volumeSampling = File.Exists(volumeSamplingPath)
|
|
? File.ReadAllText(volumeSamplingPath)
|
|
: throw new InvalidOperationException("Cannot verify presentation environment ownership; VolumeSampling.cs is missing.");
|
|
if (actionGameManager.Contains("CurrentEnvironment", StringComparison.Ordinal) ||
|
|
volumeSampling.Contains("ActionGameManager.Instance?.CurrentEnvironment", StringComparison.Ordinal) ||
|
|
volumeSampling.Contains("ActionGameManager.Instance.CurrentEnvironment", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager must not expose presentation environment as manager-global gameplay state; visual sampling should read presentation settings directly.");
|
|
}
|
|
|
|
var sceneSplatSourcePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaSceneRenderSplatSource.cs");
|
|
if (File.Exists(sceneSplatSourcePath) ||
|
|
volumeSampling.Contains("AetheriaSceneRenderSplatSource", StringComparison.Ordinal) ||
|
|
volumeSampling.Contains("ResolveSceneSplatDocument", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity scene mocks must not publish a competing fog-splat document; VolumeSampling may lower daemon-owned runtime splats only.");
|
|
}
|
|
|
|
var mapRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MapRenderer.cs");
|
|
var mapRenderer = File.Exists(mapRendererPath)
|
|
? File.ReadAllText(mapRendererPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon current-zone UI projection; MapRenderer.cs is missing.");
|
|
var sectorRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs");
|
|
var sectorRenderer = File.Exists(sectorRendererPath)
|
|
? File.ReadAllText(sectorRendererPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon current-zone UI projection; SectorRenderer.cs is missing.");
|
|
var sectorMapPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorMap.cs");
|
|
var sectorMap = File.Exists(sectorMapPath)
|
|
? File.ReadAllText(sectorMapPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon current-zone UI projection; SectorMap.cs is missing.");
|
|
|
|
if (mapRenderer.Contains("GameManager.Zone.GalaxyZone", StringComparison.Ordinal) ||
|
|
sectorRenderer.Contains("GameManager.Zone.GalaxyZone", StringComparison.Ordinal) ||
|
|
sectorRenderer.Contains("GameManager.CurrentEntity.Zone.GalaxyZone", StringComparison.Ordinal) ||
|
|
mapRenderer.Contains("GameManager.CurrentDaemonGalaxyZone", StringComparison.Ordinal) ||
|
|
sectorRenderer.Contains("GameManager.CurrentDaemonGalaxyZone", StringComparison.Ordinal) ||
|
|
mapRenderer.Contains("GameManager.ZoneRenderer", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public GalaxyZone CurrentDaemonGalaxyZone", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Map and sector UI must read current-zone identity from the daemon-observed run, not Unity's mirrored Zone hierarchy.");
|
|
}
|
|
|
|
var compactMapRenderer = CompactSource(mapRenderer);
|
|
var compactSectorRenderer = CompactSource(sectorRenderer);
|
|
var compactSectorMap = CompactSource(sectorMap);
|
|
if (!mapRenderer.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) ||
|
|
!compactMapRenderer.Contains(".MapViewportDocuments(viewport,\"unity-map-renderer\")", StringComparison.Ordinal) ||
|
|
!compactMapRenderer.Contains(CompactSource("AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-map-renderer\")"), StringComparison.Ordinal) ||
|
|
!compactMapRenderer.Contains(CompactSource("_runtimeState.PlayerSettings.Latest()"), StringComparison.Ordinal) ||
|
|
!sectorRenderer.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) ||
|
|
!sectorRenderer.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-renderer\")", StringComparison.Ordinal) ||
|
|
!sectorRenderer.Contains("RuntimeState.ZoneDetailsSurface(zoneIndex).Latest()", StringComparison.Ordinal) ||
|
|
!sectorRenderer.Contains("RuntimeState.CurrentZone.Latest()", StringComparison.Ordinal) ||
|
|
!sectorMap.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) ||
|
|
!sectorMap.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-map\")", StringComparison.Ordinal) ||
|
|
!sectorMap.Contains("_sectorMap = _runtimeState.SectorMap.Latest()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Map and sector UI must request managed typed map, zone, and surface documents through the Unity runtime client provider.");
|
|
}
|
|
|
|
var forbiddenMapRendererSettingsSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument>",
|
|
".ReactivePlayerSettingsDocument(\"unity-map-renderer\")",
|
|
"AetheriaRuntimePlayerSettingsSession",
|
|
".ObservePlayer()",
|
|
"_playerSettings?.Dispose()"
|
|
}
|
|
.Where(symbol => mapRenderer.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenMapRendererSettingsSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MapRenderer should sample player settings through current typed state; only viewport rendering documents should stay reactive: " +
|
|
string.Join(", ", forbiddenMapRendererSettingsSymbols));
|
|
}
|
|
|
|
if (sectorMap.Contains("ActionGameManager.ObservedGalaxy", StringComparison.Ordinal) ||
|
|
sectorRenderer.Contains("ActionGameManager.ObservedGalaxy", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Map and sector UI must not read the raw ObservedGalaxy projection directly.");
|
|
}
|
|
|
|
var forbiddenSectorMapSymbols = new[]
|
|
{
|
|
"CultMeshReactiveDocument<AetheriaRuntimeSectorMapDocument>",
|
|
".ReactiveSectorMap(\"unity-sector-map\")",
|
|
"AetheriaRuntimeSectorMapSession",
|
|
".ObserveSectorMap()",
|
|
"_sectorMapDocument?.Dispose()"
|
|
}
|
|
.Where(symbol => sectorMap.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenSectorMapSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SectorMap should sample the sector map through named current typed state instead of owning a reactive/session handle: " +
|
|
string.Join(", ", forbiddenSectorMapSymbols));
|
|
}
|
|
|
|
var requiredDaemonControlValidationSymbols = new[]
|
|
{
|
|
"case AetheriaRuntimeDaemonCommandKinds.SetTractorPower:",
|
|
"!IsNormalizedScalar(command.ScalarValue)",
|
|
"private static bool IsNormalizedScalar(double value)",
|
|
"!IsFinite(command.DirectionX)",
|
|
"!IsFinite(command.DirectionY)"
|
|
};
|
|
var missingDaemonControlValidationSymbols = requiredDaemonControlValidationSymbols
|
|
.Where(symbol => !daemonOperationsSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonControlValidationSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon continuous controls no longer validate normalized movement and tractor inputs: " +
|
|
string.Join(", ", missingDaemonControlValidationSymbols));
|
|
}
|
|
|
|
if (actionGameManager.Contains("EntityConstructionBlueprintMaterializer.InstantiateAuthoritativeFromBlueprint", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("EntityConstructionBlueprintProjector.InstantiateAuthoritativeFromBlueprint", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("EntityConstructionBlueprintProjector.InstantiateFromBlueprint", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity frame application must project observed daemon state, not instantiate authoritative gameplay entities.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("galaxyZone.Contents", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity gameplay must not cache observed runtime zone context on the projected GalaxyZone model.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("ObservedGalaxy.Zones[run.CurrentZoneIndex]", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity gameplay must resolve observed zones by daemon zone identity, not projected array slot.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("FindDaemonZoneSnapshot(", StringComparison.Ordinal) ||
|
|
sectorRenderer.Contains("FindDaemonZoneSnapshot(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity UI must query daemon zone snapshots by daemon zone identity instead of passing projected zone objects.");
|
|
}
|
|
|
|
if (galaxy.Contains("public Zone Contents", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Projected GalaxyZone must not own Unity runtime Zone contents; observed zone context belongs to the client lowerer.");
|
|
}
|
|
|
|
var requiredObservedZoneIdentitySymbols = new[]
|
|
{
|
|
"public int ZoneIndex = -1;",
|
|
"ZoneIndex = zone.ZoneIndex,",
|
|
"new GalaxyZone { ZoneIndex = index, Position = v }"
|
|
};
|
|
var missingObservedZoneIdentitySymbols = requiredObservedZoneIdentitySymbols
|
|
.Where(symbol => !galaxy.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingObservedZoneIdentitySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Projected GalaxyZone no longer preserves daemon zone identity: " +
|
|
string.Join(", ", missingObservedZoneIdentitySymbols));
|
|
}
|
|
|
|
var contextPrepRendererLoads = FindMethodScopedLineHits(
|
|
actionGameManager,
|
|
new[] { "LoadDaemonZoneView(", "BindToEntity(" })
|
|
.Where(hit => hit.MethodName == "ResolveContext")
|
|
.Select(hit => $"{hit.MethodName}:{hit.LineNumber}: {hit.Line.Trim()}")
|
|
.ToArray();
|
|
if (contextPrepRendererLoads.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity zone context prep must not load renderer instances before observed daemon facades have been rebuilt: " +
|
|
string.Join("; ", contextPrepRendererLoads));
|
|
}
|
|
|
|
var unauthorizedEntityProjectionHits = FindMethodScopedLineHits(
|
|
actionGameManager,
|
|
new[] { "Zone.Entities.Add(", "Zone.Entities.Remove(", "Zone.Agents.Clear()" })
|
|
.Select(hit => $"{hit.MethodName}:{hit.LineNumber}: {hit.Line.Trim()}")
|
|
.ToArray();
|
|
if (unauthorizedEntityProjectionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity gameplay must not rebuild renderer-local Zone entity membership while lowering daemon snapshots: " +
|
|
string.Join("; ", unauthorizedEntityProjectionHits));
|
|
}
|
|
|
|
var zonePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs");
|
|
var zoneSource = File.Exists(zonePath)
|
|
? File.ReadAllText(zonePath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; Zone.cs is missing.");
|
|
var materializerPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "EntityConstructionBlueprintMaterializer.cs");
|
|
var materializerSource = File.Exists(materializerPath)
|
|
? File.ReadAllText(materializerPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity observer authority; EntityConstructionBlueprintMaterializer.cs is missing.");
|
|
|
|
var requiredProjectionBoundarySymbols = new[]
|
|
{
|
|
"public static Galaxy ProjectObservedDaemonRun",
|
|
"private Galaxy(",
|
|
"AetheriaRuntimeRunCheckpointCommit run,",
|
|
"public static class EntityConstructionBlueprintCapture",
|
|
"public static EntityConstructionBlueprint Capture(Entity entity)",
|
|
"public static class EntityConstructionBlueprintMaterializer",
|
|
"public static Entity InstantiateAuthoritativeFromBlueprint",
|
|
"public static Entity MaterializeObservedFromBlueprint",
|
|
"private static Entity BuildFromBlueprint",
|
|
"EntityConstructionBlueprintMaterializer.InstantiateAuthoritativeFromBlueprint(_itemManager, this, entityBlueprint)"
|
|
};
|
|
var projectionBoundaryCorpus = materializerSource + "\n" + zoneSource + "\n" + galaxy;
|
|
var missingProjectionBoundarySymbols = requiredProjectionBoundarySymbols
|
|
.Where(symbol => !projectionBoundaryCorpus.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingProjectionBoundarySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Entity projection no longer separates authoritative construction from observed daemon lowering: " +
|
|
string.Join(", ", missingProjectionBoundarySymbols));
|
|
}
|
|
|
|
if (galaxy.Contains("public Galaxy(\r\n AetheriaRuntimeRunCheckpointCommit run,", StringComparison.Ordinal) ||
|
|
galaxy.Contains("public Galaxy(\n AetheriaRuntimeRunCheckpointCommit run,", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon-observed galaxy projection must be reached through ProjectObservedDaemonRun, not a public constructor.");
|
|
}
|
|
|
|
var forbiddenDaemonQueueLanguage = new[]
|
|
{
|
|
"observer.Queue",
|
|
"_lastQueuedDaemon",
|
|
"_hasQueuedDaemon",
|
|
"Failed to queue Aetheria daemon",
|
|
"Queued Aetheria daemon"
|
|
};
|
|
var forbiddenDaemonQueueHits = forbiddenDaemonQueueLanguage
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenDaemonQueueHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity gameplay still uses queue-shaped daemon language instead of typed sent operations: " +
|
|
string.Join(", ", forbiddenDaemonQueueHits));
|
|
}
|
|
|
|
if (daemonObserver.Contains("public AetheriaRuntimeDaemonCommandEnvelope Queue", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("Queued Aetheria daemon command", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaDaemonObserver still exposes queue-shaped public daemon APIs instead of typed Operations.");
|
|
}
|
|
|
|
var forbiddenManagerFacadeSymbols = new[]
|
|
{
|
|
"public static ActionGameManager Instance",
|
|
"public Entity CurrentEntity",
|
|
"public ItemManager ItemManager",
|
|
"public Zone Zone",
|
|
"public EntitySettings NewEntitySettings",
|
|
"using Ink;",
|
|
"using Ink.Runtime;",
|
|
"private List<Story>",
|
|
"private readonly (float2 direction, string name)[] _directions",
|
|
"private static float Unlerp(",
|
|
"DeathPostTransitionTime",
|
|
"HypothermiaPost",
|
|
"SevereHypothermiaPost",
|
|
"WormholeCamera",
|
|
"public SectorMap SectorMap",
|
|
"public EventLog EventLog",
|
|
"public void EnablePlayerInput()",
|
|
"public void DisablePlayerInput()"
|
|
};
|
|
var managerFacadeHits = forbiddenManagerFacadeSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerFacadeHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still exposes Unity gameplay facade state as public API: " +
|
|
string.Join(", ", managerFacadeHits));
|
|
}
|
|
|
|
var requiredDaemonNavigationAuthoritySymbols = new[]
|
|
{
|
|
"ApplyEnterWormholeIntent(run, command, context.Intents,",
|
|
"ApplyTowToStation(run, command, context.DockingDistance)",
|
|
"intents.Wormholes.Add",
|
|
"entity.WormholeTransition != null",
|
|
"StepWormholeTransitions(",
|
|
"AetheriaRuntimeDaemonOperations.MoveEntityToZone(",
|
|
"entity.WormholeTransition = null",
|
|
"run.CurrentZoneIndex = targetZoneIndex",
|
|
"run.CurrentEntityKey = movedEntityKey",
|
|
"run.DiscoveredZoneIndices = discovered.ToArray()"
|
|
};
|
|
var daemonNavigationCorpus = daemonOperationsSource + "\n" + daemonSimulationSource;
|
|
var missingDaemonNavigationAuthoritySymbols = requiredDaemonNavigationAuthoritySymbols
|
|
.Where(symbol => !daemonNavigationCorpus.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonNavigationAuthoritySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon navigation no longer validates intent then commits canonical transition state inside daemon-owned operations/simulation: " +
|
|
string.Join(", ", missingDaemonNavigationAuthoritySymbols));
|
|
}
|
|
|
|
var requiredSentOperationSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityPilotCommandSender",
|
|
"_lastSentDaemonMoveVector",
|
|
"_lastSentDaemonLookDirection",
|
|
"_lastSentDaemonTractorPower",
|
|
"_hasSentDaemonMoveVector",
|
|
"_hasSentDaemonLookDirection",
|
|
"_hasSentDaemonTractorPower",
|
|
"Func<AetheriaControl> _resolveControl",
|
|
"Action<AetheriaControl> submit",
|
|
"submit(control)",
|
|
"Failed to send Aetheria daemon pilot {label} operation",
|
|
"operations => operations.SetMoveVector",
|
|
"operations => operations.SetLookDirection",
|
|
"operations => operations.SetTractorPower"
|
|
};
|
|
var missingSentOperationSymbols = requiredSentOperationSymbols
|
|
.Where(symbol => !pilotCommandSender.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSentOperationSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity pilot command sender no longer describes daemon input as sent typed operations: " +
|
|
string.Join(", ", missingSentOperationSymbols));
|
|
}
|
|
|
|
var forbiddenSentOperationSymbols = new[]
|
|
{
|
|
"Func<AetheriaClient> _resolveClient",
|
|
"submit(client.Control)",
|
|
"Func<AetheriaDaemonObserver> _resolveObserver",
|
|
"Action<AetheriaRuntimeDaemonOperationsClient> submit",
|
|
"submit(observer.Operations)"
|
|
};
|
|
var sentOperationHits = forbiddenSentOperationSymbols
|
|
.Where(symbol => pilotCommandSender.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (sentOperationHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity pilot command sender still routes typed input through the daemon observer operation escape hatch: " +
|
|
string.Join(", ", sentOperationHits));
|
|
}
|
|
|
|
var forbiddenManagerPilotCommandSymbols = new[]
|
|
{
|
|
"private bool TrySubmitPilotOperation(",
|
|
"_lastSentDaemonMoveVector",
|
|
"_lastSentDaemonLookDirection",
|
|
"_lastSentDaemonTractorPower",
|
|
"_hasSentDaemonMoveVector",
|
|
"_hasSentDaemonLookDirection",
|
|
"_hasSentDaemonTractorPower",
|
|
"DaemonMoveCommandIntervalSeconds",
|
|
"DaemonLookCommandIntervalSeconds",
|
|
"DaemonTractorCommandIntervalSeconds"
|
|
};
|
|
var managerPilotCommandHits = forbiddenManagerPilotCommandSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerPilotCommandHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still owns pilot command transport/throttling state instead of delegating to AetheriaUnityPilotCommandSender: " +
|
|
string.Join(", ", managerPilotCommandHits));
|
|
}
|
|
|
|
var requiredDockingLoweringSymbols = new[]
|
|
{
|
|
"private AetheriaUnityPilotOperationController PilotOperationController =>",
|
|
"PilotOperationController = PilotOperationController",
|
|
"public sealed class AetheriaUnityPilotOperationController",
|
|
"operations => operations.DockNearest()",
|
|
"operations => operations.Undock()",
|
|
"operations => operations.Interact()"
|
|
};
|
|
|
|
var dockingLoweringSource = actionGameManager + "\n" + pilotOperationController;
|
|
var missingDockingLoweringSymbols = requiredDockingLoweringSymbols
|
|
.Where(symbol => !dockingLoweringSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingDockingLoweringSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity docking/towing UI no longer lowers intent through daemon commands: " +
|
|
string.Join(", ", missingDockingLoweringSymbols));
|
|
}
|
|
|
|
var forbiddenUnityDockingAuthoritySymbols = new[]
|
|
{
|
|
"public void Dock()",
|
|
"public void Undock()",
|
|
"public void TowShip()",
|
|
"public ActionGameManager GameManager",
|
|
"ActionGameManager GameManager",
|
|
"private void RequestDock()",
|
|
"private void RequestUndock()",
|
|
"private void RequestInteract()",
|
|
"private void RequestTowToStation()",
|
|
"PilotOperationController.RequestTowToStation(_towingStation)",
|
|
"public void RequestTowToStation(Entity towingStation)",
|
|
"_towingStation",
|
|
"UpdateTowingStation",
|
|
"ResolveObservedEntityZoneIndex(_towingStation)",
|
|
"_towingStation.CultPositionXZ.x",
|
|
"_towingStation.CultPositionXZ.y",
|
|
"Missing cockpit component",
|
|
"Missing thruster component",
|
|
"Missing reactor component",
|
|
"Must empty docking bay",
|
|
"ZoneRenderer.WormholeInstances.Keys",
|
|
"RequestEnterWormhole(wormhole);"
|
|
};
|
|
var unityDockingAuthorityHits = forbiddenUnityDockingAuthoritySymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (unityDockingAuthorityHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity docking/towing still exposes imperative local authority instead of daemon-bound requests: " +
|
|
string.Join(", ", unityDockingAuthorityHits));
|
|
}
|
|
|
|
var undockLocalAuthorityHits = FindMethodScopedLineHits(
|
|
actionGameManager,
|
|
new[] { "CurrentEntity?.Parent == null" })
|
|
.Where(hit => hit.MethodName == "TryRequestDaemonUndock")
|
|
.Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}")
|
|
.ToArray();
|
|
if (undockLocalAuthorityHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity undock request still rejects through renderer-local parent state instead of daemon acceptance: " +
|
|
string.Join("; ", undockLocalAuthorityHits));
|
|
}
|
|
|
|
var dockLocalAuthorityHits = FindMethodScopedLineHits(
|
|
actionGameManager,
|
|
new[] { "CurrentEntity?.Parent != null" })
|
|
.Where(hit => hit.MethodName == "TryRequestDaemonDock")
|
|
.Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}")
|
|
.ToArray();
|
|
if (dockLocalAuthorityHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity dock request still rejects through renderer-local parent state instead of daemon acceptance: " +
|
|
string.Join("; ", dockLocalAuthorityHits));
|
|
}
|
|
|
|
var dockTargetSelectionHits = FindMethodScopedLineHits(
|
|
actionGameManager,
|
|
new[] { "FindDaemonDockTarget", "Zone.Entities", "CultPositionXZ - CurrentEntity.CultPositionXZ" })
|
|
.Where(hit => hit.MethodName == "TryRequestDaemonDock" || hit.MethodName == "FindDaemonDockTarget")
|
|
.Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}")
|
|
.ToArray();
|
|
if (dockTargetSelectionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity dock request still selects docking targets from renderer-local zone state instead of daemon acceptance: " +
|
|
string.Join("; ", dockTargetSelectionHits));
|
|
}
|
|
|
|
var requiredDaemonDockNearestSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeDaemonCommandKinds.DockNearest",
|
|
"AetheriaRuntimeDaemonCommandKinds.Interact",
|
|
"internal AetheriaRuntimeDaemonCommandEnvelope DockNearest(",
|
|
"internal AetheriaRuntimeDaemonCommandEnvelope Interact(",
|
|
"ApplyDockNearestIntent(run, command, context, context.DockingDistance)",
|
|
"context.WormholeExitRadius",
|
|
"AetheriaRuntimeDaemonOperationContext.DefaultDockingDistance",
|
|
"AetheriaRuntimeDaemonOperationContext.DefaultWormholeExitRadius",
|
|
"var maximumDistance = defaultDockingDistance;",
|
|
"TryFindFirstDockTarget(",
|
|
"TryFindNearestWormholeTarget("
|
|
};
|
|
var daemonDockNearestSources = daemonDocuments + "\n" + daemonOperationClient + "\n" + daemonOperationsSource;
|
|
var missingDaemonDockNearestSymbols = requiredDaemonDockNearestSymbols
|
|
.Where(symbol => !daemonDockNearestSources.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonDockNearestSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon no longer owns first-eligible docking target selection: " +
|
|
string.Join(", ", missingDaemonDockNearestSymbols));
|
|
}
|
|
|
|
var unityOwnedInteractionTuningHits = FindMethodScopedLineHits(
|
|
actionGameManager,
|
|
new[] { "Settings.GameplaySettings.DockingDistance", "Settings.GameplaySettings.WormholeExitRadius" })
|
|
.Where(hit => hit.MethodName == "TryRequestDaemonDock" || hit.MethodName == "TryRequestDaemonInteract")
|
|
.Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}")
|
|
.ToArray();
|
|
if (unityOwnedInteractionTuningHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity interaction requests still carry GameSettings tuning instead of semantic daemon operations: " +
|
|
string.Join("; ", unityOwnedInteractionTuningHits));
|
|
}
|
|
|
|
var movementLocalAuthorityHits = FindMethodScopedLineHits(
|
|
actionGameManager,
|
|
new[] { "CurrentEntity?.Parent != null" })
|
|
.Where(hit => hit.MethodName == "TryRequestDaemonEnterWormhole" || hit.MethodName == "TryRequestDaemonTowToStation")
|
|
.Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}")
|
|
.ToArray();
|
|
if (movementLocalAuthorityHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity movement requests still reject through renderer-local parent state instead of daemon acceptance: " +
|
|
string.Join("; ", movementLocalAuthorityHits));
|
|
}
|
|
|
|
var requiredShieldToggleSymbols = new[]
|
|
{
|
|
"PilotOperationController.RequestShieldToggle()",
|
|
"operations => operations.ToggleShieldEnabled()",
|
|
"AetheriaRuntimeDaemonCommandKinds.ToggleShieldEnabled",
|
|
"ApplyToggleEquipmentBehaviorItem(run, command, \"Shield\""
|
|
};
|
|
var gameplayInputShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayInputShell.cs");
|
|
var gameplayInputShell = File.Exists(gameplayInputShellPath)
|
|
? File.ReadAllText(gameplayInputShellPath)
|
|
: throw new InvalidOperationException("Cannot verify Unity shield input lowering; AetheriaUnityGameplayInputShell.cs is missing.");
|
|
var unityPilotOperationSources = actionGameManager + "\n" + pilotOperationController + "\n" + gameplayInputShell;
|
|
var missingShieldToggleSymbols = requiredShieldToggleSymbols
|
|
.Where(symbol =>
|
|
!unityPilotOperationSources.Contains(symbol, StringComparison.Ordinal) &&
|
|
!daemonOperationsSource.Contains(symbol, StringComparison.Ordinal) &&
|
|
!daemonDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingShieldToggleSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shield input no longer lowers through a semantic daemon shield command: " +
|
|
string.Join(", ", missingShieldToggleSymbols));
|
|
}
|
|
|
|
var forbiddenUnityShieldAuthoritySymbols = new[]
|
|
{
|
|
"RequestShieldEnabled(",
|
|
"TryRequestDaemonShieldEnabled(",
|
|
"CurrentEntity.Shield.Item.Enabled",
|
|
"var shieldItem = CurrentEntity?.Shield?.Item",
|
|
"CurrentEntity.Equipment.IndexOf(shieldItem)"
|
|
};
|
|
var shieldAuthorityHits = forbiddenUnityShieldAuthoritySymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (shieldAuthorityHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity shield input still derives equipment authority from renderer-local shield components: " +
|
|
string.Join(", ", shieldAuthorityHits));
|
|
}
|
|
|
|
var forbiddenActionBarIntentAcceptanceSymbols = new[]
|
|
{
|
|
"observer == null || !observer.HasAuthoritativeState || equipmentIndex < 0 || behaviorIndex < 0",
|
|
"observer == null || !observer.HasAuthoritativeState || weaponGroup < 0"
|
|
};
|
|
var actionBarIntentAcceptanceHits = forbiddenActionBarIntentAcceptanceSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (actionBarIntentAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity action-bar intent requests still reject through renderer-local index checks instead of daemon authority: " +
|
|
string.Join(", ", actionBarIntentAcceptanceHits));
|
|
}
|
|
|
|
var requiredTargetAuthoritySymbols = new[]
|
|
{
|
|
"ApplySetTarget(run, command, context)",
|
|
"ApplyTargetCycle(run, command, TargetCycleMode.Nearest)",
|
|
"ApplyTargetCycle(run, command, TargetCycleMode.Next)",
|
|
"ApplyTargetCycle(run, command, TargetCycleMode.Previous)",
|
|
"ApplyTargetReticle(run, command)",
|
|
"VisibleHostileTargets(zone, actor, actorIndex)",
|
|
"actor.Contacts ?? Array.Empty<AetheriaRuntimeEntityContactCommit>()",
|
|
"contact.TargetEntityIndex == targetIndex && contact.Visible"
|
|
};
|
|
var missingTargetAuthoritySymbols = requiredTargetAuthoritySymbols
|
|
.Where(symbol => !daemonOperationsSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTargetAuthoritySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon target selection no longer validates visibility through authoritative contact state: " +
|
|
string.Join(", ", missingTargetAuthoritySymbols));
|
|
}
|
|
|
|
var requiredUnityTargetRequestSymbols = new[]
|
|
{
|
|
"RequestTargetNearest()",
|
|
"RequestTargetNext()",
|
|
"RequestTargetPrevious()",
|
|
"RequestTargetReticle()",
|
|
"operations => operations.TargetNearest()",
|
|
"operations => operations.TargetNext()",
|
|
"operations => operations.TargetPrevious()",
|
|
"operations => operations.TargetReticle("
|
|
};
|
|
var unityTargetRequestSources = actionGameManager + "\n" + pilotOperationController + "\n" + daemonOperations;
|
|
var missingUnityTargetRequestSymbols = requiredUnityTargetRequestSymbols
|
|
.Where(symbol => !unityTargetRequestSources.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingUnityTargetRequestSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity target cycling no longer lowers through typed daemon requests: " +
|
|
string.Join(", ", missingUnityTargetRequestSymbols));
|
|
}
|
|
|
|
var forbiddenUnityTargetCycleSymbols = new[]
|
|
{
|
|
".MaxBy(x => CultMath.math.length(x.CultPosition - CurrentEntity.CultPosition))",
|
|
".OrderBy(x => CultMath.math.length(x.CultPosition - CurrentEntity.CultPosition))",
|
|
".MaxBy(x => CultMath.math.dot(",
|
|
"Array.IndexOf(targets"
|
|
};
|
|
var unityTargetCycleHits = forbiddenUnityTargetCycleSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (unityTargetCycleHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity target cycling still orders or selects targets from renderer-local entity state instead of daemon contacts: " +
|
|
string.Join(", ", unityTargetCycleHits));
|
|
}
|
|
|
|
var requiredIntentAuthoritySymbols = new[]
|
|
{
|
|
"!HasWeaponGroup(entity, command.WeaponGroup)",
|
|
"!HasEquipmentBehavior(entity, command.EquipmentIndex, command.BehaviorIndex)",
|
|
"private static bool HasWeaponGroup(AetheriaRuntimeEntitySnapshotCommit entity, int weaponGroup)",
|
|
"private static bool HasEquipmentBehavior("
|
|
};
|
|
var missingIntentAuthoritySymbols = requiredIntentAuthoritySymbols
|
|
.Where(symbol => !daemonOperationsSource.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingIntentAuthoritySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon simulation intents no longer validate weapon-group and behavior indices against authoritative entity state: " +
|
|
string.Join(", ", missingIntentAuthoritySymbols));
|
|
}
|
|
|
|
var forbiddenUnityInputApplySymbols = new[]
|
|
{
|
|
"ApplyEnterWormhole(",
|
|
"ApplyLookDirection(",
|
|
"ApplyTractorPower(",
|
|
"ApplyTargetSelection(",
|
|
"ApplyOverrideShutdown(",
|
|
"ApplySensorPing(",
|
|
"ApplyHeatsinksEnabled(",
|
|
"ApplyShieldEnabled(",
|
|
"ApplyDock(",
|
|
"ApplyUndock("
|
|
};
|
|
var unityInputApplyHits = forbiddenUnityInputApplySymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (unityInputApplyHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity input still exposes Apply vocabulary for daemon-bound requests: " +
|
|
string.Join(", ", unityInputApplyHits));
|
|
}
|
|
|
|
var requiredTowDaemonSymbols = new[]
|
|
{
|
|
"TowToStation",
|
|
"AetheriaRuntimeDaemonCommandKinds.TowToStation",
|
|
"ApplyTowToStation(",
|
|
"double attachmentDistance",
|
|
"towing.ChildEntityIndices",
|
|
"command.TextValue, \"attach\"",
|
|
"attachmentDistance * attachmentDistance",
|
|
"command.TextValue, \"detach\"",
|
|
"zone.Orbits =",
|
|
"station.OrbitKey = orbitKey",
|
|
"public CultMeshOperationReceipt TowToStation(string stationEntityKey)"
|
|
};
|
|
|
|
var missingTowDaemonSymbols = requiredTowDaemonSymbols
|
|
.Where(symbol =>
|
|
!daemonDocuments.Contains(symbol, StringComparison.Ordinal) &&
|
|
!daemonOperationsSource.Contains(symbol, StringComparison.Ordinal) &&
|
|
!daemonIntent.Contains(symbol, StringComparison.Ordinal) &&
|
|
!daemonObserver.Contains(symbol, StringComparison.Ordinal) &&
|
|
!daemonOperations.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingTowDaemonSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Towing is not fully owned by daemon command state: " +
|
|
string.Join(", ", missingTowDaemonSymbols));
|
|
}
|
|
|
|
var forbiddenMenuSymbols = new[]
|
|
{
|
|
"new Galaxy(Settings",
|
|
"new Galaxy(settings",
|
|
"new Galaxy(",
|
|
"SectorGenerationSettings",
|
|
"RunGenerator",
|
|
"ZoneGenerator.GenerateZone"
|
|
};
|
|
|
|
var forbiddenMenuHits = forbiddenMenuSymbols
|
|
.Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (forbiddenMenuHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still has local run generation fallback on the gameplay boot path: " +
|
|
string.Join(", ", forbiddenMenuHits));
|
|
}
|
|
|
|
var requiredMenuSymbols = new[]
|
|
{
|
|
"TryStartDaemonObservedGame",
|
|
"ResolveSectorMap(stateBoot)",
|
|
"AetheriaUnityRuntimeClientProvider",
|
|
".RuntimeState(stateBoot, \"unity-main-menu\")",
|
|
".SectorMap",
|
|
".Latest()",
|
|
"sectorMap.FrameId",
|
|
"SceneManager.LoadScene(\"ARPG\")"
|
|
};
|
|
|
|
var missingMenuSymbols = requiredMenuSymbols
|
|
.Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingMenuSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu no longer boots gameplay strictly from an authoritative daemon frame: " +
|
|
string.Join(", ", missingMenuSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireDaemonEnergyAndThermalAuthority(string root)
|
|
{
|
|
var runtime = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime");
|
|
var energyPath = Path.Combine(runtime, "AetheriaRuntimeEnergySimulation.cs");
|
|
var thermalPath = Path.Combine(runtime, "AetheriaRuntimeThermalSimulation.cs");
|
|
var medicalPath = Path.Combine(runtime, "AetheriaRuntimeThermalMedicalSimulation.cs");
|
|
var settingsPath = Path.Combine(runtime, "AetheriaRuntimeDaemonSimulationSettings.cs");
|
|
var simulationPath = Path.Combine(runtime, "AetheriaRuntimeDaemonSimulation.cs");
|
|
var snapshotPath = Path.Combine(runtime, "AetheriaRuntimeSnapshotDocuments.cs");
|
|
var surfacePath = Path.Combine(runtime, "AetheriaRuntimeDaemonGameSurfaceBuilder.cs");
|
|
var operationsPath = Path.Combine(runtime, "AetheriaRuntimeDaemonOperations.cs");
|
|
|
|
var sources = new Dictionary<string, string>
|
|
{
|
|
[energyPath] = File.Exists(energyPath) ? File.ReadAllText(energyPath) : "",
|
|
[thermalPath] = File.Exists(thermalPath) ? File.ReadAllText(thermalPath) : "",
|
|
[medicalPath] = File.Exists(medicalPath) ? File.ReadAllText(medicalPath) : "",
|
|
[settingsPath] = File.Exists(settingsPath) ? File.ReadAllText(settingsPath) : "",
|
|
[simulationPath] = File.Exists(simulationPath) ? File.ReadAllText(simulationPath) : "",
|
|
[snapshotPath] = File.Exists(snapshotPath) ? File.ReadAllText(snapshotPath) : "",
|
|
[surfacePath] = File.Exists(surfacePath) ? File.ReadAllText(surfacePath) : ""
|
|
};
|
|
var required = new Dictionary<string, string[]>
|
|
{
|
|
[energyPath] = new[]
|
|
{
|
|
"public static class AetheriaRuntimeEnergySimulation",
|
|
"public static bool TryConsume(",
|
|
"public static void StepRadiators(",
|
|
"public static void SettleReactors(",
|
|
"AddCapacitorCharge("
|
|
},
|
|
[thermalPath] = new[]
|
|
{
|
|
"public static void EnsureTopology(",
|
|
"public static void UpdateEquipmentStates(",
|
|
"public static void AddHeatToEquipment(",
|
|
"public static void ApplyWear("
|
|
},
|
|
[medicalPath] = new[]
|
|
{
|
|
"public static class AetheriaRuntimeThermalMedicalSimulation",
|
|
"temperature > settings.HeatstrokeTemperature",
|
|
"settings.HeatstrokeRecoveryPerSecond * deltaSeconds",
|
|
"temperature < settings.HypothermiaTemperature",
|
|
"settings.HypothermiaRecoveryPerSecond * deltaSeconds",
|
|
"entity.Heatstroke > 0.99",
|
|
"entity.Hypothermia > 0.99"
|
|
},
|
|
[settingsPath] = new[]
|
|
{
|
|
"public double SevereThermalRiskThreshold",
|
|
"public double HeatstrokeTemperature",
|
|
"public double HeatstrokeMultiplier",
|
|
"public double HypothermiaTemperature",
|
|
"public double HypothermiaMultiplier"
|
|
},
|
|
[simulationPath] = new[]
|
|
{
|
|
"AetheriaRuntimeThermalSimulation.EnsureTopology(entity, catalog)",
|
|
"AetheriaRuntimeThermalSimulation.UpdateEquipmentStates(entity, catalog, deltaSeconds)",
|
|
"AetheriaRuntimeEnergySimulation.BeginTick(entity, catalog)",
|
|
"AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, deltaSeconds)",
|
|
"AetheriaRuntimeEnergySimulation.SettleReactors(entity, catalog, deltaSeconds)",
|
|
"AetheriaRuntimeThermalSimulation.Step(entity, deltaSeconds, catalog)",
|
|
"AetheriaRuntimeThermalMedicalSimulation.Step(",
|
|
"PublishThermalMedicalEvents(",
|
|
"CommitDestruction(run, zone, entity, -1,",
|
|
"Reason = target.CauseOfDeath",
|
|
"AetheriaRuntimeEnergySimulation.TryConsume(entity, catalog, demand)"
|
|
},
|
|
[snapshotPath] = new[]
|
|
{
|
|
"public IReadOnlyList<AetheriaRuntimeEquipmentStateCommit> EquipmentStates",
|
|
"public sealed class AetheriaRuntimeEquipmentStateCommit",
|
|
"public double ThermalPerformance",
|
|
"public double Wear",
|
|
"public bool Online",
|
|
"public string CauseOfDeath"
|
|
},
|
|
[surfacePath] = new[]
|
|
{
|
|
"[\"capacitorCharge\"]",
|
|
"[\"reactorDraw\"]",
|
|
"[\"radiatorTemperature\"]",
|
|
"[\"minimumEquipmentThermalPerformance\"]",
|
|
"[\"offlineEquipmentCount\"]",
|
|
"[\"cockpitTemperature\"]",
|
|
"[\"heatstrokeRisk\"]",
|
|
"[\"hypothermiaRisk\"]",
|
|
"[\"heatstrokePhasingFrequency\"]",
|
|
"[\"deathTransitionSeconds\"]",
|
|
"[\"causeOfDeath\"]"
|
|
}
|
|
};
|
|
var missing = required.SelectMany(pair => pair.Value
|
|
.Where(symbol => !sources[pair.Key].Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"))
|
|
.ToArray();
|
|
if (missing.Length > 0)
|
|
throw new InvalidOperationException(
|
|
"Daemon energy and thermal authority must execute one catalog-derived network and publish its facts through Eve: " +
|
|
string.Join("; ", missing));
|
|
|
|
var operations = File.Exists(operationsPath) ? File.ReadAllText(operationsPath) : "";
|
|
var forbiddenOperations = new[]
|
|
{
|
|
"ApplyCommitEnergy(",
|
|
"ApplySetHeat(",
|
|
"ApplySetEquipmentOnline(",
|
|
"ApplyEquipmentWear(",
|
|
"ApplySetHeatstroke(",
|
|
"ApplySetHypothermia(",
|
|
"ApplySetCauseOfDeath("
|
|
};
|
|
var surviving = forbiddenOperations.Where(symbol => operations.Contains(symbol, StringComparison.Ordinal)).ToArray();
|
|
if (surviving.Length > 0)
|
|
throw new InvalidOperationException(
|
|
"Client commands may request semantic behavior but may not directly write daemon energy, heat, wear, or equipment online state: " +
|
|
string.Join(", ", surviving));
|
|
|
|
var simulation = sources[simulationPath];
|
|
var ordered = new[]
|
|
{
|
|
"AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, deltaSeconds)",
|
|
"AetheriaRuntimeEnergySimulation.SettleReactors(entity, catalog, deltaSeconds)",
|
|
"AetheriaRuntimeThermalSimulation.Step(entity, deltaSeconds, catalog)"
|
|
};
|
|
if (!(simulation.IndexOf(ordered[0], StringComparison.Ordinal) < simulation.IndexOf(ordered[1], StringComparison.Ordinal) &&
|
|
simulation.IndexOf(ordered[1], StringComparison.Ordinal) < simulation.IndexOf(ordered[2], StringComparison.Ordinal)))
|
|
throw new InvalidOperationException(
|
|
"Daemon tick order must pump radiators before reactor settlement, then conduct and radiate the resulting cell heat.");
|
|
}
|
|
|
|
static void RequireThermalPresentationAssetAuthority(string root)
|
|
{
|
|
var assets = File.ReadAllText(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeAssets.cs"));
|
|
var daemon = File.ReadAllText(Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"));
|
|
var builder = File.ReadAllText(Path.Combine(root, "Assets", "Editor", "EveAssetBundleBuilder.cs"));
|
|
var migrator = File.ReadAllText(Path.Combine(root, "Assets", "Editor", "EveThermalProfileMigrator.cs"));
|
|
var sink = File.ReadAllText(Path.Combine(root, "Aetheria.Unity", "Assets", "AetheriaUnityThermalPresentationSink.cs"));
|
|
var manifest = File.ReadAllText(Path.Combine(root, "Aetheria.Unity", "Packages", "manifest.json"));
|
|
var required = new[]
|
|
{
|
|
"post.thermal.heatstroke", "post.thermal.severe-heatstroke",
|
|
"post.thermal.hypothermia", "post.thermal.severe-hypothermia", "post.death",
|
|
"Assets/Generated/Eve/Thermal/Heatstroke.asset",
|
|
"EveThermalProfileMigrator.EnsureGenerated()",
|
|
"[\"presentationRole\"] = entry.Ref.Metadata.TryGetValue",
|
|
"IEveUnityThermalPresentationAssetSink",
|
|
"assets.Heatstroke as VolumeProfile",
|
|
"_death.weight = frame.DeathWeight"
|
|
};
|
|
var body = assets + daemon + builder + migrator + sink + manifest;
|
|
var missing = required.Where(symbol => !body.Contains(symbol, StringComparison.Ordinal)).ToArray();
|
|
if (missing.Length > 0)
|
|
throw new InvalidOperationException(
|
|
"Thermal presentation assets must remain provider-owned, role-addressed CDN artifacts lowered by the generic EveUnity sink contract: " +
|
|
string.Join(", ", missing));
|
|
if (sink.Contains("HeatstrokeTemperature", StringComparison.Ordinal) ||
|
|
sink.Contains("HypothermiaTemperature", StringComparison.Ordinal) ||
|
|
sink.Contains("Mathf.Sin", StringComparison.Ordinal))
|
|
throw new InvalidOperationException(
|
|
"The Aetheria Unity thermal sink may bind profiles and weights but may not reconstruct medical or phasing authority.");
|
|
}
|
|
|
|
static void RequireCatalogHullPresentationAuthority(string root)
|
|
{
|
|
var assets = File.ReadAllText(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeAssets.cs"));
|
|
var surface = File.ReadAllText(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonGameSurfaceBuilder.cs"));
|
|
var soa = File.ReadAllText(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSoaFramePublisher.cs"));
|
|
var tick = File.ReadAllText(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonTickRunner.cs"));
|
|
var daemon = File.ReadAllText(Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"));
|
|
var builder = File.ReadAllText(Path.Combine(root, "Assets", "Editor", "EveAssetBundleBuilder.cs"));
|
|
var required = new[]
|
|
{
|
|
"catalog?.FindItem(entity.HullItemKey",
|
|
"HullPrefabAssetKey(ResolvePresentationHull(hull, catalog!).ItemKey)",
|
|
"HullPrefabAssetKey(item.ItemKey)",
|
|
"!string.IsNullOrWhiteSpace(item.HullPrefab)",
|
|
"ResolveEntityPrefabAssetRef(entity, catalog)",
|
|
"using var hotFrame = publisher.BuildCurrentZoneEntities(",
|
|
"realtimeDemand: hasRealtimeConsumers);",
|
|
"AetheriaRuntimeCatalogStore.OpenReadOnly(",
|
|
"AetheriaRuntimeAssets.ProjectManifest(catalog).Assets"
|
|
};
|
|
var body = assets + surface + soa + tick + daemon + builder;
|
|
var missing = required.Where(symbol => !body.Contains(symbol, StringComparison.Ordinal)).ToArray();
|
|
if (missing.Length > 0)
|
|
throw new InvalidOperationException(
|
|
"Typed hull identity must choose one provider-owned catalog prefab across the manifest, Eve scene, and SoA paths: " +
|
|
string.Join(", ", missing));
|
|
if (!Regex.IsMatch(
|
|
daemon,
|
|
@"publisher\.BuildCurrentZoneEntities\(\s*frame,\s*catalog,\s*realtimeDemand:\s*hasRealtimeConsumers\)",
|
|
RegexOptions.CultureInvariant))
|
|
throw new InvalidOperationException(
|
|
"The daemon hot SoA path must pass the same managed runtime catalog used by the asset manifest and Eve scene projection.");
|
|
|
|
var catalogLookup = assets.IndexOf("catalog?.FindItem(entity.HullItemKey", StringComparison.Ordinal);
|
|
var fallbackLookup = assets.IndexOf("var kind = (entity.Kind", StringComparison.Ordinal);
|
|
if (catalogLookup < 0 || fallbackLookup < 0 || catalogLookup > fallbackLookup)
|
|
throw new InvalidOperationException(
|
|
"Faction/category prefab fallbacks must not override an authored typed hull prefab.");
|
|
}
|
|
|
|
static void RequireUnityDoesNotCallSharedSimulationTicks(string root)
|
|
{
|
|
var checkedRoots = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI")
|
|
};
|
|
|
|
var forbiddenCallerSymbols = new[]
|
|
{
|
|
"Zone.Update(",
|
|
".Zone.Update(",
|
|
"CurrentEntity.Update(",
|
|
".CurrentEntity.Update(",
|
|
"entity.Update(delta",
|
|
"entity.Update(Time.deltaTime",
|
|
"ship.Update(delta",
|
|
"ship.Update(Time.deltaTime",
|
|
"agent.Update(delta",
|
|
"agent.Update(Time.deltaTime",
|
|
"equippedItem.Update(delta",
|
|
"equippedItem.Update(Time.deltaTime",
|
|
"alwaysUpdatedBehavior.Update(delta",
|
|
"alwaysUpdatedBehavior.Update(Time.deltaTime"
|
|
};
|
|
|
|
var hits = checkedRoots
|
|
.Where(Directory.Exists)
|
|
.SelectMany(path => Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories))
|
|
.Where(path => !Path.GetFileName(path).EndsWith("Tests.cs", StringComparison.Ordinal))
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenCallerSymbols.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 gameplay/UI still calls shared simulation tick methods instead of observing daemon frames: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var daemonTickRunnerPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeDaemonTickRunner.cs");
|
|
var daemonTickRunner = File.Exists(daemonTickRunnerPath)
|
|
? File.ReadAllText(daemonTickRunnerPath)
|
|
: throw new InvalidOperationException("Cannot verify shared simulation authority; daemon tick runner is missing.");
|
|
var daemonProgramPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs");
|
|
var daemonProgram = File.Exists(daemonProgramPath)
|
|
? File.ReadAllText(daemonProgramPath)
|
|
: throw new InvalidOperationException("Cannot verify shared simulation authority; daemon host is missing.");
|
|
|
|
var requiredDaemonTickSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeDaemonTickRunner",
|
|
"Tick(",
|
|
"AetheriaRuntimeDaemonFrameDocument.Create",
|
|
"BuildPublications",
|
|
"AetheriaRuntimeDaemonOperations.Execute("
|
|
};
|
|
|
|
var missingDaemonTickSymbols = requiredDaemonTickSymbols
|
|
.Where(symbol => !daemonTickRunner.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingDaemonTickSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared simulation ticks no longer produce daemon-owned managed frame documents: " +
|
|
string.Join(", ", missingDaemonTickSymbols));
|
|
}
|
|
|
|
var snapshotDocumentsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeSnapshotDocuments.cs");
|
|
var oldRuntimeSpecificAuthorityFiles = new[]
|
|
{
|
|
Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeRtsSimulation.cs"),
|
|
Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeRtsSimulation.cs.meta"),
|
|
Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeRtsDocuments.cs"),
|
|
Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeRtsViewportDocuments.cs")
|
|
};
|
|
var survivingRuntimeSpecificAuthorityFiles = oldRuntimeSpecificAuthorityFiles
|
|
.Where(File.Exists)
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (survivingRuntimeSpecificAuthorityFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria daemon/shared simulation and viewport authority must not be runtime-named: " +
|
|
string.Join(", ", survivingRuntimeSpecificAuthorityFiles));
|
|
}
|
|
|
|
var daemonSimulationPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeDaemonSimulation.cs");
|
|
var daemonSimulationSettingsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeDaemonSimulationSettings.cs");
|
|
var flightSimulationPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeFlightSimulation.cs");
|
|
var worldPhysicsContractPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeWorldPhysics.cs");
|
|
var ymirWorldPhysicsPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaYmirWorldPhysics.cs");
|
|
var obsoletePhysicalPayloadAuthorityPaths = new[]
|
|
{
|
|
Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimePhysicalPayloadPhysics.cs"),
|
|
Path.Combine(root, "Aetheria.State.Daemon", "AetheriaYmirPhysicalPayloadPhysics.cs")
|
|
};
|
|
var daemonProjectPath = Path.Combine(root, "Aetheria.State.Daemon", "Aetheria.State.Daemon.csproj");
|
|
var gameDocumentsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeGameDocuments.cs");
|
|
var gameViewportDocumentsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeGameViewportDocuments.cs");
|
|
var verseAuthorityPolicyPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeVerseAuthorityPolicy.cs");
|
|
var stateBoundaryPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeStateBoundary.cs");
|
|
var clientTargetStorePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeClientTargetStore.cs");
|
|
var runtimeClientProviderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRuntimeClientProvider.cs");
|
|
var rtsLocalDocumentsPath = Path.Combine(root, "Aetheria.Rts.Web", "Electron", "aetheria-rts-local-documents.ts");
|
|
var rtsGeneratedBindingsPath = Path.Combine(root, "Aetheria.Rts.Web", "Electron", "aetheria-rts-generated-bindings.ts");
|
|
var rtsElectronMainPath = Path.Combine(root, "Aetheria.Rts.Web", "Electron", "main.ts");
|
|
var rtsElectronCultMeshPath = Path.Combine(root, "Aetheria.Rts.Web", "Electron", "aetheria-cultmesh.ts");
|
|
var rtsClientAppPath = Path.Combine(root, "Aetheria.Rts.Web", "Client", "app.ts");
|
|
var verseAuthorityPlanPath = Path.Combine(root, "Aetheria.State", "docs", "verse-authority-implementation-plan.md");
|
|
var stage7ThinClientPlanPath = Path.Combine(root, "Aetheria.State", "docs", "stage-7-thin-client-staged-implementation-plan.md");
|
|
var stage7ClientSurfaceInventoryPath = Path.Combine(root, "Aetheria.State", "docs", "stage-7-client-surface-inventory.md");
|
|
var daemonCodeMapPath = Path.Combine(root, "Aetheria.State", "docs", "daemon-code-map-for-rust-port.md");
|
|
var daemonRuntimeDocumentTestsPath = Path.Combine(root, "Assets", "Scripts", "Tests", "DaemonRuntimeDocumentTests.cs");
|
|
var authoritySmokePath = Path.Combine(root, "Aetheria.State.AuthoritySmoke", "Program.cs");
|
|
var freezePath = Path.Combine(root, "Aetheria.State.Freeze", "Program.cs");
|
|
var snapshotDocuments = File.Exists(snapshotDocumentsPath)
|
|
? File.ReadAllText(snapshotDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon projectile authority; runtime snapshot documents are missing.");
|
|
var daemonSimulation = File.Exists(daemonSimulationPath)
|
|
? File.ReadAllText(daemonSimulationPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon projectile authority; daemon simulation is missing.");
|
|
var daemonSimulationSettings = File.Exists(daemonSimulationSettingsPath)
|
|
? File.ReadAllText(daemonSimulationSettingsPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon simulation authority; daemon simulation settings are missing.");
|
|
var flightSimulation = File.Exists(flightSimulationPath)
|
|
? File.ReadAllText(flightSimulationPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon flight authority; actuator simulation is missing.");
|
|
var worldPhysicsContract = File.Exists(worldPhysicsContractPath)
|
|
? File.ReadAllText(worldPhysicsContractPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon projectile authority; shared world physics port is missing.");
|
|
var ymirWorldPhysics = File.Exists(ymirWorldPhysicsPath)
|
|
? File.ReadAllText(ymirWorldPhysicsPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon projectile authority; retained daemon Ymir owner is missing.");
|
|
var daemonProject = File.ReadAllText(daemonProjectPath);
|
|
var gameDocuments = File.Exists(gameDocumentsPath)
|
|
? File.ReadAllText(gameDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon projectile authority; game viewport documents are missing.");
|
|
var gameViewportDocuments = File.Exists(gameViewportDocumentsPath)
|
|
? File.ReadAllText(gameViewportDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon projectile authority; zone render documents are missing.");
|
|
var verseAuthorityPolicy = File.Exists(verseAuthorityPolicyPath)
|
|
? File.ReadAllText(verseAuthorityPolicyPath)
|
|
: throw new InvalidOperationException("Cannot verify runtime-neutral Verse authority policy; policy document is missing.");
|
|
var stateBoundary = File.Exists(stateBoundaryPath)
|
|
? File.ReadAllText(stateBoundaryPath)
|
|
: throw new InvalidOperationException("Cannot verify runtime-neutral client target authority; state boundary is missing.");
|
|
var clientTargetStore = File.Exists(clientTargetStorePath)
|
|
? File.ReadAllText(clientTargetStorePath)
|
|
: throw new InvalidOperationException("Cannot verify runtime-neutral client target authority; client target store is missing.");
|
|
var runtimeClientProvider = File.Exists(runtimeClientProviderPath)
|
|
? File.ReadAllText(runtimeClientProviderPath)
|
|
: throw new InvalidOperationException("Cannot verify runtime-neutral client target authority; Unity runtime client provider is missing.");
|
|
var rtsLocalDocuments = File.Exists(rtsLocalDocumentsPath)
|
|
? File.ReadAllText(rtsLocalDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon projectile authority; Electron local projection is missing.");
|
|
var rtsGeneratedBindings = File.Exists(rtsGeneratedBindingsPath)
|
|
? File.ReadAllText(rtsGeneratedBindingsPath)
|
|
: throw new InvalidOperationException("Cannot verify Electron generated game bindings; generated bindings are missing.");
|
|
var rtsElectronMain = File.Exists(rtsElectronMainPath)
|
|
? File.ReadAllText(rtsElectronMainPath)
|
|
: throw new InvalidOperationException("Cannot verify Electron CultMesh CDN asset authority; Electron main process is missing.");
|
|
var rtsElectronCultMesh = File.Exists(rtsElectronCultMeshPath)
|
|
? File.ReadAllText(rtsElectronCultMeshPath)
|
|
: throw new InvalidOperationException("Cannot verify Electron CultMesh CDN asset authority; Electron CultMesh client is missing.");
|
|
var rtsClientApp = File.Exists(rtsClientAppPath)
|
|
? File.ReadAllText(rtsClientAppPath)
|
|
: throw new InvalidOperationException("Cannot verify Electron CultMesh CDN asset authority; renderer app is missing.");
|
|
var activeStage7ArchitectureDocs = new[]
|
|
{
|
|
verseAuthorityPlanPath,
|
|
stage7ThinClientPlanPath,
|
|
stage7ClientSurfaceInventoryPath,
|
|
daemonCodeMapPath
|
|
}
|
|
.Select(path => File.Exists(path)
|
|
? File.ReadAllText(path)
|
|
: throw new InvalidOperationException(
|
|
"Cannot verify RTS client authority documentation; architecture doc is missing: " +
|
|
Path.GetRelativePath(root, path)))
|
|
.ToArray();
|
|
var daemonRuntimeDocumentTests = File.Exists(daemonRuntimeDocumentTestsPath)
|
|
? File.ReadAllText(daemonRuntimeDocumentTestsPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon projectile authority; daemon runtime document tests are missing.");
|
|
var authoritySmoke = File.Exists(authoritySmokePath)
|
|
? File.ReadAllText(authoritySmokePath)
|
|
: throw new InvalidOperationException("Cannot verify daemon transport vocabulary; authority smoke harness is missing.");
|
|
var freeze = File.Exists(freezePath)
|
|
? File.ReadAllText(freezePath)
|
|
: throw new InvalidOperationException("Cannot verify daemon transport vocabulary; freeze harness is missing.");
|
|
|
|
var requiredPhysicalPayloadAuthoritySymbols = new Dictionary<string, string[]>
|
|
{
|
|
[snapshotDocumentsPath] = new[]
|
|
{
|
|
"public IReadOnlyList<AetheriaRuntimePhysicalPayloadCommit> PhysicalPayloads",
|
|
"public sealed class AetheriaRuntimePhysicalPayloadCommit"
|
|
},
|
|
[daemonSimulationPath] = new[]
|
|
{
|
|
"public static class AetheriaRuntimeDaemonSimulation",
|
|
"AetheriaRuntimeDaemonSimulationSettings settings",
|
|
"StepCombat(run, zone, entities, intents, deltaSeconds, settings, worldPhysics, catalog,",
|
|
".FindAllOperational(attacker, catalog);",
|
|
"ResolveWeapons(attacker, operationalBehaviors, catalog, settings)",
|
|
"EnsureWeaponState(",
|
|
"CommitWeaponRound(attacker, weapon, catalog)",
|
|
"weapon.State.BurstRemaining = weapon.BurstCount",
|
|
"weapon.State.BurstTimer += deltaSeconds",
|
|
"weapon.SingleAmmoBurst",
|
|
"weapon.reload.started",
|
|
"weapon.reload.completed",
|
|
"AetheriaRuntimeCargoTransactions.TryRemoveQuantity(",
|
|
"CommitEnergy(entity, catalog, weapon.Energy)",
|
|
"CommitShotResolution(run, zone, attacker, target, weapon, shotId, frameId, catalog, settings)",
|
|
"PreparePhysicalPayloads(zone, byIndex, deltaSeconds)",
|
|
"worldPhysics.StepPhysicalPayloads(",
|
|
"Kind = \"physical-payload.contact\""
|
|
},
|
|
[daemonSimulationSettingsPath] = new[]
|
|
{
|
|
"public readonly struct AetheriaRuntimeDaemonSimulationSettings",
|
|
"public static AetheriaRuntimeDaemonSimulationSettings AetheriaDefault",
|
|
"public double ProjectileSpeed { get; }",
|
|
"public double AttackRange { get; }"
|
|
},
|
|
[worldPhysicsContractPath] = new[]
|
|
{
|
|
"public interface IAetheriaRuntimeWorldPhysics",
|
|
"AetheriaRuntimePhysicalPayloadStep StepPhysicalPayloads(",
|
|
"public sealed class AetheriaRuntimePhysicalPayloadStep",
|
|
"public sealed class AetheriaRuntimePhysicalPayloadHit"
|
|
},
|
|
[ymirWorldPhysicsPath] = new[]
|
|
{
|
|
"public sealed class AetheriaYmirWorldPhysics : IAetheriaRuntimeWorldPhysics, IDisposable",
|
|
"private sealed class SessionState(YmirSession worldSession)",
|
|
"public YmirSession WorldSession { get; set; } = worldSession",
|
|
"public YmirSession? PayloadSession { get; set; }",
|
|
"var stepped = payloadSession.Step(new YmirStepSessionCommand(",
|
|
"state.WorldSession.CastCircle(new YmirSessionCircleCastQuery(",
|
|
"state.WorldSession.OverlapCircle(new YmirSessionCircleOverlapQuery(",
|
|
"PhysicalPayloadPrefix + payload.PayloadId",
|
|
"Payload phase requires the retained world phase",
|
|
"present.IsBullet != body.IsBullet",
|
|
"present.ParticipatesInFields != body.ParticipatesInFields",
|
|
"present.CollisionCategoryBits != body.CollisionCategoryBits",
|
|
"\"respawn-profile\""
|
|
},
|
|
[daemonProjectPath] = new[]
|
|
{
|
|
"Ymir.Core.csproj"
|
|
},
|
|
[daemonProgramPath] = new[]
|
|
{
|
|
"using var worldPhysics = new AetheriaYmirWorldPhysics();",
|
|
"WorldPhysics = worldPhysics"
|
|
},
|
|
[gameDocumentsPath] = new[]
|
|
{
|
|
"var projectiles = zone.PhysicalPayloads",
|
|
".Concat(projectiles",
|
|
"private static AetheriaRuntimeViewportObject ToViewportObject(",
|
|
"AetheriaRuntimePhysicalPayloadCommit projectile)"
|
|
},
|
|
[gameViewportDocumentsPath] = new[]
|
|
{
|
|
"public IReadOnlyList<AetheriaRuntimePhysicalPayloadCommit> PhysicalPayloads"
|
|
}
|
|
};
|
|
var missingPhysicalPayloadAuthoritySymbols = requiredPhysicalPayloadAuthoritySymbols
|
|
.SelectMany(pair =>
|
|
{
|
|
var text = pair.Key == snapshotDocumentsPath ? snapshotDocuments :
|
|
pair.Key == daemonSimulationPath ? daemonSimulation :
|
|
pair.Key == daemonSimulationSettingsPath ? daemonSimulationSettings :
|
|
pair.Key == worldPhysicsContractPath ? worldPhysicsContract :
|
|
pair.Key == ymirWorldPhysicsPath ? ymirWorldPhysics :
|
|
pair.Key == daemonProjectPath ? daemonProject :
|
|
pair.Key == daemonProgramPath ? daemonProgram :
|
|
pair.Key == gameDocumentsPath ? gameDocuments :
|
|
gameViewportDocuments;
|
|
return pair.Value
|
|
.Where(symbol => !text.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}");
|
|
})
|
|
.ToArray();
|
|
if (missingPhysicalPayloadAuthoritySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon combat must resolve ordinary shots as receipts while the retained Ymir zone owner supplies typed payload motion and query facts: " +
|
|
string.Join("; ", missingPhysicalPayloadAuthoritySymbols));
|
|
}
|
|
|
|
var forbiddenObsoletePhysicalPayloadAuthorities = new[]
|
|
{
|
|
"IAetheriaRuntimePhysicalPayloadPhysics",
|
|
"AetheriaRuntimePhysicalPayloadPhysicsUnavailable",
|
|
"AetheriaYmirPhysicalPayloadPhysics",
|
|
"PhysicalPayloadPhysics =",
|
|
"private readonly YmirSimulator _simulator"
|
|
};
|
|
var survivingObsoletePhysicalPayloadAuthorities = forbiddenObsoletePhysicalPayloadAuthorities
|
|
.Where(symbol => daemonSimulation.Contains(symbol, StringComparison.Ordinal) ||
|
|
worldPhysicsContract.Contains(symbol, StringComparison.Ordinal) ||
|
|
ymirWorldPhysics.Contains(symbol, StringComparison.Ordinal) ||
|
|
daemonProgram.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingObsoletePhysicalPayloadAuthorities.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Physical payloads must not retain a snapshot-world port or adapter beside the retained zone physics owner: " +
|
|
string.Join(", ", survivingObsoletePhysicalPayloadAuthorities));
|
|
}
|
|
var survivingObsoletePhysicalPayloadAuthorityFiles = obsoletePhysicalPayloadAuthorityPaths
|
|
.Where(File.Exists)
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (survivingObsoletePhysicalPayloadAuthorityFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The snapshot-world physical payload authority must be deleted, not left available for reinjection: " +
|
|
string.Join(", ", survivingObsoletePhysicalPayloadAuthorityFiles));
|
|
}
|
|
if (daemonTickRunner.Contains("PhysicalPayloadPhysics", StringComparison.Ordinal))
|
|
throw new InvalidOperationException(
|
|
"The daemon tick runner must inject one world physics owner, not a second physical-payload authority.");
|
|
|
|
var forbiddenPhysicalPayloadDamageSymbols = new[]
|
|
{
|
|
"Damage(target, hit.Payload",
|
|
"SpawnProjectile(zone, attacker",
|
|
"TraceBeam("
|
|
};
|
|
var survivingPhysicalPayloadDamageSymbols = forbiddenPhysicalPayloadDamageSymbols
|
|
.Where(symbol => daemonSimulation.Contains(symbol, StringComparison.Ordinal) ||
|
|
worldPhysicsContract.Contains(symbol, StringComparison.Ordinal) ||
|
|
ymirWorldPhysics.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingPhysicalPayloadDamageSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Ymir physical payload contacts may report physics facts but may not decide ordinary weapon damage: " +
|
|
string.Join(", ", survivingPhysicalPayloadDamageSymbols));
|
|
}
|
|
|
|
var shotReceiptDocument = File.ReadAllText(snapshotDocumentsPath);
|
|
if (daemonSimulation.Contains("var shield = GetStat(target, Shield)", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("SetStat(target, Shield, shield -", StringComparison.Ordinal) ||
|
|
!daemonSimulation.Contains("damage * energyUsage", StringComparison.Ordinal) ||
|
|
!daemonSimulation.Contains("damage / efficiency", StringComparison.Ordinal) ||
|
|
!shotReceiptDocument.Contains("ShieldAbsorbedDamage", StringComparison.Ordinal) ||
|
|
!shotReceiptDocument.Contains("ShieldEnergyConsumed", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shield authority must be the equipped energy-funded behavior transaction, not a scalar shield hitpoint pool.");
|
|
}
|
|
|
|
var requiredArmorTransactionSymbols = new[]
|
|
{
|
|
"EnsureArmorState(entity, catalog)",
|
|
"ResolveDamageCells(target, hull, impactX, impactZ, source, penetration, spread, splash)",
|
|
"ApplyGridDamage(armor, cell.X, cell.Y, remaining)",
|
|
"FindEquipmentAt(target, catalog, cell.X, cell.Y)",
|
|
"for (var distance = 0.5; distance < penetration; distance += 0.5)",
|
|
"ArmorAppliedDamage",
|
|
"EquipmentAppliedDamage",
|
|
"DamageCells = damage.Cells"
|
|
};
|
|
var missingArmorTransactionSymbols = requiredArmorTransactionSymbols
|
|
.Where(symbol => !daemonSimulation.Contains(symbol, StringComparison.Ordinal) &&
|
|
!shotReceiptDocument.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingArmorTransactionSymbols.Length > 0 ||
|
|
daemonSimulation.Contains("ResolveDamage(target, weapon.Damage, catalog)", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("ResolveDamage(target, payload.PayloadMagnitude, catalog)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon damage must resolve shield, armor cells, installed equipment, then hull through one typed transaction: " +
|
|
string.Join(", ", missingArmorTransactionSymbols));
|
|
}
|
|
|
|
var daemonOperations = File.ReadAllText(Path.Combine(
|
|
root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs"));
|
|
var requiredDestructionTransactionSymbols = new[]
|
|
{
|
|
"CommitDestruction(run, zone, target",
|
|
"target.DestructionId = destructionId",
|
|
"settings.LootDropProbability",
|
|
"settings.LootDropVelocity",
|
|
"settings.PickupLifetimeSeconds",
|
|
"Kind = \"pickup.dropped\"",
|
|
"Kind = \"entity.destroyed\"",
|
|
"zone.DroppedPickups = pickups"
|
|
};
|
|
var missingDestructionTransactionSymbols = requiredDestructionTransactionSymbols
|
|
.Where(symbol => !daemonSimulation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDestructionTransactionSymbols.Length > 0 ||
|
|
daemonOperations.Contains("ApplyDestroyEntity(", StringComparison.Ordinal) ||
|
|
daemonOperations.Contains("case AetheriaRuntimeDaemonCommandKinds.DestroyEntity:", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Lethal daemon damage must own exactly-once destruction, loot drops, and cleanup; clients may not command entity erasure: " +
|
|
string.Join(", ", missingDestructionTransactionSymbols));
|
|
}
|
|
|
|
if (daemonSimulation.Contains("AetheriaRuntimeRtsSimulation", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("daemon-rts", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("rts-commander", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("Damage(target, damage)", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("Damage(target, ResolveProjectileDamage(attacker)", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("private static void StepProjectiles(", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("private static bool ProjectileHits(", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("private const double PawnSpeed", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("private const double AttackRange", StringComparison.Ordinal) ||
|
|
daemonSimulation.Contains("private const double ProjectileSpeed", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon combat regressed to runtime-named simulation, hardcoded game feel, direct target damage, or local projectile physics instead of daemon settings plus Ymir-shaped projectile contact damage.");
|
|
}
|
|
|
|
var requiredFlightAuthoritySymbols = new[]
|
|
{
|
|
"ConfigureThrusterAxes(",
|
|
"StepThruster(",
|
|
"ResolveThrusterTorque(",
|
|
"AetheriaRuntimeEnergySimulation.TryConsume(",
|
|
"AetheriaRuntimeThermalSimulation.AddHeatToEquipment(",
|
|
"ApplyThrusterVisibility(",
|
|
"StepAetherDrive(",
|
|
"entity.HelmStrafe",
|
|
"entity.LookDirectionX"
|
|
};
|
|
var missingFlightAuthoritySymbols = requiredFlightAuthoritySymbols
|
|
.Where(symbol => !flightSimulation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
var forbiddenDirectFlightSymbols = new[]
|
|
{
|
|
"ResolveSpeed(entity, settings)",
|
|
"entity.VelocityX = normalized.X * speed",
|
|
"entity.VelocityY = normalized.Y * speed",
|
|
"settings.PawnSpeed",
|
|
"settings.RaiderSpeed"
|
|
};
|
|
var directFlightHits = forbiddenDirectFlightSymbols
|
|
.Where(symbol => daemonSimulation.Contains(symbol, StringComparison.Ordinal) ||
|
|
flightSimulation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingFlightAuthoritySymbols.Length > 0 || directFlightHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon flight must be owned by installed Thruster/AetherDrive actuators and may not restore faction-speed velocity assignment. Missing: " +
|
|
string.Join(", ", missingFlightAuthoritySymbols) + "; forbidden: " +
|
|
string.Join(", ", directFlightHits));
|
|
}
|
|
|
|
var forbiddenRuntimeSpecificAuthorityPolicySymbols = new[]
|
|
{
|
|
"ElectronLaunchedDaemon",
|
|
"electron-launched-daemon",
|
|
"UnityHost(",
|
|
"unity-host",
|
|
"BrowserWasmHost",
|
|
"browser-wasm-host",
|
|
"UnityPlayer",
|
|
"unity-player",
|
|
"BrowserObserver",
|
|
"browser-observer",
|
|
"BrowserSimulationHost",
|
|
"browser-simulation-host",
|
|
"aetheria-unity-host",
|
|
"aetheria.unity-host.v1"
|
|
};
|
|
var runtimeSpecificAuthorityPolicyHits = forbiddenRuntimeSpecificAuthorityPolicySymbols
|
|
.Where(symbol => verseAuthorityPolicy.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (runtimeSpecificAuthorityPolicyHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared Verse authority policy must describe daemon/trusted authority, not runtime-specific host side paths: " +
|
|
string.Join(", ", runtimeSpecificAuthorityPolicyHits));
|
|
}
|
|
|
|
if (!daemonTickRunner.Contains("AetheriaRuntimeDaemonSimulation.Step(", StringComparison.Ordinal) ||
|
|
!daemonTickRunner.Contains("public AetheriaRuntimeDaemonSimulationSettings SimulationSettings { get; set; }", StringComparison.Ordinal) ||
|
|
!daemonTickRunner.Contains("AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault", StringComparison.Ordinal) ||
|
|
!daemonTickRunner.Contains("simulationSettings: options.SimulationSettings", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon ticks must route Aetheria simulation through daemon-owned settings, not runtime-local constants.");
|
|
}
|
|
|
|
if (daemonProgram.Contains("RtsCultMesh", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("ReadOption(args, \"--rts-cultmesh", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("ReadNonNegativeInt(args, \"--rts-cultmesh", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("ReadPositiveInt(args, \"--rts-cultmesh", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("RTS CultMesh endpoint", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("aetheria-rts-rudp", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("daemon:aetheria.rts.viewport", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("eve-browser-lowering", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("raven-unity", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("pilot-client", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("starbridge-daemon", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("local-rts", StringComparison.Ordinal) ||
|
|
daemonProgram.Contains("starfire-rts", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria daemon transport and seeded role documents must be client-neutral; runtime-branded CultMesh flags, transport ids, and placeholder client identities belong outside daemon authority.");
|
|
}
|
|
if (!daemonProgram.Contains("RejectRemovedOption(args, \"--rts-cultmesh-port\", \"--client-cultmesh-port\")", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria daemon must explicitly reject the removed RTS-branded CultMesh port flag and point callers at the client-neutral flag.");
|
|
}
|
|
if (authoritySmoke.Contains("--rts-cultmesh-port", StringComparison.Ordinal) ||
|
|
authoritySmoke.Contains("rtsPort", StringComparison.Ordinal) ||
|
|
authoritySmoke.Contains("raven-unity", StringComparison.Ordinal) ||
|
|
authoritySmoke.Contains("starfire-rts", StringComparison.Ordinal) ||
|
|
freeze.Contains("--rts-cultmesh-port", StringComparison.Ordinal) ||
|
|
freeze.Contains("local-rts", StringComparison.Ordinal) ||
|
|
freeze.Contains("starfire-rts", StringComparison.Ordinal) ||
|
|
freeze.Contains("const string rts", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria daemon smoke/freeze callers and fixtures must use client-neutral endpoint, run, and runtime vocabulary instead of preserving RTS-branded transport names.");
|
|
}
|
|
if (rtsLocalDocuments.Contains("local-rts", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Electron local document projections must not invent RTS-branded run ids when the daemon frame omits run identity.");
|
|
}
|
|
if (rtsGeneratedBindings.Contains("AetheriaRuntimeRtsQuery", StringComparison.Ordinal) ||
|
|
rtsGeneratedBindings.Contains("AetheriaRuntimeRtsOperation", StringComparison.Ordinal) ||
|
|
rtsGeneratedBindings.Contains("AetheriaRuntimeRtsDocument", StringComparison.Ordinal) ||
|
|
rtsGeneratedBindings.Contains("AetheriaRuntimeRtsVerse", StringComparison.Ordinal) ||
|
|
rtsElectronCultMesh.Contains("createAetheriaRuntimeRts", StringComparison.Ordinal) ||
|
|
rtsElectronCultMesh.Contains("describeAetheriaRuntimeRts", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Electron may be the RTS client, but generated shared Aetheria game handles must use runtime-neutral Game vocabulary.");
|
|
}
|
|
if (stateBoundary.Contains("UnityRuntimeIdOverrideEnvironmentVariable", StringComparison.Ordinal) ||
|
|
stateBoundary.Contains("AETHERIA_UNITY_RUNTIME_ID", StringComparison.Ordinal) ||
|
|
clientTargetStore.Contains("raven-unity", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("raven-unity", StringComparison.Ordinal) ||
|
|
!runtimeClientProvider.Contains("AetheriaRuntimeStateBoundary.DefaultClientRuntimeId", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria shared client-target authority must use generic runtime identity only; Unity-specific defaults and environment fallbacks must not survive as ambient provider defaults.");
|
|
}
|
|
|
|
var sharedRuntimeText = string.Join(
|
|
"\n",
|
|
daemonSimulation,
|
|
gameDocuments,
|
|
gameViewportDocuments);
|
|
var forbiddenSharedRuntimeAuthoritySymbols = new[]
|
|
{
|
|
"AetheriaRuntimeRtsDocuments",
|
|
"AetheriaRuntimeRtsViewport",
|
|
"RtsViewport",
|
|
"gamecult.aetheria.rts_viewport",
|
|
"local-rts",
|
|
"daemon-rts",
|
|
"rts-commander",
|
|
"aetheria.asset.sprite.rts."
|
|
};
|
|
var sharedRuntimeAuthorityHits = forbiddenSharedRuntimeAuthoritySymbols
|
|
.Where(symbol => sharedRuntimeText.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (sharedRuntimeAuthorityHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria shared runtime still exposes RTS-specific authority vocabulary: " +
|
|
string.Join(", ", sharedRuntimeAuthorityHits));
|
|
}
|
|
|
|
var staleRuntimeSpecificTestAuthoritySources = new Dictionary<string, string>
|
|
{
|
|
["Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs"] = daemonRuntimeDocumentTests
|
|
}
|
|
.Where(pair =>
|
|
pair.Value.Contains("daemon-rts", StringComparison.Ordinal) ||
|
|
pair.Value.Contains("rts-commander", StringComparison.Ordinal))
|
|
.Select(pair => pair.Key)
|
|
.ToArray();
|
|
if (staleRuntimeSpecificTestAuthoritySources.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria daemon tests must assert daemon-owned simulation vocabulary, not stale RTS owner kinds: " +
|
|
string.Join(", ", staleRuntimeSpecificTestAuthoritySources));
|
|
}
|
|
|
|
var staleRuntimeSpecificAssetSources = new Dictionary<string, string>
|
|
{
|
|
["Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts"] = rtsLocalDocuments,
|
|
["Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs"] = daemonRuntimeDocumentTests
|
|
}
|
|
.Where(pair => pair.Value.Contains("aetheria.asset.sprite.rts.", StringComparison.Ordinal))
|
|
.Select(pair => pair.Key)
|
|
.ToArray();
|
|
if (staleRuntimeSpecificAssetSources.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria projectile and map assets must use daemon/shared game asset keys, not runtime-branded sprite keys: " +
|
|
string.Join(", ", staleRuntimeSpecificAssetSources));
|
|
}
|
|
|
|
if (rtsLocalDocuments.Contains("resources://", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("\"resources\"", StringComparison.Ordinal) ||
|
|
rtsClientApp.Contains("Assets/Resources", StringComparison.Ordinal) ||
|
|
rtsClientApp.Contains("resources://", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Electron Eve lowering must not synthesize renderer-local Resources asset refs; assets resolve through CultMesh CDN.");
|
|
}
|
|
if (rtsLocalDocuments.Contains("entityIconAsset", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("bodyIconAsset", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("itemIconAsset", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("cultMeshAssetUri", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("map.entity.", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("map.body.", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains(".icon`)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Electron document decoding must not fabricate fallback Aetheria asset refs; daemon-authored documents and CultMesh CDN own asset identity.");
|
|
}
|
|
if (rtsElectronCultMesh.Contains("buildViewportDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal) ||
|
|
rtsElectronCultMesh.Contains("buildObjectsViewportDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal) ||
|
|
rtsElectronCultMesh.Contains("buildGravityViewportDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal) ||
|
|
rtsElectronCultMesh.Contains("buildRenderSplatsViewportDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal) ||
|
|
rtsElectronCultMesh.Contains("buildSelectedObjectDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal) ||
|
|
rtsElectronCultMesh.Contains("buildInventoryDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Electron queries must fetch daemon-authored managed documents, not rebuild field/view/selection/inventory documents from daemonFrame.");
|
|
}
|
|
if (rtsLocalDocuments.Contains("buildViewportDocumentFromFrame", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("buildObjectsViewportDocumentFromFrame", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("buildGravityViewportDocumentFromFrame", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("buildRenderSplatsViewportDocumentFromFrame", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("buildSelectedObjectDocumentFromFrame", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("buildInventoryDocumentFromFrame", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("frameContext(frameDocument", StringComparison.Ordinal) ||
|
|
rtsLocalDocuments.Contains("RenderSplatBuilder", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Electron local document decoding must not keep dead viewport, selection, or inventory projection builders; daemon managed documents own those fields.");
|
|
}
|
|
var staleRtsClientDocumentationPhrases = new[]
|
|
{
|
|
"aetheria-rts-local-projection.ts",
|
|
"aetheria-local-publication-reader.ts",
|
|
"AetheriaLocalPublicationReader",
|
|
"AetheriaRemotePublicationReader",
|
|
"projection facade methods backed by local CultCache publication sidecars",
|
|
"Electron sends CultMesh commands but still reads local `.cc` publications.",
|
|
"TS duplicates projection logic by decoding MessagePack arrays and slot constants."
|
|
};
|
|
var staleRtsClientDocumentationHits = activeStage7ArchitectureDocs
|
|
.SelectMany((text, index) => staleRtsClientDocumentationPhrases
|
|
.Where(phrase => text.Contains(phrase, StringComparison.Ordinal))
|
|
.Select(phrase => $"{Path.GetRelativePath(root, new[]
|
|
{
|
|
verseAuthorityPlanPath,
|
|
stage7ThinClientPlanPath,
|
|
stage7ClientSurfaceInventoryPath,
|
|
daemonCodeMapPath
|
|
}[index])}: {phrase}"))
|
|
.ToArray();
|
|
if (staleRtsClientDocumentationHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Active architecture docs must not preserve deleted Electron local projection/publication-reader wrappers as the client contract; Hermodr and Electron consume daemon-authored documents through the Eve/CultMesh contract: " +
|
|
string.Join("; ", staleRtsClientDocumentationHits));
|
|
}
|
|
if (!rtsElectronCultMesh.Contains("fetchViewportDocument(AetheriaRtsSchemas.gameViewport, \"aetheria.viewport.map\"", StringComparison.Ordinal) ||
|
|
!rtsElectronCultMesh.Contains("fetchViewportDocument(AetheriaRtsSchemas.objectsViewport, \"aetheria.viewport.objects\"", StringComparison.Ordinal) ||
|
|
!rtsElectronCultMesh.Contains("fetchViewportDocument(AetheriaRtsSchemas.gravityViewport, \"aetheria.viewport.gravity\"", StringComparison.Ordinal) ||
|
|
!rtsElectronCultMesh.Contains("fetchViewportDocument(AetheriaRtsSchemas.renderSplatsViewport, \"aetheria.viewport.render_splats\"", StringComparison.Ordinal) ||
|
|
!rtsElectronCultMesh.Contains("managedViewportRecordKey(", StringComparison.Ordinal) ||
|
|
!rtsElectronCultMesh.Contains("readRenderSplatsViewportDocument(await this.fetchViewportDocument", StringComparison.Ordinal) ||
|
|
!rtsElectronCultMesh.Contains("fetchIndexedDocument(AetheriaRtsSchemas.selectedObject, \"aetheria.object.selected\"", StringComparison.Ordinal) ||
|
|
!rtsElectronCultMesh.Contains("fetchIndexedDocument(AetheriaRtsSchemas.inventory, \"aetheria.inventory\"", StringComparison.Ordinal) ||
|
|
!rtsElectronCultMesh.Contains("readSelectedObjectDocument(await this.fetchIndexedDocument", StringComparison.Ordinal) ||
|
|
!rtsElectronCultMesh.Contains("readInventoryDocument(await this.fetchIndexedDocument", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Electron viewport, selected-object, and inventory queries no longer use the daemon managed document path.");
|
|
}
|
|
if (!daemonProgram.Contains("recordKey.StartsWith(\"aetheria.viewport.map.\"", StringComparison.Ordinal) ||
|
|
!daemonProgram.Contains("schemaId = AetheriaRuntimeDaemonSchemas.GameViewport", StringComparison.Ordinal) ||
|
|
!daemonProgram.Contains("candidate.StartsWith(\"aetheria.object.selected.\"", StringComparison.Ordinal) ||
|
|
!daemonProgram.Contains("candidate.StartsWith(\"aetheria.inventory.\"", StringComparison.Ordinal) ||
|
|
!daemonProgram.Contains("AetheriaRuntimeGameDocuments.SelectedObject(frame, indexedEntityIndex)", StringComparison.Ordinal) ||
|
|
!daemonProgram.Contains("AetheriaRuntimeGameDocuments.Inventory(frame, indexedEntityIndex)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon snapshot handling must expose managed viewport and indexed selected-object/inventory document keys.");
|
|
}
|
|
|
|
if (daemonTickRunner.Contains("AetheriaRuntimeDaemonFrameStore.PublishFrame", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared simulation ticks still write legacy frame witness files instead of returning managed typed documents.");
|
|
}
|
|
}
|
|
|
|
static void RequireUnityPhysicsIsNotGameplayAuthority(string root)
|
|
{
|
|
var checkedRoots = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay"),
|
|
Path.Combine(root, "Assets", "Scripts", "ServerShared")
|
|
};
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"OnTriggerEnter",
|
|
"OnTriggerStay",
|
|
"OnTriggerExit",
|
|
"OnCollisionEnter",
|
|
"OnCollisionStay",
|
|
"OnCollisionExit",
|
|
"OnControllerColliderHit",
|
|
"Physics.Raycast",
|
|
"Physics.SphereCast",
|
|
"Physics.BoxCast",
|
|
"Physics.CapsuleCast",
|
|
"Physics.OverlapSphere",
|
|
"Physics.OverlapBox",
|
|
"Physics.OverlapCapsule",
|
|
"Physics.CheckSphere",
|
|
"Physics.CheckBox",
|
|
"Physics.CheckCapsule",
|
|
"Rigidbody",
|
|
"Rigidbody2D",
|
|
"Collider2D"
|
|
};
|
|
|
|
var hits = checkedRoots
|
|
.Where(Directory.Exists)
|
|
.SelectMany(path => Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories))
|
|
.Where(path => !Path.GetFileName(path).EndsWith("Tests.cs", StringComparison.Ordinal))
|
|
.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 physics/collision callbacks are forbidden as gameplay authority; route queries through Ymir: " +
|
|
string.Join("; ", hits));
|
|
}
|
|
|
|
var ymirBridgePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "Physics", "AetheriaYmirPhysicsBridge.cs");
|
|
var projectilePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "Projectile.cs");
|
|
var clickRaycasterPath = Path.Combine(root, "Assets", "Scripts", "UI", "ClickRaycaster.cs");
|
|
|
|
var requiredSymbols = new Dictionary<string, string[]>
|
|
{
|
|
[ymirBridgePath] = new[]
|
|
{
|
|
"public bool TryCastClickables(",
|
|
"YmirPhysicsQueries.CastSphere(new YmirSphereCastRequest",
|
|
"private readonly Dictionary<string, ClickableCollider> _clickableBodyMap"
|
|
},
|
|
[clickRaycasterPath] = new[]
|
|
{
|
|
"AetheriaYmirPhysicsBridge.Instance.TryCastClickables"
|
|
}
|
|
};
|
|
|
|
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(
|
|
"Ymir presentation picking bridge is incomplete: " +
|
|
string.Join("; ", missingSymbols));
|
|
}
|
|
|
|
var ymirBridge = File.ReadAllText(ymirBridgePath);
|
|
var forbiddenYmirTransportSymbols = new[]
|
|
{
|
|
"public string ServiceUrl",
|
|
"public string OverlapSphereUrl",
|
|
"public string OverlapCircleUrl",
|
|
"public string CastCircleUrl",
|
|
"public string CastSphereUrl",
|
|
"TimeoutMilliseconds",
|
|
"PostJson<",
|
|
"HttpWebRequest",
|
|
"WebRequest.Create",
|
|
"http://127.0.0.1:8877"
|
|
};
|
|
var ymirTransportHits = forbiddenYmirTransportSymbols
|
|
.Where(symbol => ymirBridge.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, ymirBridgePath)}: {symbol}")
|
|
.ToArray();
|
|
|
|
if (ymirTransportHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Ymir presentation queries must use the typed query surface, not a local HTTP transport shortcut: " +
|
|
string.Join("; ", ymirTransportHits));
|
|
}
|
|
|
|
var forbiddenUnityProjectileAuthorityPaths = new[]
|
|
{
|
|
projectilePath,
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ProjectileManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "GuidedProjectile.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "GuidedProjectileManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Physics", "YmirUnityRuntimeProofTests.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "Laser.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "LaserManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "Lightning.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "LightningGunManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "HitscanEffect.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "HitscanManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantLaser.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantLaserManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantLightning.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantLightningManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantParticleWeapon.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantParticleWeaponManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantWeaponEffectManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "InstantWeaponEffectManager.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "Mine.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "MineManager.cs")
|
|
};
|
|
var survivingUnityProjectileAuthority = forbiddenUnityProjectileAuthorityPaths
|
|
.Where(File.Exists)
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
if (survivingUnityProjectileAuthority.Length > 0 ||
|
|
File.ReadAllText(ymirBridgePath).Contains("TryStepProjectile", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Ordinary weapon travel is daemon receipt presentation, not Unity/Ymir projectile simulation: " +
|
|
string.Join(", ", survivingUnityProjectileAuthority));
|
|
}
|
|
|
|
var weaponEffectRoot = Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons");
|
|
var forbiddenWeaponCombatSymbols = new[]
|
|
{
|
|
"public float Damage",
|
|
"public float Penetration",
|
|
"public float Spread",
|
|
"public DamageType",
|
|
".Damage =",
|
|
".Penetration =",
|
|
".Spread =",
|
|
".DamageType =",
|
|
".DamageSpread"
|
|
};
|
|
var weaponCombatAuthorityHits = Directory.EnumerateFiles(weaponEffectRoot, "*.cs", SearchOption.TopDirectoryOnly)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenWeaponCombatSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (weaponCombatAuthorityHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity weapon effects are presentation only; combat damage, penetration, spread, and damage type belong to daemon state: " +
|
|
string.Join("; ", weaponCombatAuthorityHits));
|
|
}
|
|
|
|
var weaponSingletonHits = Directory.EnumerateFiles(weaponEffectRoot, "*.cs", SearchOption.TopDirectoryOnly)
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => line.Line.Contains("ActionGameManager.Instance", StringComparison.Ordinal))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (weaponSingletonHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity weapon effects must receive renderer/input context explicitly instead of reaching through ActionGameManager.Instance: " +
|
|
string.Join("; ", weaponSingletonHits));
|
|
}
|
|
}
|
|
|
|
static async Task RequireTradeValuePolicyEveCommandPersistsAsync()
|
|
{
|
|
var tempStatePath = Path.Combine(
|
|
Path.GetTempPath(),
|
|
$"aetheria-trade-policy-command-{Guid.NewGuid():N}.cc");
|
|
|
|
try
|
|
{
|
|
await using var commandNode = await AetheriaStateNode.OpenAsync(
|
|
tempStatePath,
|
|
"aetheria-state-verify-trade-policy",
|
|
enableDurableShardLogs: false);
|
|
|
|
var policy = AetheriaRuntimeStateMapper.ToTradeValuePolicy(
|
|
AetheriaRuntimeTradeValueSettings.Default,
|
|
DateTimeOffset.UtcNow.ToString("O"));
|
|
await commandNode.MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey)
|
|
.ReplaceAsync(policy)
|
|
.ConfigureAwait(false);
|
|
var originalMinimum = policy.QualityPriceModifier?.Minimum ?? 0;
|
|
var editedMinimum = originalMinimum + 0.125;
|
|
|
|
var command = AetheriaRuntimeEveCommandClient.ToDocument(
|
|
AetheriaRuntimeEveCommandClient.CreateTradeValuePolicyCommand(
|
|
AetheriaRuntimeEveCommandKind.SetTradeValueQualityMinimum,
|
|
new AetheriaRuntimeTradeValuePolicyCommandBody
|
|
{
|
|
Value = editedMinimum
|
|
},
|
|
"aetheria-state-verify"));
|
|
await commandNode.SubmitEveCommandAsync(command).ConfigureAwait(false);
|
|
|
|
var report = await AetheriaEveCommandBridge.AcceptObservedAsync(commandNode).ConfigureAwait(false);
|
|
if (report.AcceptedTradeValuePolicyCommands != 1)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Trade value policy Eve command was not accepted by the typed command bridge.");
|
|
}
|
|
|
|
var editedPolicy = await commandNode.MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey).ReadAsync().ConfigureAwait(false)
|
|
?? throw new InvalidOperationException("Trade value policy disappeared after typed Eve command.");
|
|
var actualMinimum = editedPolicy.QualityPriceModifier?.Minimum ?? 0;
|
|
if (Math.Abs(actualMinimum - editedMinimum) > 0.000001)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Trade value policy Eve command did not persist the typed edit. Expected {editedMinimum}, got {actualMinimum}.");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
File.Delete(tempStatePath);
|
|
}
|
|
catch
|
|
{
|
|
// Best-effort cleanup for verifier scratch state.
|
|
}
|
|
}
|
|
}
|
|
|
|
static async Task RequireMainMenuEveCommandPersistsAsync()
|
|
{
|
|
var tempStatePath = Path.Combine(
|
|
Path.GetTempPath(),
|
|
$"aetheria-main-menu-command-{Guid.NewGuid():N}.cc");
|
|
|
|
try
|
|
{
|
|
await using var commandNode = await AetheriaStateNode.OpenAsync(
|
|
tempStatePath,
|
|
"aetheria-state-verify-main-menu",
|
|
enableDurableShardLogs: false);
|
|
|
|
if (!AetheriaRuntimeEveCommandClient.TryCreateKnownSurfaceCommand(
|
|
new EveSurfaceCommandRequest(
|
|
AetheriaRuntimeMainMenuSurfaceBuilder.ProviderId,
|
|
AetheriaRuntimeMainMenuCommands.RootSurfaceId,
|
|
CultMesh.OperationInvocation(
|
|
AetheriaRuntimeMainMenuCommands.ShowSettings,
|
|
AetheriaRuntimeEveCommandDocument.SchemaId,
|
|
new CultMeshRouteHint(CultMeshLocalityKind.Network, "aetheria-eve-command")),
|
|
CultMesh.OperationPayload(),
|
|
DateTimeOffset.UtcNow,
|
|
"aetheria-state-verify"),
|
|
out var envelope) ||
|
|
envelope == null)
|
|
{
|
|
throw new InvalidOperationException("Main menu Eve surface command did not create a typed command envelope.");
|
|
}
|
|
|
|
var command = AetheriaRuntimeEveCommandClient.ToDocument(envelope);
|
|
await commandNode.SubmitEveCommandAsync(command).ConfigureAwait(false);
|
|
|
|
var report = await AetheriaEveCommandBridge.AcceptObservedAsync(commandNode).ConfigureAwait(false);
|
|
if (report.AcceptedMainMenuCommands != 1)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Main menu Eve command was not accepted by the typed command bridge.");
|
|
}
|
|
|
|
var menuState = await commandNode.MutableDocument<AetheriaMainMenuState>(AetheriaStateNode.MainMenuStateKey).ReadAsync().ConfigureAwait(false)
|
|
?? throw new InvalidOperationException("Main menu state was not created by the typed Eve command.");
|
|
if (!string.Equals(menuState.ActiveSurfaceId, AetheriaRuntimeMainMenuCommands.SettingsSurfaceId, StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Main menu Eve command did not persist daemon-owned panel state. Expected {AetheriaRuntimeMainMenuCommands.SettingsSurfaceId}, got {menuState.ActiveSurfaceId}.");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
File.Delete(tempStatePath);
|
|
}
|
|
catch
|
|
{
|
|
// Best-effort cleanup for verifier scratch state.
|
|
}
|
|
}
|
|
}
|
|
|
|
static void RequireDeadPropertiesPanelShellDeleted(string root)
|
|
{
|
|
var deletedShellPaths = new[]
|
|
{
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "DropdownMenu.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "DropdownMenu.cs.meta"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesPanel.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesPanel.cs.meta"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesList.cs"),
|
|
Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesList.cs.meta"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Dropdown Menu.prefab"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Dropdown Menu.prefab.meta"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Properties.prefab"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Properties.prefab.meta"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Property List.prefab"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Property List.prefab.meta"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Property List 1.prefab"),
|
|
Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Property List 1.prefab.meta")
|
|
};
|
|
|
|
var survivingShells = deletedShellPaths
|
|
.Where(File.Exists)
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
|
|
if (survivingShells.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The dead generic popup inspector shell still survives in source or prefab assets: " +
|
|
string.Join(", ", survivingShells));
|
|
}
|
|
|
|
var guidChecks = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Assets", "Scenes", "ARPG.unity")] = new[]
|
|
{
|
|
"8c2bf4a7080061d42a87046c37bf0c60",
|
|
"2544c9c8b6358c54ea58c5fb33bb48b7",
|
|
"848ae49aff071f5458dcc0322b8b84eb"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scenes", "FieldShieldTest.unity")] = new[]
|
|
{
|
|
"8c2bf4a7080061d42a87046c37bf0c60",
|
|
"2544c9c8b6358c54ea58c5fb33bb48b7",
|
|
"848ae49aff071f5458dcc0322b8b84eb"
|
|
},
|
|
[Path.Combine(root, "Assets", "Prefabs", "UI", "Main Menu Canvas.prefab")] = new[]
|
|
{
|
|
"8c2bf4a7080061d42a87046c37bf0c60",
|
|
"2544c9c8b6358c54ea58c5fb33bb48b7",
|
|
"848ae49aff071f5458dcc0322b8b84eb"
|
|
}
|
|
};
|
|
|
|
var survivingGuidLinks = guidChecks
|
|
.SelectMany(entry =>
|
|
{
|
|
var text = File.Exists(entry.Key) ? File.ReadAllText(entry.Key) : "";
|
|
return entry.Value
|
|
.Where(guid => text.Contains(guid, StringComparison.Ordinal))
|
|
.Select(guid => $"{Path.GetRelativePath(root, entry.Key)}: {guid}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (survivingGuidLinks.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Scene or prefab YAML still serializes the deleted generic popup inspector shell: " +
|
|
string.Join("; ", survivingGuidLinks));
|
|
}
|
|
}
|
|
|
|
static void RequireTypedBehaviorMetadataCoverage(string root)
|
|
{
|
|
var metadataPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeBehaviorMetadata.cs");
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.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 inventoryMenu = File.Exists(inventoryMenuPath)
|
|
? File.ReadAllText(inventoryMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; InventoryMenu.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 cargoBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs");
|
|
var equippedBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs");
|
|
var tradeItemBuilderPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeTradeItemDetailsSurfaceBuilder.cs");
|
|
var cargoBuilder = File.Exists(cargoBuilderPath)
|
|
? File.ReadAllText(cargoBuilderPath)
|
|
: throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; cargo item details builder is missing.");
|
|
var equippedBuilder = File.Exists(equippedBuilderPath)
|
|
? File.ReadAllText(equippedBuilderPath)
|
|
: throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; equipped item details builder is missing.");
|
|
var tradeItemBuilder = File.Exists(tradeItemBuilderPath)
|
|
? File.ReadAllText(tradeItemBuilderPath)
|
|
: throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; trade item details builder 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 inventoryBehaviorProjection = cargoBuilder + "\n" + equippedBuilder;
|
|
var missingInventorySymbols = requiredUiSymbols
|
|
.Where(symbol => !inventoryBehaviorProjection.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingInventorySymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory item surface builders no longer render typed temperature-bearing behavior metadata: " +
|
|
string.Join(", ", missingInventorySymbols));
|
|
}
|
|
|
|
var missingTradeSymbols = requiredUiSymbols
|
|
.Where(symbol => !tradeItemBuilder.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingTradeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Trade item surface builder no longer renders typed temperature-bearing behavior metadata: " +
|
|
string.Join(", ", missingTradeSymbols));
|
|
}
|
|
|
|
if (inventoryMenu.Contains("ProjectCargoItemBehaviorMetric(", StringComparison.Ordinal) ||
|
|
inventoryMenu.Contains("ProjectEquippedItemBehaviorMetric(", StringComparison.Ordinal) ||
|
|
tradeMenu.Contains("ProjectTradeItemBehaviorMetric(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity menus must not own typed behavior metric projection after runtime surface builders take that role.");
|
|
}
|
|
}
|
|
|
|
static void RequireNameToolsUsesUiToolkit(string root)
|
|
{
|
|
var nameToolsPath = Path.Combine(root, "Assets", "Scripts", "Editor", "NameTools.cs");
|
|
var source = File.Exists(nameToolsPath)
|
|
? File.ReadAllText(nameToolsPath)
|
|
: throw new InvalidOperationException("Cannot verify NameTools editor shell; NameTools.cs is missing.");
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"using UnityEditor.UIElements;",
|
|
"using UnityEngine.UIElements;",
|
|
"private void CreateGUI()",
|
|
"new ObjectField(\"Name File\")",
|
|
"new IntegerField(",
|
|
"new Toggle(\"Strip Number Tokens\")",
|
|
"new Button(CleanNameFile)",
|
|
"new Button(ProcessNameFile)",
|
|
"new Button(GenerateName)"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"NameTools no longer owns a UI Toolkit editor shell for the remaining name helper workflow: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"void OnGUI()",
|
|
"EditorGUILayout.",
|
|
"GUILayout."
|
|
};
|
|
|
|
var hits = forbiddenSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (hits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"NameTools should not regress to IMGUI editor widgets: " +
|
|
string.Join(", ", hits));
|
|
}
|
|
}
|
|
|
|
static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root)
|
|
{
|
|
var runtimeStateRefResolverPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateRefResolver.cs");
|
|
var runtimeSurfaceDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSurfaceDocuments.cs");
|
|
var unityPackageProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj");
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var gameplayBootShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs");
|
|
var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs");
|
|
var eveSurfacePresenterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveSurfacePresenter.cs");
|
|
var eveUnitySurfaceHostPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveUnitySurfaceHost.cs");
|
|
|
|
var requiredPaths = new[]
|
|
{
|
|
runtimeStateRefResolverPath,
|
|
runtimeSurfaceDocumentsPath,
|
|
unityPackageProjectPath,
|
|
actionGameManagerPath,
|
|
gameplayBootShellPath,
|
|
mainMenuPath,
|
|
eveSurfacePresenterPath,
|
|
eveUnitySurfaceHostPath
|
|
};
|
|
|
|
var missingPaths = requiredPaths
|
|
.Where(path => !File.Exists(path))
|
|
.Select(path => Path.GetRelativePath(root, path))
|
|
.ToArray();
|
|
|
|
if (missingPaths.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Verse client state authority cannot be verified because required files are missing: " +
|
|
string.Join(", ", missingPaths));
|
|
}
|
|
|
|
var runtimeStateRefResolver = File.ReadAllText(runtimeStateRefResolverPath);
|
|
var runtimeSurfaceDocuments = File.ReadAllText(runtimeSurfaceDocumentsPath);
|
|
var unityPackageProject = File.ReadAllText(unityPackageProjectPath);
|
|
var actionGameManager = File.ReadAllText(actionGameManagerPath);
|
|
var gameplayBootShell = File.ReadAllText(gameplayBootShellPath);
|
|
var mainMenu = File.ReadAllText(mainMenuPath);
|
|
var eveSurfacePresenter = File.ReadAllText(eveSurfacePresenterPath);
|
|
var eveUnitySurfaceHost = File.ReadAllText(eveUnitySurfaceHostPath);
|
|
|
|
var requiredResolverSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeStateRefResolver",
|
|
"TryResolveDaemonStateRef",
|
|
"TryResolveDaemonItemStatRef",
|
|
"AetheriaRuntimeDaemonItemStatQueries.StateRefPrefix",
|
|
"AetheriaRuntimeDaemonStateRefs.CurrentEntityName",
|
|
"public static CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver(",
|
|
"Func<AetheriaRuntimeDaemonFrameDocument?> frameProvider",
|
|
"Func<AetheriaRuntimeDaemonHealthDocument?> healthProvider",
|
|
"Func<AetheriaRuntimeDaemonCommandBoundaryDocument?> commandBoundaryProvider",
|
|
"frameProvider?.Invoke()",
|
|
"catalogProvider?.Invoke()",
|
|
"CultMesh.StateRefResolver(",
|
|
"FindDaemonItem("
|
|
};
|
|
|
|
var missingResolverSymbols = requiredResolverSymbols
|
|
.Where(symbol => !runtimeStateRefResolver.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingResolverSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime state-ref resolver is incomplete: " +
|
|
string.Join(", ", missingResolverSymbols));
|
|
}
|
|
|
|
if (runtimeStateRefResolver.Contains("public static class AetheriaRuntimeStateReader", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime state reader compatibility wrapper still exists; use AetheriaRuntimeStateRefResolver directly.");
|
|
}
|
|
|
|
if (runtimeStateRefResolver.Contains("ReadEntitySnapshots", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime state-ref resolver still exposes file-backed entity snapshot reads; use daemon zone-render EntitySnapshots documents.");
|
|
}
|
|
|
|
if (runtimeStateRefResolver.Contains("TryReadDaemonRenderView", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("TryReadDaemonSoaView(", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("TryReadDaemonFrame(", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("ResolveEveSurfaceStateRef(", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("TryResolveEveSurfaceStateRef(", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("CreateEveSurfaceCultMeshStateRefResolver(\r\n string stateFilePath", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("CreateEveSurfaceCultMeshStateRefResolver(\n string stateFilePath", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("ReadEveSurface(", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("TryReadDaemonGameSurface(", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("TryReadDaemonGameTuiSurface(", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("TryReadDaemonEditorSurface(", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("TryReadDaemonEditorTuiSurface(", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("OpenRuntimeCatalog(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime state-ref resolver still exposes daemon file reads; use AetheriaClient and managed typed documents.");
|
|
}
|
|
|
|
if (runtimeStateRefResolver.Contains("ReadRunStates", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("ReadZoneStates", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime state-ref resolver still exposes file-backed run/zone snapshot reads; use managed daemon frame and zone-render documents.");
|
|
}
|
|
|
|
if (runtimeStateRefResolver.Contains("ReadPlayerSettings", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("ReadVerseHostSettings", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("ReadLoadoutTemplates", StringComparison.Ordinal) ||
|
|
runtimeStateRefResolver.Contains("ReadTradeValuePolicy", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime state-ref resolver still exposes file-backed catalog/settings reads; use AetheriaClient managed document handles.");
|
|
}
|
|
|
|
var runtimeCatalogStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs");
|
|
var runtimeCatalogStore = File.Exists(runtimeCatalogStorePath)
|
|
? File.ReadAllText(runtimeCatalogStorePath)
|
|
: throw new InvalidOperationException("Cannot verify deleted file-backed runtime catalog readers; AetheriaRuntimeCatalogStore.cs is missing.");
|
|
if (runtimeCatalogStore.Contains("ReadEntitySnapshots", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("ReadEntitySnapshotPayload", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("EntitySnapshotSchema", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime catalog store still exposes file-backed entity snapshot projection; daemon zone-render EntitySnapshots owns runtime entity lowering.");
|
|
}
|
|
|
|
if (runtimeCatalogStore.Contains("ReadRunStates", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("ReadZoneStates", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("ReadRunStatePayload", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("ReadZoneStatePayload", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("RunStateSchema", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("ZoneStateSchema", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime catalog store still exposes file-backed run/zone snapshot projection; managed daemon checkpoint and zone-render documents own runtime state lowering.");
|
|
}
|
|
|
|
if (runtimeCatalogStore.Contains("ReadEveSurfaces", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("ReadEveSurface(", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("ProjectStatRecipeSurfaceDocument", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("ProjectTradeValuePolicySurfaceDocument", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("EveSurfaceSchema", StringComparison.Ordinal) ||
|
|
runtimeCatalogStore.Contains("ReadFieldEveSurface", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime catalog store still exposes Eve-surface parsing/projection helpers; surface access must flow through managed CultMesh EveSurfaceDocument handles.");
|
|
}
|
|
|
|
if (!unityPackageProject.Contains("AetheriaRuntimeStateRefResolver.cs", StringComparison.Ordinal) ||
|
|
unityPackageProject.Contains("AetheriaRuntimeStateReader.cs", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"GameCult.Aetheria.State.Unity.csproj must include AetheriaRuntimeStateRefResolver.cs and not the old state reader filename.");
|
|
}
|
|
|
|
if (!unityPackageProject.Contains("AetheriaRuntimeDaemonStateRefs.cs", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"GameCult.Aetheria.State.Unity.csproj does not include the shared daemon state-ref vocabulary.");
|
|
}
|
|
|
|
var requiredSurfaceDocumentSymbols = new[]
|
|
{
|
|
"public static class AetheriaRuntimeSurfaceDocuments",
|
|
"public static EveUiSurfaceDocument ToPortableSurface(AetheriaRuntimeSurfaceDocument document)",
|
|
"public static EveUiSurfaceDocument ToEveSurfaceDocument(AetheriaRuntimeSurfaceDocument document)",
|
|
"public static EveUiSurfaceDocument ToEveSurfaceDocument(EveUiSurfaceDocument document)",
|
|
"public static EveUiSurfaceDocument ResolveStateRefs(",
|
|
"CultMeshStateRefResolver? stateRefResolver",
|
|
"ResolvePropRefs(props, resolveStateRef)",
|
|
"ResolvePropRef(props, AetheriaRuntimeSurfaceStateRefs.Source, \"value\", resolveStateRef)",
|
|
"IsStatePointerProp(prop.Key)",
|
|
"ResolvePointerValueKey(refProp.Key)",
|
|
"public static EveUiSurfaceDocument EmptySurface(string surfaceId)",
|
|
"new EveUiSurfaceDocument(",
|
|
"new EveUiSurfaceComponent("
|
|
};
|
|
var missingSurfaceDocumentSymbols = requiredSurfaceDocumentSymbols
|
|
.Where(symbol => !runtimeSurfaceDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSurfaceDocumentSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime surface documents are incomplete: " +
|
|
string.Join(", ", missingSurfaceDocumentSymbols));
|
|
}
|
|
|
|
if (!unityPackageProject.Contains("AetheriaRuntimeSurfaceDocuments.cs", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"GameCult.Aetheria.State.Unity.csproj does not include the shared runtime surface documents.");
|
|
}
|
|
|
|
var daemonObserverPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonObserver.cs");
|
|
var daemonObserver = File.Exists(daemonObserverPath)
|
|
? File.ReadAllText(daemonObserverPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaDaemonObserver.cs is missing.");
|
|
var observedFrameApplierPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedFrameApplier.cs");
|
|
var observedFrameApplier = File.Exists(observedFrameApplierPath)
|
|
? File.ReadAllText(observedFrameApplierPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityObservedFrameApplier.cs is missing.");
|
|
var runtimeClientProviderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRuntimeClientProvider.cs");
|
|
var runtimeClientProvider = File.Exists(runtimeClientProviderPath)
|
|
? File.ReadAllText(runtimeClientProviderPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityRuntimeClientProvider.cs is missing.");
|
|
var unityDaemonRenderDocumentsPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityDaemonRenderDocuments.cs");
|
|
var unityDaemonRenderDocuments = File.Exists(unityDaemonRenderDocumentsPath)
|
|
? File.ReadAllText(unityDaemonRenderDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityDaemonRenderDocuments.cs is missing.");
|
|
var unityGameViewportDocumentsPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameViewportDocuments.cs");
|
|
var unityGameViewportDocuments = File.Exists(unityGameViewportDocumentsPath)
|
|
? File.ReadAllText(unityGameViewportDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityGameViewportDocuments.cs is missing.");
|
|
var zoneRendererPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs");
|
|
var zoneRenderer = File.Exists(zoneRendererPath)
|
|
? File.ReadAllText(zoneRendererPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; ZoneRenderer.cs is missing.");
|
|
var volumeCloudRendererPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "VolumeCloudRenderer.cs");
|
|
var volumeCloudRenderer = File.Exists(volumeCloudRendererPath)
|
|
? File.ReadAllText(volumeCloudRendererPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; VolumeCloudRenderer.cs is missing.");
|
|
var renderSplatViewportSourcePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRenderSplatViewportSource.cs");
|
|
var renderSplatViewportSource = File.Exists(renderSplatViewportSourcePath)
|
|
? File.ReadAllText(renderSplatViewportSourcePath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityRenderSplatViewportSource.cs is missing.");
|
|
var schematicDisplayPath = Path.Combine(root, "Assets", "Scripts", "UI", "HUD", "SchematicDisplay.cs");
|
|
var schematicDisplay = File.Exists(schematicDisplayPath)
|
|
? File.ReadAllText(schematicDisplayPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; SchematicDisplay.cs is missing.");
|
|
var menuPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuPanel.cs");
|
|
var menuPanel = File.Exists(menuPanelPath)
|
|
? File.ReadAllText(menuPanelPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; MenuPanel.cs is missing.");
|
|
var mapRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MapRenderer.cs");
|
|
var mapRenderer = File.Exists(mapRendererPath)
|
|
? File.ReadAllText(mapRendererPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; MapRenderer.cs is missing.");
|
|
var sectorMapPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorMap.cs");
|
|
var sectorMap = File.Exists(sectorMapPath)
|
|
? File.ReadAllText(sectorMapPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; SectorMap.cs is missing.");
|
|
var localMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "LocalMenu.cs");
|
|
var localMenu = File.Exists(localMenuPath)
|
|
? File.ReadAllText(localMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; LocalMenu.cs is missing.");
|
|
var sectorRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs");
|
|
var sectorRenderer = File.Exists(sectorRendererPath)
|
|
? File.ReadAllText(sectorRendererPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; SectorRenderer.cs is missing.");
|
|
var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs");
|
|
var tradeMenu = File.Exists(tradeMenuPath)
|
|
? File.ReadAllText(tradeMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; TradeMenu.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 daemon state acquisition; InventoryMenu.cs is missing.");
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var inventoryPanel = File.Exists(inventoryPanelPath)
|
|
? File.ReadAllText(inventoryPanelPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; InventoryPanel.cs is missing.");
|
|
var inputDisplayLayoutPath = Path.Combine(root, "Assets", "Scripts", "UI", "InputScreen", "InputDisplayLayout.cs");
|
|
var inputDisplayLayout = File.Exists(inputDisplayLayoutPath)
|
|
? File.ReadAllText(inputDisplayLayoutPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; InputDisplayLayout.cs is missing.");
|
|
var aetheriaClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClient.cs");
|
|
var aetheriaClient = File.Exists(aetheriaClientPath)
|
|
? File.ReadAllText(aetheriaClientPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaClient.cs is missing.");
|
|
var aetheriaClientStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClientState.cs");
|
|
var aetheriaClientState = File.Exists(aetheriaClientStatePath)
|
|
? File.ReadAllText(aetheriaClientStatePath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaClientState.cs is missing.");
|
|
var observedDaemonStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonRenderView.cs");
|
|
var observedDaemonState = File.Exists(observedDaemonStatePath)
|
|
? File.ReadAllText(observedDaemonStatePath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaRuntimeDaemonRenderView.cs is missing.");
|
|
var daemonRuntimeDocumentTestsPath = Path.Combine(root, "Assets", "Scripts", "Tests", "DaemonRuntimeDocumentTests.cs");
|
|
var daemonRuntimeDocumentTests = File.Exists(daemonRuntimeDocumentTestsPath)
|
|
? File.ReadAllText(daemonRuntimeDocumentTestsPath)
|
|
: throw new InvalidOperationException("Cannot verify daemon state acquisition; DaemonRuntimeDocumentTests.cs is missing.");
|
|
|
|
var requiredActionGameManagerSymbols = new[]
|
|
{
|
|
"private AetheriaUnityGameplayBootShell GameplayBootShell =>",
|
|
"var boot = GameplayBootShell.Boot();",
|
|
"SceneWiring.ConfigureCurrentEntityPresentation(_currentEntityPresentation, boot.RuntimeCatalog);",
|
|
"SceneWiring.ConfigureTargetPresentation(",
|
|
"SceneWiring.ConfigureActionBarPresentation(",
|
|
"ItemManager = boot.ItemManager;",
|
|
"_loadoutItemFactory = boot.LoadoutItemFactory;",
|
|
"AetheriaUnityRuntimeClientProvider.PlayerSettings",
|
|
"ResolveDaemonObserver()",
|
|
"private AetheriaUnityObservedFrameApplier ObservedFrameApplier =>"
|
|
};
|
|
|
|
var missingActionGameManagerSymbols = requiredActionGameManagerSymbols
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingActionGameManagerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager does not route typed state reads through the shared Verse client and Unity boot shell: " +
|
|
string.Join(", ", missingActionGameManagerSymbols));
|
|
}
|
|
|
|
var requiredGameplayBootShellSymbols = new[]
|
|
{
|
|
"public sealed class AetheriaUnityGameplayBootShell",
|
|
"public AetheriaUnityGameplayBootResult Boot()",
|
|
"AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory)",
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)",
|
|
"runtimeState.Catalog.Latest()",
|
|
"runtimeState.SectorMap.Latest()",
|
|
"new ItemManager(",
|
|
"new AetheriaUnityLoadoutItemFactory(itemManager, runtimeCatalog)",
|
|
"ZoneRenderer.SetDroppedPickupItemFactory(loadoutItemFactory.CreateLoadoutItem)",
|
|
"ZoneRenderer.BodySettingsCollections = Settings.BodySettingsCollections",
|
|
"runtimeState?.DaemonFrame?.Latest()",
|
|
"render/game-feel settings are daemon authority",
|
|
"CockpitHudShell.SetRenderSettings(ZoneRenderer.RenderSettings)",
|
|
"AetheriaUnityRuntimeClientProvider.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap",
|
|
"public readonly struct AetheriaUnityGameplayBootResult"
|
|
};
|
|
|
|
var missingGameplayBootShellSymbols = requiredGameplayBootShellSymbols
|
|
.Where(symbol => !gameplayBootShell.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingGameplayBootShellSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity gameplay boot shell no longer owns typed runtime boot/catalog/render setup: " +
|
|
string.Join(", ", missingGameplayBootShellSymbols));
|
|
}
|
|
|
|
if (gameplayBootShell.Contains(".ObserveCatalog()", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains(".ObserveSectorMap()", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentCatalog(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentSectorMap(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.ResolveClient(stateBoot.StateFilePath, stateBoot.RuntimeId)", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains(".State;"))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityGameplayBootShell must acquire runtime state through provider-native typed state access, not legacy wrappers or full-client peels.");
|
|
}
|
|
|
|
var requiredRuntimeClientProviderSymbols = new[]
|
|
{
|
|
"public static class AetheriaUnityRuntimeClientProvider",
|
|
"public static RuntimePlayerSettings PlayerSettings",
|
|
"private static AetheriaClient ResolveClient(string stateFilePath, string runtimeId = \"\")",
|
|
"private static AetheriaClient ResolveClient(AetheriaRuntimeStateBootReport stateBoot, string runtimeId = \"\")",
|
|
"private static AetheriaClient RuntimeClient(string runtimeId = \"\")",
|
|
"public static AetheriaClientState RuntimeState(string runtimeId = \"\")",
|
|
"public static AetheriaUnityGameViewportDocuments MapViewportDocuments(",
|
|
"public static AetheriaUnityGameViewportDocuments ZonePresentationDocuments(",
|
|
"public static AetheriaUnityGameViewportDocuments RenderSplatViewportDocuments(",
|
|
"public static AetheriaUnityDaemonRenderDocuments DaemonRenderDocuments(",
|
|
"public static AetheriaControl Control(string runtimeId = \"\")",
|
|
"public static AetheriaUi Ui(string runtimeId = \"\")",
|
|
"public static AetheriaUi Ui(",
|
|
"public static CultMeshStateRefResolver EveSurfaceCultMeshStateRefResolver(string runtimeId = \"\")",
|
|
"public static CultMeshStateRefResolver EveSurfaceCultMeshStateRefResolver(",
|
|
"private static readonly Dictionary<string, AetheriaClient> RuntimeClients",
|
|
"RuntimeClients.TryGetValue(cacheKey, out var runtimeClient)",
|
|
"RuntimeClients[cacheKey] = runtimeClient",
|
|
"AetheriaClient",
|
|
".State",
|
|
"OpenAsync(",
|
|
"pullOnOpen: true",
|
|
"ApplyPlayerSettings(settings, stored)"
|
|
};
|
|
var missingRuntimeClientProviderSymbols = requiredRuntimeClientProviderSymbols
|
|
.Where(symbol => !runtimeClientProvider.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingRuntimeClientProviderSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime client/player-settings boot must live behind AetheriaUnityRuntimeClientProvider: " +
|
|
string.Join(", ", missingRuntimeClientProviderSymbols));
|
|
}
|
|
|
|
if (runtimeClientProvider.Contains("public static CultMeshReactiveDocument<TDocument> Reactive<TDocument>", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static CultMeshReactiveDocument<AetheriaRuntimeZoneContactsDocument> ReactiveZoneContacts(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static CultMeshReactiveDocument<AetheriaRuntimeObjectsViewportDocument> ReactiveObjectsViewport(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static CultMeshReactiveDocument<EveFieldsSplatsDocument> ReactiveRenderSplatsViewport(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static CultMeshReactiveDocument<global::Aetheria.State.Documents.EveSurfaceState> ReactiveEveSurface(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("private static CultMeshReactiveDocument<AetheriaRuntimePlayerSettingsDocument> _playerSettingsDocument", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("RuntimeState().ReactivePlayerSettingsDocument()", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("_playerSettingsDocument.Current", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("_playerSettingsDocument?.Dispose()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityRuntimeClientProvider should expose named current/reactive typed handles; do not rebuild generic static ReactiveX facade wrappers or cached reactive state.");
|
|
}
|
|
|
|
if (runtimeClientProvider.Contains("public static AetheriaRuntimeCurrentDockingDocument CurrentDockingState(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeCatalogSnapshot CurrentCatalog(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimePlayerSettingsDocument CurrentPlayerSettings(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeCurrentEntityDocument CurrentEntityState(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeCurrentZoneDocument CurrentZoneState(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeZoneContactsDocument CurrentZoneContacts(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeStationRefitDocument CurrentStationRefit(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeSectorMapDocument CurrentSectorMap(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeZoneDetailsDocument CurrentZoneDetails(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeSurfaceDocument CurrentZoneDetailsSurface(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeSurfaceDocument CurrentInventoryPanelSurface(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeSurfaceDocument CurrentInventoryDropdownSurface(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeInventoryDocument CurrentInventory(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeDaemonFrameDocument CurrentDaemonFrame(", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaRuntimeVerseHostSettingsDocument CurrentVerseHostSettings(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityRuntimeClientProvider must expose managed AetheriaClientState handles; provider-level CurrentX snapshot shortcuts are heretek.");
|
|
}
|
|
|
|
if (runtimeClientProvider.Contains("AetheriaRuntimePlayerSettingsSession _playerSettingsDocument", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaClient ResolveClient", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("public static AetheriaClient RuntimeClient", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("CurrentClientForStateFile", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains("StatePathCachePrefix", StringComparison.Ordinal) ||
|
|
runtimeClientProvider.Contains(".ObservePlayer()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityRuntimeClientProvider must expose narrow state/control/UI handles, not legacy sessions or ambient current-client lookups.");
|
|
}
|
|
|
|
var providerOwnedClientAccessSources = new Dictionary<string, string>
|
|
{
|
|
["Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs"] = eveSurfacePresenter,
|
|
["Assets/Scripts/Zone Display/ZoneRenderer.cs"] = zoneRenderer,
|
|
["Assets/Scripts/Zone Display/VolumeCloudRenderer.cs"] = volumeCloudRenderer,
|
|
["Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs"] = renderSplatViewportSource,
|
|
["Assets/Scripts/UI/Menu/MenuPanel.cs"] = menuPanel,
|
|
["Assets/Scripts/UI/Menu/MapRenderer.cs"] = mapRenderer,
|
|
["Assets/Scripts/UI/Menu/SectorMap.cs"] = sectorMap,
|
|
["Assets/Scripts/UI/Menu/SectorRenderer.cs"] = sectorRenderer,
|
|
["Assets/Scripts/UI/HUD/SchematicDisplay.cs"] = schematicDisplay,
|
|
["Assets/Scripts/UI/Menu/TradeMenu.cs"] = tradeMenu,
|
|
["Assets/Scripts/UI/Menu/InventoryMenu.cs"] = inventoryMenu,
|
|
["Assets/Scripts/UI/Menu/InventoryPanel.cs"] = inventoryPanel,
|
|
["Assets/Scripts/UI/MainMenu.cs"] = mainMenu,
|
|
["Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs"] = inputDisplayLayout,
|
|
["Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs"] = daemonObserver
|
|
};
|
|
var directClientOpenHits = providerOwnedClientAccessSources
|
|
.Where(pair =>
|
|
pair.Value.Contains(".OpenAsync(", StringComparison.Ordinal) ||
|
|
pair.Value.Contains(".OpenLocalAsync(", StringComparison.Ordinal))
|
|
.Select(pair => pair.Key)
|
|
.ToArray();
|
|
if (directClientOpenHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime readers must acquire managed typed state through AetheriaUnityRuntimeClientProvider instead of opening local clients: " +
|
|
string.Join(", ", directClientOpenHits));
|
|
}
|
|
|
|
var missingProviderClientAccess = providerOwnedClientAccessSources
|
|
.Where(pair =>
|
|
!pair.Value.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) &&
|
|
!pair.Value.Contains("AetheriaEveRuntimeUnityHooks", StringComparison.Ordinal))
|
|
.Select(pair => pair.Key)
|
|
.ToArray();
|
|
if (missingProviderClientAccess.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime readers no longer use the shared managed runtime client provider: " +
|
|
string.Join(", ", missingProviderClientAccess));
|
|
}
|
|
|
|
if (!observedFrameApplier.Contains("private AetheriaRuntimeZoneRenderDocument _lastZoneRender;", StringComparison.Ordinal) ||
|
|
!observedFrameApplier.Contains("ApplyLatestZoneRender()", StringComparison.Ordinal) ||
|
|
!observedFrameApplier.Contains("observer.LastRenderView?.ZoneRender", StringComparison.Ordinal) ||
|
|
!observedDaemonState.Contains("public AetheriaRuntimeZoneRenderDocument ZoneRender { get; }", StringComparison.Ordinal) ||
|
|
!observedDaemonState.Contains("CultMeshReactiveDocument<AetheriaRuntimeZoneRenderDocument> zoneRender", StringComparison.Ordinal) ||
|
|
!observedDaemonState.Contains("var currentZoneRender = zoneRender?.Current;", StringComparison.Ordinal) ||
|
|
!observedDaemonState.Contains("new AetheriaRuntimeDaemonRenderView(currentFrame, currentSoaView, currentZoneRender)", StringComparison.Ordinal) ||
|
|
!unityDaemonRenderDocuments.Contains("public sealed class AetheriaUnityDaemonRenderDocuments : IDisposable", StringComparison.Ordinal) ||
|
|
!unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> _frame;", StringComparison.Ordinal) ||
|
|
!unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument<AetheriaRuntimeDaemonSoaViewDocument> _soaView;", StringComparison.Ordinal) ||
|
|
!unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument<AetheriaRuntimeZoneRenderDocument> _zoneRender;", StringComparison.Ordinal) ||
|
|
!unityDaemonRenderDocuments.Contains("public AetheriaRuntimeDaemonRenderView Current", StringComparison.Ordinal) ||
|
|
!unityDaemonRenderDocuments.Contains("AetheriaRuntimeDaemonRenderView.TryCreateCurrent(", StringComparison.Ordinal) ||
|
|
!daemonObserver.Contains("private AetheriaUnityDaemonRenderDocuments _renderDocuments;", StringComparison.Ordinal) ||
|
|
!daemonObserver.Contains("_renderDocuments?.Current", StringComparison.Ordinal) ||
|
|
!daemonObserver.Contains("AetheriaUnityRuntimeClientProvider.DaemonRenderDocuments(stateBoot, clientId)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed zone-render state acquisition must sample managed typed zone-render documents directly.");
|
|
}
|
|
|
|
if (unityDaemonRenderDocuments.Contains("public CultMeshReactiveDocument<", StringComparison.Ordinal) ||
|
|
unityGameViewportDocuments.Contains("public CultMeshReactiveDocument<", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity managed document holders must not leak raw CultMesh reactive documents through their public surface.");
|
|
}
|
|
|
|
var requiredDaemonStateFacadeSymbols = new[]
|
|
{
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonProviderAdvertisementDocument> ProviderAdvertisement { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonHealthDocument> Health { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonCommandBoundaryDocument> CommandBoundary { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeVerseAuthorityPolicyDocument> AuthorityPolicy { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> DaemonFrame { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> DaemonSoaView { get; }",
|
|
"using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument;",
|
|
"public CultMeshDocumentHandle<EveSurfaceDocument> GameSurface { get; }",
|
|
"public CultMeshDocumentHandle<EveSurfaceDocument> GameTuiSurface { get; }",
|
|
"public CultMeshDocumentHandle<EveSurfaceDocument> EditorSurface { get; }",
|
|
"public CultMeshDocumentHandle<EveSurfaceDocument> EditorTuiSurface { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> Catalog { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeLoadoutTemplatesDocument> LoadoutTemplates { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimePlayerSettingsDocument> PlayerSettings { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeVerseHostSettingsDocument> VerseHostSettings { get; }"
|
|
};
|
|
var missingDaemonStateFacadeSymbols = requiredDaemonStateFacadeSymbols
|
|
.Where(symbol => !aetheriaClientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonStateFacadeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose daemon service state as managed typed document handles: " +
|
|
string.Join(", ", missingDaemonStateFacadeSymbols));
|
|
}
|
|
|
|
var forbiddenDaemonStateFacadeSymbols = new[]
|
|
{
|
|
"public AetheriaClientDaemonState Daemon { get; }",
|
|
"public sealed class AetheriaClientDaemonState",
|
|
"public AetheriaClientSettingsState Settings { get; }",
|
|
"public sealed class AetheriaClientSettingsState",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> LatestFrame { get; }",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> LatestSoaView { get; }"
|
|
};
|
|
var survivingDaemonStateFacadeSymbols = forbiddenDaemonStateFacadeSymbols
|
|
.Where(symbol => aetheriaClientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (survivingDaemonStateFacadeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState still exposes daemon/settings facade wrappers instead of flat managed typed document handles: " +
|
|
string.Join(", ", survivingDaemonStateFacadeSymbols));
|
|
}
|
|
|
|
var requiredManagedClientConnectionSymbols = new[]
|
|
{
|
|
"public AetheriaClientState State => _state;",
|
|
"var frame = CurrentDaemonFrame();",
|
|
"private CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument>? _daemonFrame;",
|
|
"_daemonFrame ??= State.DaemonFrame.Reactive();"
|
|
};
|
|
var missingManagedClientConnectionSymbols = requiredManagedClientConnectionSymbols
|
|
.Where(symbol => !aetheriaClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingManagedClientConnectionSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClient must expose the managed state object without owning document-catalog shortcuts: " +
|
|
string.Join(", ", missingManagedClientConnectionSymbols));
|
|
}
|
|
|
|
if (aetheriaClient.Contains("TryReactiveDaemonSoaView()", StringComparison.Ordinal) ||
|
|
aetheriaClient.Contains("CurrentObservedDaemon()", StringComparison.Ordinal) ||
|
|
aetheriaClient.Contains("State.CurrentObservedDaemon()", StringComparison.Ordinal) ||
|
|
aetheriaClient.Contains("State.ReactiveObservedDaemon()", StringComparison.Ordinal) ||
|
|
aetheriaClient.Contains("Aetheria() => State", StringComparison.Ordinal) ||
|
|
aetheriaClient.Contains("AetheriaRuntimeDaemonRenderView.TryCreateCurrent(", StringComparison.Ordinal) ||
|
|
aetheriaClient.Contains("AetheriaRuntimeDaemonRenderView?", StringComparison.Ordinal) ||
|
|
aetheriaClient.Contains("observedState.TryCurrent(out var current)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClient command submission still samples observed daemon state through an aggregate reactive wrapper instead of direct managed typed documents.");
|
|
}
|
|
|
|
var requiredManagedStateAccessSymbols = new[]
|
|
{
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> DaemonFrame",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> Catalog",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeCurrentEntityDocument> CurrentEntity",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeObjectsViewportDocument> ObjectsViewport(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument> Inventory(",
|
|
"public CultMeshDocumentHandle<AetheriaRuntimeStarbridgePlayerSeatDocument> StarbridgePlayerSeat("
|
|
};
|
|
var missingManagedStateAccessSymbols = requiredManagedStateAccessSymbols
|
|
.Where(symbol => !aetheriaClientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingManagedStateAccessSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must expose direct named typed document handles instead of AetheriaClient forwarding shortcuts or per-document session wrappers: " +
|
|
string.Join(", ", missingManagedStateAccessSymbols));
|
|
}
|
|
|
|
if (aetheriaClientState.Contains("public Task<TDocument> LatestAsync<TDocument>", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("public TDocument Latest<TDocument>", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("public Observable<TDocument> Watch<TDocument>", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("public CultMeshDocumentHandle<TDocument> Document<TDocument>", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("public CultMeshReactiveDocument<TDocument> Reactive<TDocument>", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("TryGetDocument<TDocument>", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("CultMeshDocumentCatalog", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("public Task<CultMeshReactiveDocument<TDocument>> ReactiveAsync<TDocument>", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("DocumentBySchema(", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("TryGetDocumentBySchema(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState still exposes generic document catalogs, latest/watch, or schema-string shortcuts instead of direct named typed CultMesh document handles.");
|
|
}
|
|
|
|
if (aetheriaClientState.Contains("public AetheriaRuntimeDaemonRenderView? CurrentObservedDaemon(", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("AetheriaRuntimeDaemonRenderView.TryCreateCurrent(frame, soaView, zoneRender", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState must not expose one-shot observed daemon aggregation; command owners should hold managed typed daemon documents.");
|
|
}
|
|
|
|
var requiredManagedCommandFrameSymbols = new[]
|
|
{
|
|
"internal AetheriaRuntimeDaemonFrameDocument? CurrentDaemonFrame()",
|
|
"_daemonFrame ??= State.DaemonFrame.Reactive();",
|
|
"return _daemonFrame.Current;",
|
|
"Func<AetheriaRuntimeDaemonOperationClient, AetheriaRuntimeDaemonFrameDocument?, AetheriaRuntimeDaemonCommandEnvelope> submit",
|
|
"frame?.SessionId ?? _sessionId"
|
|
};
|
|
var missingManagedCommandFrameSymbols = requiredManagedCommandFrameSymbols
|
|
.Where(symbol => !aetheriaClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingManagedCommandFrameSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClient command submission must sample only the managed daemon frame document instead of composing an observed render aggregate: " +
|
|
string.Join(", ", missingManagedCommandFrameSymbols));
|
|
}
|
|
|
|
if (!daemonRuntimeDocumentTests.Contains("ManagedStateSamplesCurrentDaemonRenderViewFromReactiveDocuments()", StringComparison.Ordinal) ||
|
|
!daemonRuntimeDocumentTests.Contains("using var observedFrame = client.State.DaemonFrame.Reactive();", StringComparison.Ordinal) ||
|
|
!daemonRuntimeDocumentTests.Contains("using var observedSoaView = client.State.DaemonSoaView.Reactive();", StringComparison.Ordinal) ||
|
|
!daemonRuntimeDocumentTests.Contains("using var observedZoneRender = client.State.ZoneRender.Reactive();", StringComparison.Ordinal) ||
|
|
!daemonRuntimeDocumentTests.Contains("AetheriaRuntimeDaemonRenderView.TryCreateCurrent(", StringComparison.Ordinal) ||
|
|
daemonRuntimeDocumentTests.Contains("client.State.CurrentObservedDaemon()", StringComparison.Ordinal) ||
|
|
daemonRuntimeDocumentTests.Contains("using var observedFrame = client.State.ReactiveDaemonFrame();", StringComparison.Ordinal) ||
|
|
daemonRuntimeDocumentTests.Contains("using var observedSoaView = client.State.ReactiveDaemonSoaView();", StringComparison.Ordinal) ||
|
|
daemonRuntimeDocumentTests.Contains("using var observedZoneRender = client.State.ReactiveZoneRender();", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon runtime document tests must teach observed daemon sampling through direct reactive daemon documents.");
|
|
}
|
|
|
|
var requiredObservedDaemonStateSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeDaemonSoaViewIndex.Build(soaView)",
|
|
"public static bool TryCreateCurrent(",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> frame",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeDaemonSoaViewDocument>? soaView",
|
|
"CultMeshReactiveDocument<AetheriaRuntimeZoneRenderDocument> zoneRender",
|
|
"var currentFrame = frame?.Current;",
|
|
"var currentZoneRender = zoneRender?.Current;",
|
|
"new AetheriaRuntimeDaemonRenderView(currentFrame, currentSoaView, currentZoneRender)"
|
|
};
|
|
var missingObservedDaemonStateSymbols = requiredObservedDaemonStateSymbols
|
|
.Where(symbol => !observedDaemonState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingObservedDaemonStateSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed daemon state must be composed from managed typed daemon documents: " +
|
|
string.Join(", ", missingObservedDaemonStateSymbols));
|
|
}
|
|
|
|
if (observedDaemonState.Contains("AetheriaRuntimeDaemonFrameSession", StringComparison.Ordinal) ||
|
|
observedDaemonState.Contains("AetheriaRuntimeDaemonSoaViewSession", StringComparison.Ordinal) ||
|
|
observedDaemonState.Contains("AetheriaRuntimeZoneRenderSession", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("TryObserveDaemonSoaView(options)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed daemon state still composes legacy session wrappers instead of direct reactive CultMesh documents.");
|
|
}
|
|
|
|
var clientStateSessionWrapperSymbols = new[]
|
|
{
|
|
"Session Observe",
|
|
"new AetheriaRuntimeCatalogSession",
|
|
"new AetheriaRuntimeDaemonFrameSession",
|
|
"new AetheriaRuntimeLoadoutTemplatesSession",
|
|
"new AetheriaRuntimeSectorMapSession",
|
|
"new AetheriaRuntimeZoneContactsSession",
|
|
"new AetheriaRuntimeStationRefitSession",
|
|
"new AetheriaRuntimeZoneRenderSession",
|
|
"new AetheriaRuntimeCurrentZoneSession",
|
|
"new AetheriaRuntimeCurrentEntitySession",
|
|
"new AetheriaRuntimeCurrentDockingSession",
|
|
"new AetheriaRuntimePlayerSettingsSession",
|
|
"new AetheriaRuntimeVerseHostSettingsSession",
|
|
"new AetheriaRuntimeObjectsViewportSession",
|
|
"new AetheriaRuntimeRenderSplatsViewportSession",
|
|
"new AetheriaRuntimeInventorySession",
|
|
"new AetheriaRuntimeStarbridgeScenarioSession",
|
|
"new AetheriaRuntimeStarbridgeRunSession",
|
|
"new AetheriaRuntimeStarbridgeSummarySession",
|
|
"new AetheriaRuntimeStarbridgePlayerSeatSession"
|
|
};
|
|
var clientStateSessionWrapperHits = clientStateSessionWrapperSymbols
|
|
.Where(symbol => aetheriaClientState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (clientStateSessionWrapperHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState still exposes per-document Observe* session wrappers instead of direct Reactive* document access: " +
|
|
string.Join(", ", clientStateSessionWrapperHits));
|
|
}
|
|
|
|
var publicObserveAccessorHits = System.Text.RegularExpressions.Regex
|
|
.Matches(
|
|
aetheriaClientState,
|
|
@"public\s+[^{;=]+\s+Observe[A-Za-z0-9_]*\s*\(",
|
|
System.Text.RegularExpressions.RegexOptions.CultureInvariant)
|
|
.Select(match => match.Value.Trim())
|
|
.ToArray();
|
|
if (publicObserveAccessorHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClientState reintroduced public Observe* state accessors; expose direct Reactive* typed documents instead: " +
|
|
string.Join(", ", publicObserveAccessorHits));
|
|
}
|
|
|
|
var runtimeStateSessionsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateSessions.cs");
|
|
if (File.Exists(runtimeStateSessionsPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeStateSessions.cs still exists; per-document session wrappers must be deleted in favor of direct CultMesh reactive documents.");
|
|
}
|
|
|
|
var forbiddenObservedDaemonStateSymbols = new[]
|
|
{
|
|
"string statePath",
|
|
"FramePath",
|
|
"SoaViewPath",
|
|
"AetheriaRuntimeDaemonFrameStore.GetFramePath",
|
|
"SoaViewStore.GetViewPath",
|
|
"state.LatestFrame.ReactiveAsync",
|
|
"state.LatestSoaView.ReactiveAsync",
|
|
"public static async Task<AetheriaRuntimeDaemonRenderView?> ReadAsync(",
|
|
"LatestObservedDaemonAsync(",
|
|
"LatestObservedDaemon()",
|
|
"public sealed class AetheriaRuntimeReactiveObservedDaemonState",
|
|
"ReactiveObservedDaemonAsync",
|
|
"ReactiveObservedDaemon(",
|
|
"TryCurrent(out AetheriaRuntimeDaemonRenderView? observed)",
|
|
"return new AetheriaRuntimeDaemonRenderView(frame, soaView);",
|
|
"AetheriaRuntimeGameDocuments.ZoneRender(Frame)",
|
|
"zoneRender = null"
|
|
};
|
|
var observedDaemonStateBypassHits = forbiddenObservedDaemonStateSymbols
|
|
.Where(symbol => observedDaemonState.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (observedDaemonStateBypassHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Observed daemon state still projects managed daemon documents back into witness file paths: " +
|
|
string.Join(", ", observedDaemonStateBypassHits));
|
|
}
|
|
|
|
var forbiddenDaemonClientBypassSymbols = new[]
|
|
{
|
|
".ReadAsync(State)",
|
|
"return _verse.GetObservedDaemonStateAsync();",
|
|
"return _verse.GetLatestAuthoritativeRunFrameAsync();",
|
|
"return _verse.GetHealthAsync();",
|
|
"return _verse.GetVerseAuthorityPolicyAsync();",
|
|
"return _verse.GetLatestSoaViewAsync();",
|
|
"AetheriaRuntimeDaemonPublicationStore.TryReadVerseAuthorityPolicy",
|
|
"return _verse.GetDaemonGameSurfaceAsync();",
|
|
"return _verse.GetDaemonGameTuiSurfaceAsync();",
|
|
"return _verse.GetDaemonEditorSurfaceAsync();",
|
|
"return _verse.GetDaemonEditorTuiSurfaceAsync();",
|
|
"public AetheriaRuntimeCatalogSnapshot OpenRuntimeCatalog()",
|
|
"public async Task<AetheriaRuntimePlayerSettingsSnapshot?> PlayerSettingsAsync()",
|
|
"public async Task<AetheriaRuntimeVerseHostSettingsSnapshot?> VerseHostSettingsAsync()",
|
|
"public async Task<System.Collections.Generic.IReadOnlyList<AetheriaRuntimeLoadoutTemplateSnapshot>> LoadoutTemplatesAsync()",
|
|
"public async Task<AetheriaRuntimeDaemonRenderView?> ObserveAsync()",
|
|
"public async Task<AetheriaRuntimeLoadoutTemplateCommit> LoadoutTemplateAsync(",
|
|
"scenario ??= await _verse.GetStarbridgeScenarioAsync()",
|
|
"session ??= await _verse.GetStarbridgeSessionAsync()",
|
|
"var frame = await _verse.GetLatestFrameAsync()",
|
|
"public async Task<AetheriaRuntimeDaemonFrameDocument?> LatestAuthoritativeRunFrameAsync()",
|
|
"public async Task<AetheriaRuntimeGameViewportDocument> MapViewportAsync(",
|
|
"public async Task<AetheriaRuntimeObjectsViewportDocument> ObjectsViewportAsync(",
|
|
"public async Task<AetheriaRuntimeGravityViewportDocument> GravityViewportAsync(",
|
|
"public async Task<EveFieldsSplatsDocument> RenderSplatsViewportAsync(",
|
|
"public async Task<AetheriaRuntimeAssetManifestDocument> AssetManifestAsync()",
|
|
"public async Task<AetheriaRuntimeCurrentZoneDocument> CurrentZoneAsync()",
|
|
"public async Task<AetheriaRuntimeCurrentEntityDocument> CurrentEntityAsync()",
|
|
"public async Task<AetheriaRuntimeCurrentDocking> CurrentDockingAsync()",
|
|
"public async Task<AetheriaRuntimeZoneContactsDocument> ZoneContactsAsync()",
|
|
"public async Task<AetheriaRuntimeStationRefitDocument> StationRefitAsync()",
|
|
"public async Task<AetheriaRuntimeSectorMapDocument> SectorMapAsync()",
|
|
"public async Task<AetheriaRuntimeZoneDetailsDocument> ZoneDetailsAsync(",
|
|
"public async Task<AetheriaRuntimeZoneRenderDocument> ZoneRenderAsync()",
|
|
"public async Task<AetheriaRuntimeStarbridgeSessionSummaryDocument> StarbridgeSessionSummaryAsync(",
|
|
"public async Task<AetheriaRuntimeSelectedObjectDocument> SelectedObjectAsync(",
|
|
"public async Task<AetheriaRuntimeInventoryDocument> InventoryAsync(",
|
|
"public async Task<AetheriaRuntimeDaemonHealthDocument?> DaemonHealthAsync()",
|
|
"public async Task<AetheriaRuntimeVerseAuthorityPolicyDocument?> AuthorityStatusAsync()",
|
|
"public async Task<AetheriaRuntimeDaemonSoaViewDocument?> SoaViewAsync()",
|
|
"public async Task<global::Aetheria.State.Documents.EveSurfaceState?> DaemonGameSurfaceAsync()",
|
|
"public async Task<global::Aetheria.State.Documents.EveSurfaceState?> DaemonGameTuiSurfaceAsync()",
|
|
"public async Task<global::Aetheria.State.Documents.EveSurfaceState?> DaemonEditorSurfaceAsync()",
|
|
"public async Task<global::Aetheria.State.Documents.EveSurfaceState?> DaemonEditorTuiSurfaceAsync()",
|
|
"public CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver()",
|
|
"public CultMeshDocumentHandle<TDocument> Document<TDocument>()",
|
|
"public Observable<TDocument> Watch<TDocument>()",
|
|
"public Task<CultMeshReactiveDocument<TDocument>> ReactiveAsync<TDocument>(",
|
|
"public CultMeshReactiveDocument<TDocument> Reactive<TDocument>("
|
|
};
|
|
var daemonClientBypassHits = forbiddenDaemonClientBypassSymbols
|
|
.Where(symbol => aetheriaClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (daemonClientBypassHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaClient still bypasses managed daemon documents for compatibility reads: " +
|
|
string.Join(", ", daemonClientBypassHits));
|
|
}
|
|
|
|
var forbiddenActionGameManagerReaderSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeStateReader.ReadPlayerSettings",
|
|
"AetheriaRuntimeStateReader.ReadLoadoutTemplates",
|
|
"AetheriaRuntimeStateReader.OpenRuntimeCatalog",
|
|
"LoadoutTemplatesAsync()",
|
|
"ObservedLoadoutTemplates",
|
|
"_observedLoadoutTemplates",
|
|
"LoadRuntimeLoadoutTemplates",
|
|
"AetheriaRuntimeStateBoot.Inspect(",
|
|
"ResolveClient(stateBoot.StateFilePath).OpenRuntimeCatalog()",
|
|
"ItemManager = new ItemManager(",
|
|
"ZoneRenderer.SetDroppedPickupItemFactory(",
|
|
"ZoneRenderer.BodySettingsCollections = Settings.BodySettingsCollections",
|
|
"ResolveRuntimeVerseClient(",
|
|
"PlayerSettingsAsync()",
|
|
"ApplyRuntimePlayerSettings(",
|
|
"CreateDefaultRuntimePlayerSettings(",
|
|
"_runtimeClient"
|
|
};
|
|
var actionGameManagerReaderHits = forbiddenActionGameManagerReaderSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (actionGameManagerReaderHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still reads typed state through the runtime file reader instead of the shared Aetheria client facade: " +
|
|
string.Join(", ", actionGameManagerReaderHits));
|
|
}
|
|
|
|
if (!daemonObserver.Contains("AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory)", StringComparison.Ordinal) ||
|
|
!daemonObserver.Contains("AetheriaRuntimeDaemonRenderView", StringComparison.Ordinal) ||
|
|
!daemonObserver.Contains("private AetheriaUnityDaemonRenderDocuments _renderDocuments;", StringComparison.Ordinal) ||
|
|
!daemonObserver.Contains("_renderDocuments?.Current", StringComparison.Ordinal) ||
|
|
!daemonObserver.Contains("AetheriaUnityRuntimeClientProvider.DaemonRenderDocuments(stateBoot, clientId)", StringComparison.Ordinal) ||
|
|
!unityDaemonRenderDocuments.Contains("public AetheriaRuntimeDaemonRenderView Current", StringComparison.Ordinal) ||
|
|
!unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> _frame;", StringComparison.Ordinal) ||
|
|
!unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument<AetheriaRuntimeDaemonSoaViewDocument> _soaView;", StringComparison.Ordinal) ||
|
|
!unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument<AetheriaRuntimeZoneRenderDocument> _zoneRender;", StringComparison.Ordinal) ||
|
|
!daemonObserver.Contains("DisposeRenderViewDocuments()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaDaemonObserver must hold a native managed daemon-render document handle and read its current state.");
|
|
}
|
|
|
|
if (daemonObserver.Contains("AetheriaRuntimeStateReader.TryReadDaemonRenderView", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("public AetheriaClient Client", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("public AetheriaControl Control", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("AetheriaUnityRuntimeClientProvider.ResolveClient(", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("AetheriaClientState ResolveState()", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, clientId)", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> _daemonFrame", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("CultMeshReactiveDocument<AetheriaRuntimeDaemonSoaViewDocument> _daemonSoaView", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("CultMeshReactiveDocument<AetheriaRuntimeZoneRenderDocument> _zoneRender", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("_daemonFrame ??= state.ReactiveDaemonFrame();", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("_daemonSoaView ??= state.ReactiveDaemonSoaView();", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("_zoneRender ??= state.ReactiveZoneRender();", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("AetheriaRuntimeDaemonRenderView.TryCreateCurrent(", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("ResolveClient()?.State", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains(".ReadAsync(client.State)", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("AetheriaRuntimeReactiveObservedDaemonState", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains(".ReactiveObservedDaemon()", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("reactive.TryCurrent(out var observed)", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("AetheriaRuntimeObservedDaemonSession", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains(".ObserveDaemon()", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("return new AetheriaRuntimeDaemonRenderView(frame, soaView, zoneRender);", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("DisposeObservedDaemonSession()", StringComparison.Ordinal) ||
|
|
daemonObserver.Contains("DisposeReactiveObservedDaemonDocuments()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaDaemonObserver still routes through observed-daemon compatibility wrappers instead of direct reactive documents.");
|
|
}
|
|
|
|
if (daemonObserver.Contains("ObserveAsync()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaDaemonObserver still reads observed daemon state through the AetheriaClient compatibility helper.");
|
|
}
|
|
|
|
if (!mainMenu.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) ||
|
|
!mainMenu.Contains("ResolveMainMenuSurface(string surfaceId)", StringComparison.Ordinal) ||
|
|
!mainMenu.Contains(".RuntimeState(stateBoot, \"unity-main-menu\")", StringComparison.Ordinal) ||
|
|
!mainMenu.Contains(".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", StringComparison.Ordinal) ||
|
|
!mainMenu.Contains(".Latest()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu must read menu state through provider-native managed typed documents.");
|
|
}
|
|
|
|
if (mainMenu.Contains("PrepareDocumentAccess(stateBoot)", StringComparison.Ordinal) ||
|
|
mainMenu.Contains(".ReactiveSectorMap(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains(".ReactivePlayerSettingsDocument(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains(".ReactiveVerseHostSettings(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still owns reactive document handles instead of sampling named current typed documents.");
|
|
}
|
|
|
|
if (mainMenu.Contains("AetheriaRuntimeStateReader.TryReadDaemonFrame", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still reads daemon frames through the runtime file reader instead of managed typed documents.");
|
|
}
|
|
|
|
if (mainMenu.Contains("OpenRuntimeCatalog(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still names runtime catalog access as a raw catalog open instead of a managed latest document read.");
|
|
}
|
|
|
|
if (mainMenu.Contains("LatestRuntimeCatalog(", StringComparison.Ordinal) ||
|
|
mainMenu.Contains("AetheriaUnityObservedRunProjection.Project(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still owns gameplay catalog/sector projection instead of leaving observed-scene boot to AetheriaUnityGameplayBootShell.");
|
|
}
|
|
|
|
var requiredGameplayBootSymbols = new[]
|
|
{
|
|
"AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)",
|
|
"runtimeState.Catalog.Latest()",
|
|
"runtimeState.SectorMap.Latest()",
|
|
"Galaxy.ProjectObservedSectorMap(",
|
|
"sectorMap.IsTutorial",
|
|
"sectorMap.GenerationSeed",
|
|
"new AetheriaUnityGameplayBootResult(",
|
|
"ObservedGalaxy"
|
|
};
|
|
var missingGameplayBootSymbols = requiredGameplayBootSymbols
|
|
.Where(symbol => !gameplayBootShell.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingGameplayBootSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityGameplayBootShell must own managed catalog/sector-map projection for the observed gameplay scene: " +
|
|
string.Join(", ", missingGameplayBootSymbols));
|
|
}
|
|
|
|
if (gameplayBootShell.Contains(".ReactiveCatalogSnapshot()", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("runtimeCatalogDocument.Current", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains(".ReactiveSectorMap()", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("sectorMapDocument.Current", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentCatalog(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) ||
|
|
gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentSectorMap(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityGameplayBootShell should use the managed runtime state handle for one-shot gameplay boot catalog/sector-map access.");
|
|
}
|
|
|
|
var observedRunProjectionPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedRunProjection.cs");
|
|
if (File.Exists(observedRunProjectionPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaUnityObservedRunProjection is legacy projection chaff; gameplay boot should project the managed sector-map document directly.");
|
|
}
|
|
|
|
var forbiddenMainMenuReaderSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeStateReader.ReadPlayerSettings",
|
|
"AetheriaRuntimeStateReader.ReadVerseHostSettings"
|
|
};
|
|
var mainMenuReaderHits = forbiddenMainMenuReaderSymbols
|
|
.Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (mainMenuReaderHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MainMenu still reads typed state through the runtime file reader instead of the shared Aetheria client facade: " +
|
|
string.Join(", ", mainMenuReaderHits));
|
|
}
|
|
|
|
if (!eveSurfacePresenter.Contains(".RequireRuntimeState(stateBoot, \"unity-eve-surface-presenter\")", StringComparison.Ordinal) ||
|
|
!eveSurfacePresenter.Contains(".EveSurfaceDocument(surfaceId)", StringComparison.Ordinal) ||
|
|
!eveSurfacePresenter.Contains("?.Reactive()", StringComparison.Ordinal) ||
|
|
!eveSurfacePresenter.Contains("ReadDaemonSurface(stateBoot)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve surface presenter must grab the provider RuntimeState and read the daemon surface through AetheriaClientState reactive typed documents.");
|
|
}
|
|
|
|
if (!eveSurfacePresenter.Contains("AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver(", StringComparison.Ordinal) ||
|
|
!eveUnitySurfaceHost.Contains("AetheriaEveRuntimeUnityHooks.TryCreateDefaultStateRefResolver()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity Eve surface lowering must resolve provider state refs through provider-native managed AetheriaClientState documents.");
|
|
}
|
|
|
|
if (eveSurfacePresenter.Contains("AetheriaUnityRuntimeClientProvider.ResolveClient(", StringComparison.Ordinal) ||
|
|
eveSurfacePresenter.Contains("client.State.CreateEveSurfaceCultMeshStateRefResolver()", StringComparison.Ordinal) ||
|
|
eveSurfacePresenter.Contains("ResolveClient(statePath).State.CreateEveSurfaceCultMeshStateRefResolver()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Aetheria Eve surface presenter still peels through the raw client instead of using provider-native state/ref handles.");
|
|
}
|
|
|
|
if (eveSurfacePresenter.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceStateRefResolver", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity Eve surface lowering still resolves state refs through the file reader instead of the shared AetheriaClient facade.");
|
|
}
|
|
|
|
if (eveUnitySurfaceHost.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceStateRefResolver", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity Eve surface host still resolves default state refs through the file reader instead of the managed runtime client provider.");
|
|
}
|
|
|
|
if (!aetheriaClientState.Contains("AetheriaRuntimeStateRefResolver.CreateEveSurfaceCultMeshStateRefResolver(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Managed client state no longer resolves Eve state refs through the typed runtime state-ref resolver.");
|
|
}
|
|
|
|
if (aetheriaClientState.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceCultMeshStateRefResolver", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceStateRefResolver", StringComparison.Ordinal) ||
|
|
aetheriaClientState.Contains("public Func<string, string> CreateEveSurfaceStateRefResolver()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Managed client state still routes or exposes Eve state-ref resolution through file-backed/delegate compatibility.");
|
|
}
|
|
|
|
var forbiddenDirectStoreSymbols = new Dictionary<string, string[]>
|
|
{
|
|
[actionGameManagerPath] = new[]
|
|
{
|
|
"AetheriaRuntimeCatalogStore.ReadPlayerSettings",
|
|
"AetheriaRuntimeCatalogStore.ReadLoadoutTemplates",
|
|
"AetheriaRuntimeCatalogStore.OpenReadOnly",
|
|
"AetheriaRuntimeCatalogStore.ReadZoneStates",
|
|
"AetheriaRuntimeCatalogStore.ReadEntitySnapshots"
|
|
},
|
|
[mainMenuPath] = new[]
|
|
{
|
|
"AetheriaRuntimeCatalogStore.ReadRunStates",
|
|
"AetheriaRuntimeCatalogStore.ReadVerseHostSettings"
|
|
},
|
|
[eveSurfacePresenterPath] = new[]
|
|
{
|
|
"AetheriaRuntimeCatalogStore.ReadEveSurfaces"
|
|
}
|
|
};
|
|
|
|
var directStoreHits = forbiddenDirectStoreSymbols
|
|
.SelectMany(entry =>
|
|
{
|
|
var source = File.ReadAllText(entry.Key);
|
|
return entry.Value
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, entry.Key)} -> {symbol}");
|
|
})
|
|
.ToArray();
|
|
|
|
if (directStoreHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity gameplay/UI still reads typed state directly from the raw store instead of the shared runtime state reader: " +
|
|
string.Join("; ", directStoreHits));
|
|
}
|
|
|
|
var unitySurfaceLowererFiles = Directory
|
|
.EnumerateFiles(Path.Combine(root, "Assets", "Scripts", "UI"), "*.cs", SearchOption.AllDirectories)
|
|
.ToArray();
|
|
var forbiddenLocalSurfaceAdapterSymbols = new[]
|
|
{
|
|
"private static EveSurfaceDocument ToEveSurfaceDocument(AetheriaRuntimeSurfaceDocument document)",
|
|
"private static EveSurfaceComponent ToEveSurfaceComponent(AetheriaRuntimeSurfaceComponent component)",
|
|
"new EveSurfaceDocument(\r\n \"surface-state\"",
|
|
"new EveCommandTemplate(command.Command, command.Label, command.Transport)"
|
|
};
|
|
var localSurfaceAdapterHits = unitySurfaceLowererFiles
|
|
.SelectMany(path =>
|
|
{
|
|
var source = File.ReadAllText(path);
|
|
return forbiddenLocalSurfaceAdapterSymbols
|
|
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{Path.GetRelativePath(root, path)} -> {symbol}");
|
|
})
|
|
.ToArray();
|
|
if (localSurfaceAdapterHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity UI lowerers still duplicate runtime-to-Eve surface conversion instead of using the shared runtime surface documents: " +
|
|
string.Join("; ", localSurfaceAdapterHits));
|
|
}
|
|
}
|
|
|
|
static void RequireRuntimeSimulationTuningRequests(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 inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var inventoryMenu = File.Exists(inventoryMenuPath)
|
|
? File.ReadAllText(inventoryMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify runtime simulation tuning authority; InventoryMenu.cs is missing.");
|
|
|
|
var requiredInventoryRequestSymbols = new[]
|
|
{
|
|
"private void RequestEntityShutdownPerformance(string targetEntityKey, float shutdownPerformance)",
|
|
"private void RequestEquippedItemOverrideShutdown(EquippedItem item, bool enabled)",
|
|
"private void RequestThermotoggleTargetTemperature(",
|
|
"operations => operations.SetShutdownPerformance(",
|
|
"operations => operations.SetItemOverrideShutdown(",
|
|
"operations => operations.SetThermotoggleTargetTemperature("
|
|
};
|
|
|
|
var missingInventoryRequestSymbols = requiredInventoryRequestSymbols
|
|
.Where(symbol => !inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingInventoryRequestSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Runtime simulation tuning request methods are missing from InventoryMenu's typed daemon operation boundary: " +
|
|
string.Join(", ", missingInventoryRequestSymbols));
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
var shutdownRequest = inventoryMenu.IndexOf("RequestEntityShutdownPerformance(", StringComparison.Ordinal);
|
|
var staleShipSettingsRender = inventoryMenu.IndexOf("RenderCurrentShipSettingsSurface(entity);", shutdownRequest, StringComparison.Ordinal);
|
|
if (shutdownRequest >= 0 && staleShipSettingsRender >= 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still redraws ship settings immediately after submitting daemon shutdown-performance requests.");
|
|
}
|
|
|
|
var normalizedInventoryMenu = inventoryMenu.Replace("\r\n", "\n", StringComparison.Ordinal);
|
|
var forbiddenEquippedItemTuningRedraws = new[]
|
|
{
|
|
"GameManager.RequestEquippedItemOverrideShutdown(item, !item.EquippableItem.OverrideShutdown);\n RenderEquippedItemDetailsSurface(item);",
|
|
"GameManager.RequestThermotoggleTargetTemperature(thermotoggle, command.TargetTemperature);\n RenderEquippedItemDetailsSurface(item);"
|
|
};
|
|
var equippedItemTuningRedrawHits = forbiddenEquippedItemTuningRedraws
|
|
.Where(symbol => normalizedInventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (equippedItemTuningRedrawHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still redraws equipped-item tuning surfaces immediately after daemon submission.");
|
|
}
|
|
|
|
var forbiddenUnityAcceptanceSymbols = new[]
|
|
{
|
|
"entity?.Settings == null",
|
|
"entity.Settings == null"
|
|
};
|
|
var unityAcceptanceHits = FindMethodScopedLineHits(actionGameManager, forbiddenUnityAcceptanceSymbols)
|
|
.Where(hit => hit.MethodName == "RequestEntityShutdownPerformance")
|
|
.Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}")
|
|
.ToArray();
|
|
if (unityAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity runtime tuning requests still reject through renderer-local settings checks instead of daemon authority: " +
|
|
string.Join(", ", unityAcceptanceHits));
|
|
}
|
|
|
|
var daemonOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs");
|
|
var daemonOperations = File.Exists(daemonOperationsPath)
|
|
? File.ReadAllText(daemonOperationsPath)
|
|
: throw new InvalidOperationException("Cannot verify runtime simulation tuning authority; AetheriaRuntimeDaemonOperations.cs is missing.");
|
|
var requiredDaemonValidationSymbols = new[]
|
|
{
|
|
"case AetheriaRuntimeDaemonCommandKinds.SetShutdownPerformance:",
|
|
"command.ScalarValue < 0.0 || command.ScalarValue > 1.0"
|
|
};
|
|
var missingDaemonValidationSymbols = requiredDaemonValidationSymbols
|
|
.Where(symbol => !daemonOperations.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonValidationSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon runtime tuning no longer validates shutdown-performance range: " +
|
|
string.Join(", ", missingDaemonValidationSymbols));
|
|
}
|
|
}
|
|
|
|
static void RequireHullConductivityRequestAuthority(string root)
|
|
{
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var inventoryPanel = File.Exists(inventoryPanelPath)
|
|
? File.ReadAllText(inventoryPanelPath)
|
|
: throw new InvalidOperationException("Cannot verify hull conductivity authority; InventoryPanel.cs is missing.");
|
|
var daemonOperationClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationClient.cs");
|
|
var aetheriaControlPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaControl.cs");
|
|
var daemonOperationClient = File.Exists(daemonOperationClientPath)
|
|
? File.ReadAllText(daemonOperationClientPath)
|
|
: throw new InvalidOperationException("Cannot verify hull conductivity authority; daemon operation client is missing.");
|
|
var aetheriaControl = File.Exists(aetheriaControlPath)
|
|
? File.ReadAllText(aetheriaControlPath)
|
|
: throw new InvalidOperationException("Cannot verify hull conductivity authority; AetheriaControl.cs is missing.");
|
|
|
|
if (!daemonOperationClient.Contains("internal AetheriaRuntimeDaemonCommandEnvelope ToggleHullConductivity(", StringComparison.Ordinal) ||
|
|
!daemonOperationClient.Contains("AetheriaRuntimeDaemonCommandKinds.ToggleHullConductivity", StringComparison.Ordinal) ||
|
|
!aetheriaControl.Contains("public CultMeshOperationReceipt ToggleHullConductivity(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Hull conductivity no longer has a typed daemon request primitive.");
|
|
}
|
|
|
|
if (inventoryPanel.Contains("RequestHullConductivityToggle", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("ToggleHullConductivity", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel must not own hull conductivity cell-click authority; thermal pipe edits belong to the daemon-presented inventory surface.");
|
|
}
|
|
|
|
var forbiddenUnityAcceptanceSymbols = new[]
|
|
{
|
|
"entity?.HullConductivity == null",
|
|
"position.x >= entity.HullConductivity.GetLength(0)",
|
|
"position.y >= entity.HullConductivity.GetLength(1)",
|
|
"axis < 0",
|
|
"axis > 1"
|
|
};
|
|
var unityAcceptanceHits = forbiddenUnityAcceptanceSymbols
|
|
.Where(symbol => inventoryPanel.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (unityAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity hull conductivity requests still reject through renderer-local grid bounds instead of daemon authority: " +
|
|
string.Join(", ", unityAcceptanceHits));
|
|
}
|
|
|
|
if (inventoryPanel.Contains("public bool RequestHullConductivityToggle(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Hull conductivity request API still exposes submission as public acceptance state.");
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
var submissionAcceptanceHits = 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("if (GameManager.RequestHullConductivityToggle", StringComparison.Ordinal) ||
|
|
line.Line.Contains("if (RequestHullConductivityToggle", StringComparison.Ordinal) ||
|
|
line.Line.Contains("RefreshCells(new []{v,int2", StringComparison.Ordinal))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (submissionAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Hull conductivity UI still treats daemon submission as accepted cell state: " +
|
|
string.Join("; ", submissionAcceptanceHits));
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryEntityRenameRequestAuthority(string root)
|
|
{
|
|
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("RequestEntityName", StringComparison.Ordinal) ||
|
|
!inventoryPanel.Contains("operations => operations.SetEntityName", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Entity rename no longer has a typed daemon request primitive.");
|
|
}
|
|
|
|
if (inventoryPanel.Contains("_displayedEntity.Name =", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still renames entities directly instead of using the typed daemon request primitive.");
|
|
}
|
|
|
|
if (inventoryPanel.Contains("GameManager.RequestEntityName", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("InventoryPanel still routes entity rename through ActionGameManager instead of its typed operation client.");
|
|
}
|
|
|
|
var renameRequest = inventoryPanel.IndexOf("RequestEntityName(", StringComparison.Ordinal);
|
|
var titleRefreshAfterRename = renameRequest < 0
|
|
? -1
|
|
: inventoryPanel.IndexOf("Title.text = _displayedEntity.Name", renameRequest, StringComparison.Ordinal);
|
|
if (titleRefreshAfterRename >= 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still refreshes entity title immediately after submitting a daemon rename request.");
|
|
}
|
|
}
|
|
|
|
static void RequireWeaponGroupRequestAuthority(string root)
|
|
{
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var inventoryMenu = File.Exists(inventoryMenuPath)
|
|
? File.ReadAllText(inventoryMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify weapon group authority; InventoryMenu.cs is missing.");
|
|
|
|
if (!inventoryMenu.Contains("RequestWeaponGroupMembership", StringComparison.Ordinal) ||
|
|
!inventoryMenu.Contains("operations => operations.SetWeaponGroupMembership", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Weapon group membership no longer has a typed daemon request primitive.");
|
|
}
|
|
|
|
if (inventoryMenu.Contains("WeaponGroupDragObject", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Weapon-group action-bar binding still keeps the dead drag-object path alive instead of routing through live gameplay APIs.");
|
|
}
|
|
|
|
var forbiddenLocalAcceptanceSymbols = new[]
|
|
{
|
|
"item?.Entity?.WeaponGroups == null",
|
|
"groupIndex >= item.Entity.WeaponGroups.Length"
|
|
};
|
|
var localAcceptanceHits = forbiddenLocalAcceptanceSymbols
|
|
.Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (localAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity weapon-group membership still rejects through renderer-local weapon/group checks instead of daemon acceptance: " +
|
|
string.Join(", ", localAcceptanceHits));
|
|
}
|
|
|
|
var forbiddenPublicAcceptanceApis = new[]
|
|
{
|
|
"public bool RequestWeaponGroupMembership("
|
|
};
|
|
var publicAcceptanceApiHits = forbiddenPublicAcceptanceApis
|
|
.Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (publicAcceptanceApiHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Weapon-group request APIs still expose submission as public acceptance state: " +
|
|
string.Join(", ", publicAcceptanceApiHits));
|
|
}
|
|
|
|
var forbiddenUiSubmissionAcceptanceSymbols = new[]
|
|
{
|
|
"if (GameManager.RequestWeaponGroupMembership(",
|
|
"Unable to toggle equipped-item weapon group membership."
|
|
};
|
|
var uiSubmissionAcceptanceHits = forbiddenUiSubmissionAcceptanceSymbols
|
|
.Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (uiSubmissionAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still treats weapon-group membership submission as accepted equipment state: " +
|
|
string.Join(", ", uiSubmissionAcceptanceHits));
|
|
}
|
|
|
|
var assignmentPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "WeaponGroupAssignment.cs");
|
|
var elementPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "WeaponGroupElement.cs");
|
|
var weaponGroupsPrefabPath = Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Fancy", "Weapon Groups.prefab");
|
|
var weaponGroupPrefabPath = Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Fancy", "Weapon Group.prefab");
|
|
|
|
var survivingLegacyPaths = new[]
|
|
{
|
|
assignmentPath,
|
|
elementPath,
|
|
weaponGroupsPrefabPath,
|
|
weaponGroupPrefabPath
|
|
}
|
|
.Where(File.Exists)
|
|
.ToArray();
|
|
|
|
if (survivingLegacyPaths.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Legacy inventory weapon-group uGUI shells still survive after the Eve surface cut: " +
|
|
string.Join(", ", survivingLegacyPaths.Select(path => Path.GetRelativePath(root, path))));
|
|
}
|
|
}
|
|
|
|
static void RequireActionBarBindingRequestAuthority(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 action-bar binding authority; ActionGameManager.cs is missing.");
|
|
var actionBarPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityActionBarPresentation.cs");
|
|
var actionBarPresentation = File.Exists(actionBarPresentationPath)
|
|
? File.ReadAllText(actionBarPresentationPath)
|
|
: throw new InvalidOperationException("Cannot verify action-bar binding authority; AetheriaUnityActionBarPresentation.cs is missing.");
|
|
var gameplayInputShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayInputShell.cs");
|
|
var gameplayInputShell = File.Exists(gameplayInputShellPath)
|
|
? File.ReadAllText(gameplayInputShellPath)
|
|
: throw new InvalidOperationException("Cannot verify action-bar binding authority; AetheriaUnityGameplayInputShell.cs is missing.");
|
|
var gameplaySceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs");
|
|
var gameplaySceneWiring = File.Exists(gameplaySceneWiringPath)
|
|
? File.ReadAllText(gameplaySceneWiringPath)
|
|
: throw new InvalidOperationException("Cannot verify action-bar binding authority; AetheriaUnityGameplaySceneWiring.cs is missing.");
|
|
|
|
var requiredManagerSymbols = new[]
|
|
{
|
|
"ActionBarPresentation = _actionBarPresentation",
|
|
"SceneWiring.ConfigureActionBarPresentation(",
|
|
"ApplyActionBarBindings = _ => _actionBarPresentation?.ApplyLocalBindings()"
|
|
};
|
|
|
|
var missingManagerSymbols = requiredManagerSymbols
|
|
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingManagerSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager no longer delegates action-bar binding request/restore through the action-bar presentation owner: " +
|
|
string.Join(", ", missingManagerSymbols));
|
|
}
|
|
|
|
if (actionGameManager.Contains("AetheriaUnityActionBarBindingAdapter", StringComparison.Ordinal) ||
|
|
File.Exists(Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityActionBarBindingAdapter.cs")))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Action-bar binding restore must go straight to AetheriaUnityActionBarPresentation; the old adapter owned no authority.");
|
|
}
|
|
|
|
var requiredSceneWiringSymbols = new[]
|
|
{
|
|
"public void ConfigureActionBarPresentation(",
|
|
"gameplayInputShell?.ActionBarSlots ?? Array.Empty<ActionBarSlot>()",
|
|
"actionBarPresentation?.Bind(",
|
|
"resolveActionBarControl);"
|
|
};
|
|
var missingSceneWiringSymbols = requiredSceneWiringSymbols
|
|
.Where(symbol => !gameplaySceneWiring.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSceneWiringSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity scene wiring no longer injects action-bar slots into the action-bar presentation owner: " +
|
|
string.Join(", ", missingSceneWiringSymbols));
|
|
}
|
|
|
|
var requiredGameplayInputShellSymbols = new[]
|
|
{
|
|
"private void RegisterActionBarInput()",
|
|
"private void CreateActionBarSlot(string controlPath)",
|
|
"var action = new InputAction(binding: controlPath);",
|
|
"_actionBarActions.Add(action);",
|
|
"_actionBarSlots.Add(slot);",
|
|
"DragSession.RegisterTarget(dragAction => ActionBarPresentation.RequestBinding(slot, dragAction));",
|
|
"slot.PointerExitTrigger.OnPointerExitAsObservable().Subscribe(_ => DragSession.UnregisterTarget());"
|
|
};
|
|
var missingGameplayInputShellSymbols = requiredGameplayInputShellSymbols
|
|
.Where(symbol => !gameplayInputShell.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingGameplayInputShellSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity gameplay input shell no longer owns local action-bar control binding and drag target wiring: " +
|
|
string.Join(", ", missingGameplayInputShellSymbols));
|
|
}
|
|
|
|
var requiredPresentationSymbols = new[]
|
|
{
|
|
"public void RequestBinding(ActionBarSlot slot, DragObject dragAction)",
|
|
"public bool RequestWeaponGroupBinding(int slotIndex, int groupIndex)",
|
|
"public bool ClearBinding(int slotIndex)",
|
|
"public void ApplyLocalBindings()",
|
|
"public void ApplyBindings(",
|
|
"private AetheriaUnityActionBarBinding CreateBindingCommit(",
|
|
"private ActionBarBinding CreateBinding(",
|
|
"private void SetLocalBinding(AetheriaUnityActionBarBinding binding)",
|
|
"_localBindings = (_localBindings ?? Array.Empty<AetheriaUnityActionBarBinding>())",
|
|
"SetLocalBinding(binding);",
|
|
"ApplyLocalBindings();"
|
|
};
|
|
|
|
var missingPresentationSymbols = requiredPresentationSymbols
|
|
.Where(symbol => !actionBarPresentation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingPresentationSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Action-bar presentation no longer owns typed daemon drag/drop binding requests and observed restore lowering: " +
|
|
string.Join(", ", missingPresentationSymbols));
|
|
}
|
|
|
|
var forbiddenPresentationSymbols = new[]
|
|
{
|
|
"_resolveClient()?.Operations.SetActionBarBinding(",
|
|
"_resolveClient()?.Operations.ClearActionBarBinding(",
|
|
"Failed to send Aetheria daemon action-bar binding operation",
|
|
"Failed to send Aetheria daemon action-bar clear operation"
|
|
};
|
|
var presentationHits = forbiddenPresentationSymbols
|
|
.Where(symbol => actionBarPresentation.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (presentationHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Action-bar presentation still treats local input bindings as daemon gameplay operations: " +
|
|
string.Join(", ", presentationHits));
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"var newbinds = Enumerable.Range(0, 64)",
|
|
".Zip(\r\n bindings,",
|
|
"groupIndex >= CurrentEntity.WeaponGroups.Length",
|
|
"ApplyDefaultActionBarBindings",
|
|
"ProjectActionBarBindings",
|
|
"ProjectActionBarBinding(slot, slot?.Binding)",
|
|
"Enumerable.Range(0, CurrentEntity.WeaponGroups.Length)",
|
|
"slot == null || groupIndex < 0",
|
|
"RequestActionBarBinding(",
|
|
"CreateActionBarBinding(",
|
|
"CreateActionBarBindingCommit(",
|
|
"ResolveActionBarClient()?.Operations.SetActionBarBinding("
|
|
};
|
|
|
|
var legacyHits = forbiddenSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (legacyHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Action-bar binding still contains the old unconditional weapon-group overwrite path: " +
|
|
string.Join(", ", legacyHits));
|
|
}
|
|
|
|
var forbiddenPublicAcceptanceApis = new[]
|
|
{
|
|
"public bool RequestWeaponGroupActionBarBinding(",
|
|
"public bool RequestClearActionBarBinding(",
|
|
"TryRequestDaemonActionBarBinding(",
|
|
"TryRequestDaemonActionBarBindingClear(",
|
|
"public bool TryRequestDaemonActionBarConsumable(",
|
|
"public bool TryRequestDaemonActionBarBehavior(",
|
|
"public bool TryRequestDaemonActionBarWeaponGroup("
|
|
};
|
|
var publicAcceptanceApiHits = forbiddenPublicAcceptanceApis
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (publicAcceptanceApiHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionBar binding request APIs still expose submission as public acceptance state: " +
|
|
string.Join(", ", publicAcceptanceApiHits));
|
|
}
|
|
|
|
var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs");
|
|
var inventoryMenu = File.Exists(inventoryMenuPath)
|
|
? File.ReadAllText(inventoryMenuPath)
|
|
: throw new InvalidOperationException("Cannot verify action-bar binding authority; InventoryMenu.cs is missing.");
|
|
var requiredInventoryActionBarSymbols = new[]
|
|
{
|
|
"SetActionBarPresentation(AetheriaUnityActionBarPresentation actionBarPresentation)"
|
|
};
|
|
var missingInventoryActionBarSymbols = requiredInventoryActionBarSymbols
|
|
.Where(symbol => !inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingInventoryActionBarSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu no longer owns typed equipped-item action-bar binding operations: " +
|
|
string.Join(", ", missingInventoryActionBarSymbols));
|
|
}
|
|
var forbiddenInventoryActionBarSymbols = new[]
|
|
{
|
|
"ProjectEquippedItemActionBarSlots(",
|
|
"RequestWeaponGroupActionBarBinding(",
|
|
"RequestClearActionBarBinding(",
|
|
"_actionBarPresentation?.RequestWeaponGroupBinding(slotIndex, groupIndex)",
|
|
"_actionBarPresentation?.ClearBinding(slotIndex)",
|
|
"operations => operations.SetActionBarBinding(",
|
|
"operations => operations.ClearActionBarBinding(controlPath)",
|
|
"SetActionBarBinding(",
|
|
"ClearActionBarBinding(controlPath)"
|
|
};
|
|
var inventoryActionBarHits = forbiddenInventoryActionBarSymbols
|
|
.Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (inventoryActionBarHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still sends local action-bar binding edits to the daemon: " +
|
|
string.Join(", ", inventoryActionBarHits));
|
|
}
|
|
var forbiddenUiAcceptanceSymbols = new[]
|
|
{
|
|
"GameManager.RequestWeaponGroupActionBarBinding(command.SlotIndex, command.GroupIndex))",
|
|
"GameManager.RequestClearActionBarBinding(command.SlotIndex))",
|
|
"Unable to bind equipped-item weapon group to action bar.",
|
|
"Unable to clear equipped-item action bar binding."
|
|
};
|
|
var uiAcceptanceHits = forbiddenUiAcceptanceSymbols
|
|
.Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (uiAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryMenu still treats equipped-item action-bar command submission as accepted binding state: " +
|
|
string.Join(", ", uiAcceptanceHits));
|
|
}
|
|
|
|
var actionBarSlotPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionBarSlot.cs");
|
|
var actionBarSlot = File.Exists(actionBarSlotPath)
|
|
? File.ReadAllText(actionBarSlotPath)
|
|
: throw new InvalidOperationException("Cannot verify action-bar activation authority; ActionBarSlot.cs is missing.");
|
|
var forbiddenSlotSymbols = new[]
|
|
{
|
|
"TryRequestDaemonActionBarConsumable(",
|
|
"TryRequestDaemonActionBarBehavior(",
|
|
"TryRequestDaemonActionBarWeaponGroup("
|
|
};
|
|
var slotHits = forbiddenSlotSymbols
|
|
.Where(symbol => actionBarSlot.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (slotHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionBarSlot still calls daemon transport helpers directly instead of submit-only request APIs: " +
|
|
string.Join(", ", slotHits));
|
|
}
|
|
|
|
if (actionBarSlot.Contains("ActionGameManager.Instance", StringComparison.Ordinal) ||
|
|
!actionBarSlot.Contains("protected GameSettings Settings { get; }", StringComparison.Ordinal) ||
|
|
!actionBarPresentation.Contains("new ActionBarGearBinding(entity, slot, _resolveControl, _settings,", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionBarSlot must receive icon presentation settings explicitly and submit through a narrow control handle.");
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryDoubleClickTransferRequestAuthority(string root)
|
|
{
|
|
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 actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
if (!File.Exists(actionGameManagerPath))
|
|
{
|
|
throw new InvalidOperationException("Cannot verify inventory transfer authority; ActionGameManager.cs is missing.");
|
|
}
|
|
|
|
var operationClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationClient.cs");
|
|
var operationClient = File.Exists(operationClientPath)
|
|
? File.ReadAllText(operationClientPath)
|
|
: throw new InvalidOperationException("Cannot verify inventory transfer authority; AetheriaRuntimeDaemonOperationClient.cs is missing.");
|
|
|
|
var panelSurfacePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonGameSurfaceBuilder.cs");
|
|
var panelSurface = File.Exists(panelSurfacePath)
|
|
? File.ReadAllText(panelSurfacePath)
|
|
: throw new InvalidOperationException("Cannot verify inventory transfer authority; AetheriaRuntimeDaemonGameSurfaceBuilder.cs is missing.");
|
|
var surfaceCatalogPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveSurfaceCatalog.cs");
|
|
var surfaceCatalog = File.Exists(surfaceCatalogPath)
|
|
? File.ReadAllText(surfaceCatalogPath)
|
|
: throw new InvalidOperationException("Cannot verify inventory transfer authority; AetheriaRuntimeEveSurfaceCatalog.cs is missing.");
|
|
|
|
var requiredDaemonRequests = new[]
|
|
{
|
|
"TransferCargoItem(",
|
|
"EquipItem(",
|
|
"StoreItem(",
|
|
"AetheriaRuntimeDaemonCommandKinds.TransferCargoItem",
|
|
"AetheriaRuntimeDaemonCommandKinds.EquipItem",
|
|
"AetheriaRuntimeDaemonCommandKinds.StoreItem"
|
|
};
|
|
|
|
var missingDaemonRequests = requiredDaemonRequests
|
|
.Where(symbol => !operationClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingDaemonRequests.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory transfer no longer has complete typed daemon operation primitives: " +
|
|
string.Join(", ", missingDaemonRequests));
|
|
}
|
|
|
|
var requiredSurfaceSymbols = new[]
|
|
{
|
|
"DockedRefitPanel(",
|
|
"\"inventory.grid\"",
|
|
"\"inventory.item\"",
|
|
"\"itemKey\"",
|
|
"\"sourceKind\"",
|
|
"\"sourceEntityKey\"",
|
|
"\"sourceIndex\"",
|
|
"\"targetKind\"",
|
|
"\"targetEntityKey\"",
|
|
"\"targetIndex\"",
|
|
"\"dropCommand.cargo\"",
|
|
"\"dropCommand.equipment\"",
|
|
"\"x\"",
|
|
"\"y\"",
|
|
"\"quantity\"",
|
|
"SurfaceCommand(AetheriaRuntimeDaemonCommandKinds.TransferCargoItem)",
|
|
"SurfaceCommand(AetheriaRuntimeDaemonCommandKinds.EquipItem)",
|
|
"SurfaceCommand(AetheriaRuntimeDaemonCommandKinds.StoreItem)"
|
|
};
|
|
var missingSurfaceSymbols = requiredSurfaceSymbols
|
|
.Where(symbol => !panelSurface.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingSurfaceSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory panel surface must publish typed item identity and command transport data through CultUI: " +
|
|
string.Join(", ", missingSurfaceSymbols));
|
|
}
|
|
if (surfaceCatalog.Contains(
|
|
"Surface(AetheriaRuntimeInventoryPanelSurfaceBuilder.SurfaceId",
|
|
StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"The request-local Unity inventory panel is still advertised as a player gameplay surface.");
|
|
}
|
|
|
|
var forbiddenUnityInventoryAcceptanceSymbols = new[]
|
|
{
|
|
"TryFindSpace(",
|
|
"ItemFits(",
|
|
"Cargo.ContainsKey(",
|
|
"Equipment.Contains(",
|
|
"origin == destination",
|
|
"ReferenceEquals(equippedItem.Entity, origin)",
|
|
"!origin.Cargo.TryGetValue(item, out var sourcePosition)",
|
|
"origin.Cargo.TryGetValue(item, out var originPosition)",
|
|
"TryResolveManagedInventoryRows(",
|
|
"TryValidateTypedCargoSlot(",
|
|
"TryValidateTypedEquipmentSlot(",
|
|
"RuntimeState.Inventory(entityIndex).Latest()",
|
|
"RequestCargoItemTransfer",
|
|
"RequestCargoItemEquip",
|
|
"RequestEquippedItemTransfer",
|
|
"RequestEquippedItemStore",
|
|
"RequestDraggedItemToEntity",
|
|
"RequestDraggedItemToCargo",
|
|
"InventoryCargoEventData",
|
|
"InventoryEntityEventData",
|
|
"GearOccupancy[",
|
|
"Occupancy[",
|
|
"CellInstances[",
|
|
"OnClickAsObservable().Subscribe"
|
|
};
|
|
var inventoryTransferSources = inventoryPanel + "\n" + inventoryMenu;
|
|
var unityInventoryAcceptanceHits = forbiddenUnityInventoryAcceptanceSymbols
|
|
.Where(symbol => inventoryTransferSources.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (unityInventoryAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity inventory UI still owns inventory cell interpretation, transfer validation, or transfer submission: " +
|
|
string.Join(", ", unityInventoryAcceptanceHits));
|
|
}
|
|
|
|
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 forbiddenInventoryUiAcceptanceSymbols = new[]
|
|
{
|
|
".ItemFits(",
|
|
".TryFindSpace("
|
|
};
|
|
var inventoryUiAcceptanceHits = File.ReadLines(inventoryPanelPath)
|
|
.Select((line, index) => new { LineNumber = index + 1, Line = line })
|
|
.Where(line => forbiddenInventoryUiAcceptanceSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, inventoryPanelPath)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
if (inventoryUiAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still rejects transfer placement through renderer-local fit checks instead of daemon authority: " +
|
|
string.Join("; ", inventoryUiAcceptanceHits));
|
|
}
|
|
|
|
var forbiddenUnknownOriginSymbols = new[] { "int.MinValue" };
|
|
var unknownOriginHits = new[] { inventoryMenuPath, inventoryPanelPath }
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenUnknownOriginSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
if (unknownOriginHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory cargo/equipment submissions still send unknown origin coordinates instead of typed slot positions: " +
|
|
string.Join("; ", unknownOriginHits));
|
|
}
|
|
|
|
var forbiddenManagerCommandTargetSymbols = new[]
|
|
{
|
|
"TryResolveObservedCargoBayCommandTarget",
|
|
"TryResolveObservedEquippedItemCommandTarget",
|
|
"TryResolveCargoBayCommandTarget",
|
|
"TryResolveEquippedItemCommandTarget"
|
|
};
|
|
var managerCommandTargetHits = new[] { actionGameManagerPath, inventoryMenuPath, inventoryPanelPath }
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenManagerCommandTargetSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
if (managerCommandTargetHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory cargo/equipment submissions must not depend on ActionGameManager command-target adapters: " +
|
|
string.Join("; ", managerCommandTargetHits));
|
|
}
|
|
|
|
var forbiddenPublicAcceptanceApis = new[]
|
|
{
|
|
"public bool RequestCargoItemTransfer(",
|
|
"public bool RequestCargoItemEquip(",
|
|
"public bool RequestEquippedItemStore(",
|
|
"public bool RequestEquippedItemEquip(",
|
|
"private bool RequestDraggedItemToEntity(",
|
|
"private bool RequestDraggedItemToCargo(",
|
|
"private bool RequestCargoItemTransfer(",
|
|
"private bool RequestEquippedItemTransfer(",
|
|
"public bool EndDrag(",
|
|
"Func<DragObject, bool>",
|
|
"RegisterDragTarget(Func<DragObject, bool>"
|
|
};
|
|
var publicAcceptanceApiHits = new[] { inventoryMenuPath, inventoryPanelPath }
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenPublicAcceptanceApis.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
if (publicAcceptanceApiHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory drag/request APIs still expose submission as public acceptance state: " +
|
|
string.Join("; ", publicAcceptanceApiHits));
|
|
}
|
|
}
|
|
|
|
static void RequireTradePurchaseRequestAuthority(string root)
|
|
{
|
|
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 requiredRequests = new[]
|
|
{
|
|
"RequestTradePurchase",
|
|
"operations => operations.TradePurchase("
|
|
};
|
|
|
|
var missingRequests = requiredRequests
|
|
.Where(symbol => !tradeMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingRequests.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Trade purchase no longer has a typed daemon request primitive: " +
|
|
string.Join(", ", missingRequests));
|
|
}
|
|
|
|
var forbiddenUnityTradeAcceptanceSymbols = new[]
|
|
{
|
|
"price > Credits",
|
|
"totalPrice > Credits",
|
|
"targetCargo.TryFindSpace("
|
|
};
|
|
var unityTradeAcceptanceHits = forbiddenUnityTradeAcceptanceSymbols
|
|
.Where(symbol => tradeMenu.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (unityTradeAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity trade/loadout request code still rejects requests through renderer-local credits or cargo-capacity checks: " +
|
|
string.Join(", ", unityTradeAcceptanceHits));
|
|
}
|
|
|
|
var daemonOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs");
|
|
var daemonOperations = File.Exists(daemonOperationsPath)
|
|
? File.ReadAllText(daemonOperationsPath)
|
|
: throw new InvalidOperationException("Cannot verify trade purchase authority; daemon operations are missing.");
|
|
var requiredDaemonShipPurchaseSymbols = new[]
|
|
{
|
|
"ApplyCreateDockedShipPurchase(",
|
|
"var createsDockedShip = !string.IsNullOrWhiteSpace(typedItem.HullType)",
|
|
"ApplyCreateDockedShipPurchase(run, dockParentKey, itemKey, out var purchasedShipKey)",
|
|
"run.CurrentEntityKey = purchasedShipKey",
|
|
"HullItemKey = itemKey"
|
|
};
|
|
var missingDaemonShipPurchaseSymbols = requiredDaemonShipPurchaseSymbols
|
|
.Where(symbol => !daemonOperations.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingDaemonShipPurchaseSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon trade purchase no longer materializes purchased docked ships as typed run state: " +
|
|
string.Join(", ", missingDaemonShipPurchaseSymbols));
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
var forbiddenLocalAcceptanceSymbols = new[]
|
|
{
|
|
"price > GameManager.Credits",
|
|
"totalPrice > GameManager.Credits",
|
|
"GameManager.Credits / price",
|
|
"min(quantity, simpleCommodity.Quantity)",
|
|
"quantity = min(q, simpleCommodity.Quantity)",
|
|
"\"Insufficient Credits!\"",
|
|
"\"Insufficient Cargo Space!\"",
|
|
"\"Unable to create ship!\""
|
|
};
|
|
|
|
var localAcceptanceHits = File.ReadLines(tradeMenuPath)
|
|
.Select((line, index) => new { LineNumber = index + 1, Line = line })
|
|
.Where(line => forbiddenLocalAcceptanceSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, tradeMenuPath)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (localAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu still rejects purchases through renderer-local acceptance checks instead of daemon authority: " +
|
|
string.Join("; ", localAcceptanceHits));
|
|
}
|
|
|
|
if (tradeMenu.Contains("GameManager.RequestTradePurchase", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("TradeMenu still routes purchases through ActionGameManager instead of its typed operation client.");
|
|
}
|
|
|
|
var forbiddenSubmissionAcceptanceSymbols = new[]
|
|
{
|
|
"if (!GameManager.RequestTradePurchase(",
|
|
"\"Purchase request rejected!\"",
|
|
"public bool RequestTradePurchase("
|
|
};
|
|
var submissionAcceptanceHits = new[] { tradeMenuPath }
|
|
.SelectMany(path => File.ReadLines(path)
|
|
.Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line }))
|
|
.Where(line => forbiddenSubmissionAcceptanceSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (submissionAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu still treats daemon purchase submission as accepted or rejected purchase state: " +
|
|
string.Join("; ", submissionAcceptanceHits));
|
|
}
|
|
|
|
var firstTradeRequest = tradeMenu.IndexOf("RequestTradePurchase(", StringComparison.Ordinal);
|
|
var firstCreditRefreshAfterRequest = tradeMenu.IndexOf("UpdateCreditsLabel();", firstTradeRequest, StringComparison.Ordinal);
|
|
if (firstTradeRequest >= 0 && firstCreditRefreshAfterRequest >= 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"TradeMenu still refreshes projected credits immediately after submitting a daemon purchase request instead of waiting for daemon-observed state.");
|
|
}
|
|
}
|
|
|
|
static void RequireTradeSaleAuthority(string root)
|
|
{
|
|
var runtime = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime");
|
|
var documents = File.ReadAllText(Path.Combine(runtime, "AetheriaRuntimeDaemonDocuments.cs"));
|
|
var operations = File.ReadAllText(Path.Combine(runtime, "AetheriaRuntimeDaemonOperations.cs"));
|
|
var operationClient = File.ReadAllText(Path.Combine(runtime, "AetheriaRuntimeDaemonOperationClient.cs"));
|
|
var surfaceClient = File.ReadAllText(Path.Combine(runtime, "AetheriaRuntimeDaemonOperationsClient.cs"));
|
|
var inputCapabilities = File.ReadAllText(Path.Combine(runtime, "AetheriaRuntimeInputCapabilityDocuments.cs"));
|
|
|
|
var required = new Dictionary<string, string[]>
|
|
{
|
|
["command contract"] =
|
|
[
|
|
"TradeSale",
|
|
"public sealed class AetheriaRuntimeTradeSaleCommand",
|
|
"public AetheriaRuntimeTradeSaleCommand TradeSale"
|
|
],
|
|
["daemon transaction"] =
|
|
[
|
|
"ApplyTradeSale(run, command, context)",
|
|
"AetheriaRuntimeRefitTransactions.TryTransferCargo(",
|
|
"run.Credits += (int)payout",
|
|
"TradeStationNoFit"
|
|
],
|
|
["operation client"] =
|
|
[
|
|
"internal AetheriaRuntimeDaemonCommandEnvelope TradeSale(",
|
|
"command.TradeSale.ItemKey",
|
|
"command.TradeSale.Quantity"
|
|
],
|
|
["generic Eve translation"] =
|
|
[
|
|
"public CultMeshOperationReceipt TradeSale(",
|
|
"AetheriaRuntimeDaemonCommandKinds.TradeSale => client.TradeSale("
|
|
],
|
|
["advertised gameplay lever"] =
|
|
[
|
|
"trade.sell.",
|
|
"Sell {typedItem.Name}",
|
|
"\"TradeSale\""
|
|
]
|
|
};
|
|
var sources = new Dictionary<string, string>
|
|
{
|
|
["command contract"] = documents,
|
|
["daemon transaction"] = operations,
|
|
["operation client"] = operationClient,
|
|
["generic Eve translation"] = surfaceClient,
|
|
["advertised gameplay lever"] = inputCapabilities
|
|
};
|
|
var missing = required
|
|
.SelectMany(group => group.Value
|
|
.Where(symbol => !sources[group.Key].Contains(symbol, StringComparison.Ordinal))
|
|
.Select(symbol => $"{group.Key}: {symbol}"))
|
|
.ToArray();
|
|
if (missing.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Docked sale no longer crosses the complete daemon/Eve authority boundary: " +
|
|
string.Join(", ", missing));
|
|
}
|
|
|
|
var forbiddenClientOpinions = new[]
|
|
{
|
|
"TradeSale.UnitPrice",
|
|
"TradeSale.TotalPrice",
|
|
"TradeSale.StationEntityKey",
|
|
"TradeSale.StationCargoIndex"
|
|
};
|
|
var clientOpinionHits = forbiddenClientOpinions
|
|
.Where(symbol => operationClient.Contains(symbol, StringComparison.Ordinal) ||
|
|
surfaceClient.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (clientOpinionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Trade sale client still supplies daemon-owned station or payout opinions: " +
|
|
string.Join(", ", clientOpinionHits));
|
|
}
|
|
|
|
Console.WriteLine(
|
|
"Trade sale authority: generic Eve advertises source-stack intent; daemon owns station placement and payout");
|
|
}
|
|
|
|
static void RequireLootPickupRequestAuthority(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.");
|
|
|
|
if (actionGameManager.Contains("CommitLootPickup", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loot pickup should not have a Unity-owned gameplay mutation primitive; daemon snapshots own pickup state.");
|
|
}
|
|
|
|
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 still routes loot pickup through Unity gameplay authority.");
|
|
}
|
|
|
|
static void RequireEntityDestroyedRequestAuthority(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.");
|
|
|
|
if (actionGameManager.Contains("CommitEntityDestroyed", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Entity destruction should not have a Unity-owned gameplay mutation primitive; daemon frames own destruction and drops.");
|
|
}
|
|
|
|
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 still routes destruction through Unity gameplay authority.");
|
|
|
|
var authorityPolicyPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseAuthorityPolicy.cs");
|
|
var authorityPolicy = File.Exists(authorityPolicyPath)
|
|
? File.ReadAllText(authorityPolicyPath)
|
|
: throw new InvalidOperationException("Cannot verify entity destruction authority; Verse authority policy is missing.");
|
|
if (authorityPolicy.Contains("case AetheriaRuntimeDaemonCommandKinds.DestroyEntity:", StringComparison.Ordinal))
|
|
throw new InvalidOperationException("The retired DestroyEntity value still participates in command claim or subject routing.");
|
|
}
|
|
|
|
static void RequireDroppedPickupCheckpointState(string root)
|
|
{
|
|
var requiredFiles = new Dictionary<string, string[]>
|
|
{
|
|
[Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs")] = new[]
|
|
{
|
|
"AetheriaRuntimeDroppedPickupCommit",
|
|
"DroppedPickups",
|
|
"public double Temperature { get; set; }"
|
|
},
|
|
[Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs")] = new[]
|
|
{
|
|
"AetheriaDroppedPickupSnapshot",
|
|
"DroppedPickups",
|
|
"public double Temperature { get; set; }"
|
|
},
|
|
[Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs")] = new[]
|
|
{
|
|
"AetheriaRuntimeDroppedPickupCommit",
|
|
"public IReadOnlyList<AetheriaRuntimeDroppedPickupCommit> DroppedPickups",
|
|
"public double Temperature { get; set; }"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs")] = new[]
|
|
{
|
|
"private AetheriaUnityLoadoutItemFactory _loadoutItemFactory",
|
|
"_loadoutItemFactory = boot.LoadoutItemFactory"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs")] = new[]
|
|
{
|
|
"public sealed class AetheriaUnityGameplayBootShell",
|
|
"ZoneRenderer.SetDroppedPickupItemFactory(loadoutItemFactory.CreateLoadoutItem)"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedFrameApplier.cs")] = new[]
|
|
{
|
|
"public sealed class AetheriaUnityObservedFrameApplier",
|
|
"zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render)"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityLoadoutItemFactory.cs")] = new[]
|
|
{
|
|
"public sealed class AetheriaUnityLoadoutItemFactory",
|
|
"public ItemInstance CreateLoadoutItem(AetheriaRuntimeLoadoutItemCommit item)",
|
|
"_itemManager.CreateSimpleCommodityInstance",
|
|
"_itemManager.CreateCraftedInstance"
|
|
},
|
|
[Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs")] = new[]
|
|
{
|
|
"public void SetDroppedPickupItemFactory(Func<AetheriaRuntimeLoadoutItemCommit, ItemInstance> createDroppedPickupItem)",
|
|
"public void RestoreDroppedPickupsFromZoneRender(AetheriaRuntimeZoneRenderDocument render)",
|
|
"_createDroppedPickupItem?.Invoke(pickup.Item)",
|
|
"AetheriaRuntimeDroppedPickupCommit",
|
|
"DroppedPickups",
|
|
"DropItem(",
|
|
"ActiveLoot"
|
|
}
|
|
};
|
|
|
|
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));
|
|
}
|
|
|
|
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
|
var actionGameManager = File.Exists(actionGameManagerPath)
|
|
? File.ReadAllText(actionGameManagerPath)
|
|
: throw new InvalidOperationException("Cannot verify dropped pickup state; ActionGameManager.cs is missing.");
|
|
var forbiddenManagerSymbols = new[]
|
|
{
|
|
"ZoneRenderer.DropItem(",
|
|
"AetheriaRuntimeDroppedPickupCommit",
|
|
"ClearRenderedLoot("
|
|
};
|
|
var managerHits = forbiddenManagerSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (managerHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still lowers daemon dropped pickups into Unity loot instead of delegating renderer presentation: " +
|
|
string.Join(", ", managerHits));
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryLoadoutSaveRequestAuthority(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 save authority; ActionGameManager.cs is missing.");
|
|
var eveCommandDocumentPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeEveCommandDocument.cs");
|
|
var loadoutCommandsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeLoadoutTemplateCommands.cs");
|
|
var eveBridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs");
|
|
var runtimeStateMapperPath = Path.Combine(root, "Aetheria.State", "AetheriaRuntimeStateMapper.cs");
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var loadoutProjectorPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "AetheriaRuntimeLoadoutProjector.cs");
|
|
var snapshotDocumentsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeSnapshotDocuments.cs");
|
|
var loadoutSnapshotProjectorPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeLoadoutSnapshotProjector.cs");
|
|
var clientStatePath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaClientState.cs");
|
|
var dragSessionPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityDragSession.cs");
|
|
var dragObjectsPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityDragObjects.cs");
|
|
var gameplaySceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs");
|
|
|
|
if (File.Exists(loadoutProjectorPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Legacy AetheriaRuntimeLoadoutProjector must stay deleted; loadout-template saves should compose typed commits from managed daemon-frame documents.");
|
|
}
|
|
|
|
var requiredFiles = new[] { eveCommandDocumentPath, loadoutCommandsPath, eveBridgePath, runtimeStateMapperPath, inventoryPanelPath, snapshotDocumentsPath, clientStatePath, dragSessionPath, gameplaySceneWiringPath };
|
|
var missingFiles = requiredFiles.Where(path => !File.Exists(path)).ToArray();
|
|
if (missingFiles.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot verify loadout save authority; missing files: " +
|
|
string.Join(", ", missingFiles.Select(path => Path.GetRelativePath(root, path))));
|
|
}
|
|
|
|
var eveCommandDocument = File.ReadAllText(eveCommandDocumentPath);
|
|
var loadoutCommands = File.ReadAllText(loadoutCommandsPath);
|
|
var eveBridge = File.ReadAllText(eveBridgePath);
|
|
var runtimeStateMapper = File.ReadAllText(runtimeStateMapperPath);
|
|
var inventoryPanel = File.ReadAllText(inventoryPanelPath);
|
|
var snapshotDocuments = File.ReadAllText(snapshotDocumentsPath);
|
|
var clientState = File.ReadAllText(clientStatePath);
|
|
var dragSession = File.ReadAllText(dragSessionPath);
|
|
var gameplaySceneWiring = File.ReadAllText(gameplaySceneWiringPath);
|
|
var dragObjects = File.Exists(dragObjectsPath)
|
|
? File.ReadAllText(dragObjectsPath)
|
|
: throw new InvalidOperationException("Cannot verify inventory drag/drop authority; AetheriaUnityDragObjects.cs is missing.");
|
|
|
|
if (actionGameManager.Contains("ProjectLoadoutTemplate(Entity entity)", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("ActionGameManager.ProjectLoadoutTemplate", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loadout-template projection must not live on ActionGameManager; callers should use the managed daemon-frame snapshot path.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("public DragObject DragObject", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public bool HasDragTarget", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("_dragObject", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("_endDragCallback", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public abstract class DragObject", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public abstract class ItemDragObject", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public class ItemInstanceDragObject", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public class EquippedItemDragObject", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("GameManager.DragObject", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("GameManager.HasDragTarget", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("GameManager.BeginDrag", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("GameManager.EndDrag", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("GameManager.TryGetDraggedItem", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("GameManager.RegisterDragTarget", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("GameManager.UnregisterDragTarget", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public void BeginDrag(", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public bool TryGetDraggedItem(", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public void RegisterDragTarget(", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public void UnregisterDragTarget(", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public bool EndDrag(", StringComparison.Ordinal) ||
|
|
!actionGameManager.Contains("private readonly AetheriaUnityDragSession _dragSession", StringComparison.Ordinal) ||
|
|
!actionGameManager.Contains("SceneWiring.ConfigureInventoryDragSession(_dragSession)", StringComparison.Ordinal) ||
|
|
!gameplaySceneWiring.Contains("public void ConfigureInventoryDragSession(AetheriaUnityDragSession dragSession)", StringComparison.Ordinal) ||
|
|
!gameplaySceneWiring.Contains("Inventory?.SetDragSession(dragSession)", StringComparison.Ordinal) ||
|
|
!gameplaySceneWiring.Contains("ShipPanel?.SetDragSession(dragSession)", StringComparison.Ordinal) ||
|
|
!gameplaySceneWiring.Contains("TargetShipPanel?.SetDragSession(dragSession)", StringComparison.Ordinal) ||
|
|
!dragSession.Contains("public sealed class AetheriaUnityDragSession", StringComparison.Ordinal) ||
|
|
!dragObjects.Contains("public abstract class DragObject", StringComparison.Ordinal) ||
|
|
!dragObjects.Contains("public abstract class ItemDragObject : DragObject", StringComparison.Ordinal) ||
|
|
!dragObjects.Contains("public class ItemInstanceDragObject : ItemDragObject", StringComparison.Ordinal) ||
|
|
!dragObjects.Contains("public class EquippedItemDragObject : ItemDragObject", StringComparison.Ordinal) ||
|
|
!dragSession.Contains("private DragObject _dragObject", StringComparison.Ordinal) ||
|
|
!dragSession.Contains("private Action<DragObject> _endDragCallback", StringComparison.Ordinal) ||
|
|
!inventoryPanel.Contains("public void SetDragSession(AetheriaUnityDragSession dragSession)", StringComparison.Ordinal) ||
|
|
!inventoryPanel.Contains("private AetheriaUnityDragSession DragSession", StringComparison.Ordinal) ||
|
|
!inventoryPanel.Contains("DragSession.TryGetDraggedItem(out var itemDragObject)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Inventory drag/drop must not use ActionGameManager as a drag facade; inject the shared AetheriaUnityDragSession into inventory UI.");
|
|
}
|
|
|
|
var requiredInventoryPanelSymbols = new[]
|
|
{
|
|
"RequestLoadoutTemplateSave(string targetEntityKey)",
|
|
".CreateLoadoutTemplate(targetEntityKey ?? \"\")",
|
|
".Ui(\"unity-inventory\")",
|
|
".SaveLoadoutTemplateAsync(loadout, \"unity-inventory\")"
|
|
};
|
|
var missingInventoryPanelSymbols = requiredInventoryPanelSymbols
|
|
.Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal))
|
|
.Concat(new[] { "RuntimeState.DaemonFrame.Latest()" }
|
|
.Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal)))
|
|
.ToArray();
|
|
if (missingInventoryPanelSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel no longer sends a typed Eve loadout-template command from the managed current daemon frame: " +
|
|
string.Join(", ", missingInventoryPanelSymbols));
|
|
}
|
|
|
|
if (inventoryPanel.Contains("AetheriaRuntimeDaemonFrameSession _loadoutFrame", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> _loadoutFrame", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains(".ReactiveDaemonFrame(\"unity-inventory\")", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("_loadoutFrame?.Dispose()", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains(".ObserveDaemonFrame()", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("AetheriaUnityRuntimeClientProvider.RuntimeClient(\"unity-inventory\")", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains(".Ui.SaveLoadoutTemplateAsync(loadout, \"unity-inventory\")", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel must use managed current typed daemon-frame state and the provider-native UI command handle.");
|
|
}
|
|
|
|
if (inventoryPanel.Contains(".LoadoutTemplateAsync(", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains(".ProjectLoadoutTemplateAsync(client.State, targetEntityKey)", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("AetheriaRuntimeReactiveLoadoutSnapshotProjector", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains(".ReactiveLoadoutSnapshotProjector()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still asks an aggregate compatibility helper for loadout templates instead of reading the managed current daemon frame.");
|
|
}
|
|
|
|
if (File.Exists(loadoutSnapshotProjectorPath))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AetheriaRuntimeLoadoutSnapshotProjector is dead access chaff; AetheriaRuntimeRunCheckpointCommit owns loadout-template creation and append.");
|
|
}
|
|
|
|
var requiredRunCheckpointLoadoutSymbols = new[]
|
|
{
|
|
"public AetheriaRuntimeLoadoutTemplateCommit CreateLoadoutTemplate(string entityKey)",
|
|
"public AetheriaRuntimeLoadoutTemplateCommit CreateLoadoutTemplate(int zoneIndex, int entityIndex)",
|
|
"public string AppendLoadoutTemplateToZone(",
|
|
"TryParseEntityKey(entityKey, out var zoneIndex, out var entityIndex)",
|
|
"CreateLoadoutTemplate(zoneIndex, entityIndex)",
|
|
"CreateEntityLoadout(entity, entities)",
|
|
"AppendEntity(entities, template.RootEntity, template.Name)"
|
|
};
|
|
var missingRunCheckpointLoadoutSymbols = requiredRunCheckpointLoadoutSymbols
|
|
.Where(symbol => !snapshotDocuments.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingRunCheckpointLoadoutSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loadout template save payloads must be composed by the managed typed run checkpoint document: " +
|
|
string.Join(", ", missingRunCheckpointLoadoutSymbols));
|
|
}
|
|
|
|
if (snapshotDocuments.Contains("ProjectLoadoutTemplateAsync(", StringComparison.Ordinal) ||
|
|
snapshotDocuments.Contains("AetheriaClientState state", StringComparison.Ordinal) ||
|
|
snapshotDocuments.Contains(".Reactive<AetheriaRuntimeDaemonFrameDocument>()", StringComparison.Ordinal) ||
|
|
snapshotDocuments.Contains("frame.Current?.Run", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loadout snapshot composition must not own managed document sampling; InventoryPanel already caches the reactive daemon frame.");
|
|
}
|
|
|
|
if (snapshotDocuments.Contains("state.Daemon.LatestFrame.LatestAsync()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loadout template save payloads still perform one-shot daemon frame reads instead of using a reactive typed frame document.");
|
|
}
|
|
|
|
if (snapshotDocuments.Contains("state.Daemon.LatestFrame.ReactiveAsync", StringComparison.Ordinal) ||
|
|
snapshotDocuments.Contains("state.LatestFrame.ReactiveAsync", StringComparison.Ordinal) ||
|
|
snapshotDocuments.Contains(".ObserveDaemonFrame()", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loadout template save payloads must not sample managed daemon-frame state inside snapshot composition.");
|
|
}
|
|
|
|
if (clientState.Contains("ReactiveLoadoutSnapshotProjector", StringComparison.Ordinal) ||
|
|
snapshotDocuments.Contains("AetheriaRuntimeReactiveLoadoutSnapshotProjector", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("ProjectLoadoutTemplate(string targetEntityKey)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loadout template save payloads must not recreate aggregate reactive projector wrappers; cache or sample the managed typed daemon frame document directly.");
|
|
}
|
|
|
|
var forbiddenActionSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeStateCommitLog.QueueLoadoutTemplate",
|
|
"TrySendRuntimeLoadoutTemplateCommand(",
|
|
"private static bool TrySendRuntimeLoadoutTemplateCommand(",
|
|
"RequestLoadoutTemplateSave(EntityConstructionBlueprint blueprint)"
|
|
};
|
|
var forbiddenActionHits = forbiddenActionSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenActionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ActionGameManager still saves loadout templates through local commit or submission-acceptance authority: " +
|
|
string.Join(", ", forbiddenActionHits));
|
|
}
|
|
|
|
if (!inventoryPanel.Contains("RequestLoadoutTemplateSave(_displayedEntityKey)", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("GameManager.RequestLoadoutTemplateSave(_displayedEntity)", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("EntityConstructionBlueprintCapture.Capture(_displayedEntity)", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("EntityConstructionBlueprintProjector.CaptureBlueprint(_displayedEntity)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still captures a Unity EntityConstructionBlueprint before submitting a typed loadout-template save.");
|
|
}
|
|
|
|
var requiredBridgeSymbols = new[]
|
|
{
|
|
"AetheriaRuntimeEveCommandKind.SaveLoadoutTemplate",
|
|
"ExecuteLoadoutTemplateCommandAsync",
|
|
"command.LoadoutTemplate",
|
|
"AetheriaRuntimeStateMapper.ToLoadoutTemplate",
|
|
"AetheriaRuntimeStateMapper.LoadoutKey"
|
|
};
|
|
var missingBridgeSymbols = requiredBridgeSymbols
|
|
.Where(symbol => !eveBridge.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (missingBridgeSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Eve command bridge no longer owns typed loadout-template persistence: " +
|
|
string.Join(", ", missingBridgeSymbols));
|
|
}
|
|
|
|
if (!eveCommandDocument.Contains("AetheriaRuntimeLoadoutTemplateCommit? LoadoutTemplate", StringComparison.Ordinal) ||
|
|
!loadoutCommands.Contains("SurfaceId = \"aetheria.loadout_templates\"", StringComparison.Ordinal) ||
|
|
!loadoutCommands.Contains("Save = \"aetheria.loadout_templates.save\"", StringComparison.Ordinal) ||
|
|
!runtimeStateMapper.Contains("public static AetheriaLoadoutTemplate ToLoadoutTemplate", StringComparison.Ordinal) ||
|
|
!runtimeStateMapper.Contains("public static CultRecordKey LoadoutKey", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared loadout-template Eve command contract is incomplete.");
|
|
}
|
|
}
|
|
|
|
static void RequireInventoryLoadoutRestoreRequestAuthority(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 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 tradeQueriesPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonTradeItemQueries.cs");
|
|
var tradeQueries = File.Exists(tradeQueriesPath)
|
|
? File.ReadAllText(tradeQueriesPath)
|
|
: throw new InvalidOperationException("Cannot verify loadout restore authority; runtime trade item queries are missing.");
|
|
var rtsDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeGameViewportDocuments.cs");
|
|
var rtsDocuments = File.Exists(rtsDocumentsPath)
|
|
? File.ReadAllText(rtsDocumentsPath)
|
|
: throw new InvalidOperationException("Cannot verify loadout restore authority; RTS viewport documents are missing.");
|
|
var rtsDocumentFactoryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeGameDocuments.cs");
|
|
var rtsDocumentFactory = File.Exists(rtsDocumentFactoryPath)
|
|
? File.ReadAllText(rtsDocumentFactoryPath)
|
|
: throw new InvalidOperationException("Cannot verify loadout restore authority; RTS document factory source is missing.");
|
|
var clientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClient.cs");
|
|
var client = File.Exists(clientPath)
|
|
? File.ReadAllText(clientPath)
|
|
: throw new InvalidOperationException("Cannot verify loadout restore authority; AetheriaClient.cs is missing.");
|
|
var clientStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClientState.cs");
|
|
var clientState = File.Exists(clientStatePath)
|
|
? File.ReadAllText(clientStatePath)
|
|
: throw new InvalidOperationException("Cannot verify loadout restore authority; AetheriaClientState.cs is missing.");
|
|
var verseClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseClient.cs");
|
|
var verseClient = File.Exists(verseClientPath)
|
|
? File.ReadAllText(verseClientPath)
|
|
: throw new InvalidOperationException("Cannot verify loadout restore authority; AetheriaRuntimeVerseClient.cs is missing.");
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RequestRuntimeLoadoutRestore",
|
|
"operations => operations.RestoreLoadout",
|
|
"AetheriaRuntimeStationLoadoutRestoreOption",
|
|
"TryResolveStationRefitLoadout(",
|
|
"StationRefitSnapshot()?.LoadoutRestoreOptions",
|
|
"loadout.TargetEntityKey",
|
|
"loadout.TemplateName",
|
|
"loadout.Price"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loadout restore no longer has a typed daemon request primitive: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
if (actionGameManager.Contains("price > Credits", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("price > Credits", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("loadout.CanRestore", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity loadout restore still rejects requests through renderer-local credits or availability flags instead of daemon acceptance.");
|
|
}
|
|
|
|
if (actionGameManager.Contains("ObservedTradeValueSettings()", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("ObservedTradeValueSettings()", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("AetheriaUnityProjectionSettings.TradeValueSettings", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("AetheriaUnityProjectionSettings.TradeValueSettings", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("blueprint.Price(ItemManager)", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("blueprint.Price(ItemManager)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loadout restore must price requests through catalog-owned runtime trade policy instead of Unity settings or ItemManager value authority.");
|
|
}
|
|
|
|
var forbiddenActionSymbols = new[]
|
|
{
|
|
"TryRequestDaemonLoadoutRestore",
|
|
"private bool TryRequestDaemonLoadoutRestore("
|
|
};
|
|
var forbiddenActionHits = forbiddenActionSymbols
|
|
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (forbiddenActionHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity loadout restore still exposes daemon request submission as local acceptance state: " +
|
|
string.Join(", ", forbiddenActionHits));
|
|
}
|
|
|
|
var daemonOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs");
|
|
var daemonOperations = File.Exists(daemonOperationsPath)
|
|
? File.ReadAllText(daemonOperationsPath)
|
|
: throw new InvalidOperationException("Cannot verify loadout restore authority; daemon operations are missing.");
|
|
var missingTemplateCheck = daemonOperations.IndexOf("if (template == null)", StringComparison.Ordinal);
|
|
var creditMutation = daemonOperations.IndexOf("run.Credits -= price", StringComparison.Ordinal);
|
|
if (missingTemplateCheck < 0 ||
|
|
creditMutation < 0 ||
|
|
missingTemplateCheck > creditMutation)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon loadout restore must reject missing templates before charging credits or mutating run state.");
|
|
}
|
|
|
|
var forbiddenSymbols = new[]
|
|
{
|
|
"EntityConstructionBlueprintProjector.InstantiateFromBlueprint",
|
|
"EntityConstructionBlueprintProjector.ProjectObservedFromBlueprint",
|
|
"EntityConstructionBlueprintProjector.InstantiateAuthoritativeFromBlueprint",
|
|
"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));
|
|
}
|
|
|
|
var forbiddenLocalAcceptanceSymbols = new[]
|
|
{
|
|
"price < GameManager.Credits",
|
|
"price <= GameManager.Credits",
|
|
"price > GameManager.Credits",
|
|
"price >= GameManager.Credits"
|
|
};
|
|
var localAcceptanceHits = File.ReadLines(inventoryPanelPath)
|
|
.Select((line, index) => new { LineNumber = index + 1, Line = line })
|
|
.Where(line => forbiddenLocalAcceptanceSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
|
.Select(line => $"{Path.GetRelativePath(root, inventoryPanelPath)}:{line.LineNumber}: {line.Line.Trim()}")
|
|
.ToArray();
|
|
|
|
if (localAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still filters loadout restore through renderer-local credit checks instead of daemon acceptance: " +
|
|
string.Join("; ", localAcceptanceHits));
|
|
}
|
|
|
|
if (!inventoryPanel.Contains("TryResolveStationRefitLoadout(selection.TemplateIndex, out var loadout)", StringComparison.Ordinal) ||
|
|
!inventoryPanel.Contains("RequestRuntimeLoadoutRestore(loadout)", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("GameManager.RequestRuntimeLoadoutRestore", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("InventoryPanel no longer owns typed loadout restore operations directly.");
|
|
}
|
|
|
|
if (inventoryPanel.Contains("AetheriaRuntimeDaemonTradeItemQueries.TryPriceLoadoutTemplate(", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("AetheriaRuntimeDaemonTradeItemQueries.TryProjectLoadoutTemplatePrice(", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("blueprint.Price(GameManager.ItemManager)", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("LoadoutTemplatesAsync()", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("ResolveLoadoutTemplates", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel must consume StationRefitAsync loadout restore options instead of pricing or enumerating loadouts locally.");
|
|
}
|
|
|
|
if (tradeQueries.Contains("TryProjectLoadoutTemplatePrice(", StringComparison.Ordinal) ||
|
|
tradeQueries.Contains("TryProjectEntityLoadoutPrice(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime trade item queries must price typed loadout templates directly, without projector vocabulary.");
|
|
}
|
|
|
|
if (!tradeQueries.Contains("public static bool TryPriceLoadoutTemplate(", StringComparison.Ordinal) ||
|
|
!tradeQueries.Contains("TryPriceEntityLoadout(", StringComparison.Ordinal) ||
|
|
!tradeQueries.Contains("typedItem.Stackable", StringComparison.Ordinal) ||
|
|
!tradeQueries.Contains("typedItem.Price * Math.Max(1, item.Quantity)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Shared runtime trade item queries must own typed loadout template pricing.");
|
|
}
|
|
|
|
if (!rtsDocuments.Contains("public IReadOnlyList<AetheriaRuntimeStationLoadoutRestoreOption> LoadoutRestoreOptions", StringComparison.Ordinal) ||
|
|
!rtsDocuments.Contains("public sealed class AetheriaRuntimeStationLoadoutRestoreOption", StringComparison.Ordinal) ||
|
|
!rtsDocuments.Contains("public string TargetEntityKey", StringComparison.Ordinal) ||
|
|
!rtsDocuments.Contains("public int Price", StringComparison.Ordinal) ||
|
|
!rtsDocuments.Contains("public bool CanRestore", StringComparison.Ordinal) ||
|
|
!rtsDocumentFactory.Contains("ProjectLoadoutRestoreOptions(", StringComparison.Ordinal) ||
|
|
!rtsDocumentFactory.Contains("AetheriaRuntimeDaemonTradeItemQueries.TryPriceLoadoutTemplate(", StringComparison.Ordinal) ||
|
|
!rtsDocumentFactory.Contains("credits >= price", StringComparison.Ordinal) ||
|
|
!clientState.Contains("public CultMeshDocumentHandle<AetheriaRuntimeLoadoutTemplatesDocument> LoadoutTemplates", StringComparison.Ordinal) ||
|
|
!verseClient.Contains("RequireManagedLoadoutTemplates().Templates", StringComparison.Ordinal) ||
|
|
!verseClient.Contains("StationRefit(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"StationRefitAsync must publish typed loadout restore options with shared runtime pricing and daemon-target identity.");
|
|
}
|
|
|
|
var loadoutRestoreMatch = System.Text.RegularExpressions.Regex.Match(
|
|
inventoryPanel,
|
|
@"private void RequestRuntimeLoadoutRestore\(AetheriaRuntimeStationLoadoutRestoreOption loadout\)[\s\S]*?\n \}");
|
|
if (actionGameManager.Contains("RequestRuntimeLoadoutRestore(AetheriaRuntimeLoadoutTemplateSnapshot template, out Entity", StringComparison.Ordinal) ||
|
|
actionGameManager.Contains("public bool RequestRuntimeLoadoutRestore(", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("RequestRuntimeLoadoutRestore(template, out", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("RequestRuntimeLoadoutRestore(loadoutEntry.template, out", StringComparison.Ordinal) ||
|
|
!loadoutRestoreMatch.Success ||
|
|
loadoutRestoreMatch.Value.Contains("Display(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Loadout restore is still pretending daemon submission synchronously yields accepted Unity state.");
|
|
}
|
|
}
|
|
|
|
static void RequireDockedCurrentShipRequestAuthority(string root)
|
|
{
|
|
var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs");
|
|
var inventoryPanel = File.Exists(inventoryPanelPath)
|
|
? File.ReadAllText(inventoryPanelPath)
|
|
: throw new InvalidOperationException("Cannot verify docked current-ship authority; InventoryPanel.cs is missing.");
|
|
|
|
var requiredSymbols = new[]
|
|
{
|
|
"RequestDockedCurrentShip",
|
|
"operations => operations.SetDockedCurrentShip"
|
|
};
|
|
|
|
var missingSymbols = requiredSymbols
|
|
.Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
|
|
if (missingSymbols.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Docked current-ship selection no longer has a typed daemon request primitive: " +
|
|
string.Join(", ", missingSymbols));
|
|
}
|
|
|
|
if (inventoryPanel.Contains("DockedEntity.Children.Contains(ship)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity docked current-ship selection still rejects through renderer-local child membership instead of daemon acceptance.");
|
|
}
|
|
|
|
var forbiddenUnityAcceptanceSymbols = new[]
|
|
{
|
|
"!ship.IsPlayerShip",
|
|
"DockedEntity == null",
|
|
"DockingBay == null"
|
|
};
|
|
var unityAcceptanceHits = FindMethodScopedLineHits(inventoryPanel, forbiddenUnityAcceptanceSymbols)
|
|
.Where(hit => hit.MethodName == "RequestDockedCurrentShip")
|
|
.Select(hit => $"InventoryPanel.cs:{hit.LineNumber}: {hit.Line.Trim()}")
|
|
.ToArray();
|
|
if (unityAcceptanceHits.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Unity docked current-ship selection still rejects through renderer-local player/docking state instead of daemon acceptance: " +
|
|
string.Join(", ", unityAcceptanceHits));
|
|
}
|
|
|
|
if (inventoryPanel.Contains("public bool RequestDockedCurrentShip(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Docked current-ship request API still exposes submission as public acceptance state.");
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
if (inventoryPanel.Contains("GameManager.RequestDockedCurrentShip", StringComparison.Ordinal) ||
|
|
!inventoryPanel.Contains("RequestDockedCurrentShip(ship)", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("InventoryPanel no longer owns typed current-ship selection operations directly.");
|
|
}
|
|
|
|
if (!inventoryPanel.Contains("CurrentEntitySnapshot()?.EntityKey ?? \"\"", StringComparison.Ordinal) ||
|
|
!inventoryPanel.Contains("_displayedEntityKey", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal) ||
|
|
inventoryPanel.Contains("GameManager.CurrentEntity", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel must compare current-ship UI state through daemon-observed current entity state instead of peeking GameManager.CurrentEntity directly.");
|
|
}
|
|
|
|
if (inventoryPanel.Contains("Current.targetGraphic.color = ToggleEnabledColor;", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"InventoryPanel still paints current-ship selection as accepted immediately after daemon submission.");
|
|
}
|
|
}
|
|
|
|
static IEnumerable<(string MethodName, int LineNumber, string Line)> FindMethodScopedLineHits(
|
|
string source,
|
|
IReadOnlyList<string> symbols)
|
|
{
|
|
var currentMethod = "<top-level>";
|
|
var methodDepth = 0;
|
|
var pendingMethod = "";
|
|
var lines = source.Replace("\r\n", "\n", StringComparison.Ordinal).Split('\n');
|
|
for (var index = 0; index < lines.Length; index++)
|
|
{
|
|
var line = lines[index];
|
|
var trimmed = line.Trim();
|
|
var declaredMethod = TryReadMethodName(trimmed);
|
|
if (!string.IsNullOrWhiteSpace(declaredMethod))
|
|
pendingMethod = declaredMethod;
|
|
|
|
foreach (var symbol in symbols)
|
|
{
|
|
if (line.Contains(symbol, StringComparison.Ordinal))
|
|
yield return (currentMethod, index + 1, line);
|
|
}
|
|
|
|
var opens = line.Count(character => character == '{');
|
|
var closes = line.Count(character => character == '}');
|
|
if (!string.IsNullOrWhiteSpace(pendingMethod) && opens > 0)
|
|
{
|
|
currentMethod = pendingMethod;
|
|
pendingMethod = "";
|
|
methodDepth = opens - closes;
|
|
if (methodDepth <= 0)
|
|
{
|
|
currentMethod = "<top-level>";
|
|
methodDepth = 0;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (methodDepth <= 0)
|
|
continue;
|
|
|
|
methodDepth += opens - closes;
|
|
if (methodDepth <= 0)
|
|
{
|
|
currentMethod = "<top-level>";
|
|
methodDepth = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool ContainsUnitySettingsMember(string source, string memberName)
|
|
{
|
|
return source.Contains($" Settings.{memberName}", StringComparison.Ordinal) ||
|
|
source.Contains($"(Settings.{memberName}", StringComparison.Ordinal) ||
|
|
source.Contains($", Settings.{memberName}", StringComparison.Ordinal) ||
|
|
source.Contains($"\tSettings.{memberName}", StringComparison.Ordinal) ||
|
|
source.Contains($"\nSettings.{memberName}", StringComparison.Ordinal);
|
|
}
|
|
|
|
static string CompactSource(string source)
|
|
{
|
|
return string.Concat((source ?? "").Where(character => !char.IsWhiteSpace(character)));
|
|
}
|
|
|
|
static string TryReadMethodName(string trimmedLine)
|
|
{
|
|
if (!StartsWithMethodAccessModifier(trimmedLine) ||
|
|
!trimmedLine.Contains('(') ||
|
|
trimmedLine.Contains("=>", StringComparison.Ordinal) ||
|
|
trimmedLine.Contains("=", StringComparison.Ordinal))
|
|
{
|
|
return "";
|
|
}
|
|
|
|
var beforeParameters = trimmedLine.Substring(0, trimmedLine.IndexOf('(')).Trim();
|
|
var tokens = beforeParameters.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
|
return tokens.Length == 0 ? "" : tokens[^1];
|
|
}
|
|
|
|
static bool StartsWithMethodAccessModifier(string trimmedLine)
|
|
{
|
|
return trimmedLine.StartsWith("private ", StringComparison.Ordinal) ||
|
|
trimmedLine.StartsWith("public ", StringComparison.Ordinal) ||
|
|
trimmedLine.StartsWith("protected ", StringComparison.Ordinal) ||
|
|
trimmedLine.StartsWith("internal ", StringComparison.Ordinal) ||
|
|
trimmedLine.StartsWith("static ", StringComparison.Ordinal);
|
|
}
|
|
|
|
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}.");
|
|
}
|
|
}
|
|
|
|
static void RequireManagedContentOnlyAssetDelivery(string root)
|
|
{
|
|
var daemonPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs");
|
|
var documentsPath = Path.Combine(
|
|
root,
|
|
"Packages",
|
|
"org.gamecult.aetheria.state",
|
|
"Runtime",
|
|
"AetheriaRuntimeDaemonDocuments.cs");
|
|
var electronPath = Path.Combine(root, "Aetheria.Rts.Web", "Electron", "aetheria-cultmesh.ts");
|
|
var daemon = File.ReadAllText(daemonPath);
|
|
var documents = File.ReadAllText(documentsPath);
|
|
var electron = File.ReadAllText(electronPath);
|
|
var forbidden = new[]
|
|
{
|
|
"CultMeshCdnAssetBlob",
|
|
"gamecult.cultmesh.cdn.asset_blob.v1",
|
|
"TryResolveCultMeshCdnAssetPath"
|
|
};
|
|
var surviving = forbidden
|
|
.Where(symbol => daemon.Contains(symbol, StringComparison.Ordinal) ||
|
|
documents.Contains(symbol, StringComparison.Ordinal) ||
|
|
electron.Contains(symbol, StringComparison.Ordinal))
|
|
.ToArray();
|
|
if (surviving.Length > 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Snapshot delivery regained a byte-body authority; managed content sessions must be the sole asset-body transport: " +
|
|
string.Join(", ", surviving));
|
|
}
|
|
|
|
if (!daemon.Contains("InjectCultMeshCdnManifestSnapshots", StringComparison.Ordinal) ||
|
|
!daemon.Contains("new CultMeshTcpContentServer(", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Daemon asset delivery must publish snapshot manifests and host the dedicated TCP content plane.");
|
|
}
|
|
}
|