Import typed corporation allegiances

This commit is contained in:
Meta Vens 2026-06-13 13:18:03 +01:00
commit de36150dc2
158 changed files with 319 additions and 185 deletions

Binary file not shown.

View file

@ -91,7 +91,7 @@ await node.PutMigrationLedgerAsync(new AetheriaMigrationLedger
},
new AetheriaMigrationCount
{
DocumentType = "aetheria.corporation.v1",
DocumentType = "aetheria.corporation.v2",
Count = corporations.Length
},
new AetheriaMigrationCount
@ -412,7 +412,8 @@ internal static class LegacyCatalogReader
AllegianceCount = GetMapCount(payload, 12),
OverworldMusic = GetUInt(payload, 13),
CombatMusic = GetUInt(payload, 14),
BossMusic = GetUInt(payload, 15)
BossMusic = GetUInt(payload, 15),
Allegiances = ReadAllegiances(payload, 12)
}
: null,
unionKey == 9
@ -507,12 +508,22 @@ internal static class LegacyCatalogReader
for (var index = 0; index < length; index++)
{
var key = ReadValue(ref reader);
values[key?.ToString() ?? ""] = ReadValue(ref reader);
values[FormatMapKey(key)] = ReadValue(ref reader);
}
return values;
}
private static string FormatMapKey(object? key)
{
return key switch
{
byte[] bytes when bytes.Length == 16 => new Guid(bytes).ToString("D"),
string text => text,
_ => key?.ToString() ?? ""
};
}
private static object? SkipValue(ref MessagePackReader reader)
{
reader.Skip();
@ -598,6 +609,30 @@ internal static class LegacyCatalogReader
return payload.TryGetValue(key, out var value) && value is Dictionary<string, object?> map ? map.Count : 0;
}
private static AetheriaCorporationAllegiance[] ReadAllegiances(IReadOnlyDictionary<int, object?> payload, int key)
{
if (!payload.TryGetValue(key, out var value) || value is not Dictionary<string, object?> map)
{
return [];
}
return map
.Select(entry => new AetheriaCorporationAllegiance
{
CorporationLegacyId = NormalizeLegacyId(entry.Key),
Weight = ReadDoubleValue(entry.Value)
})
.Where(allegiance => !string.IsNullOrWhiteSpace(allegiance.CorporationLegacyId))
.ToArray();
}
private static string NormalizeLegacyId(string legacyId)
{
return Guid.TryParse(legacyId, out var parsed) && parsed != Guid.Empty
? parsed.ToString("D")
: "";
}
private static int GetArrayLength(IReadOnlyDictionary<int, object?> payload, int key)
{
return payload.TryGetValue(key, out var value) && value is object?[] array ? array.Length : 0;

View file

@ -109,12 +109,22 @@ if (catalog.GetManufacturer(manufactured) == null)
}
var packageCorporation = packageCatalog.Corporations.FirstOrDefault(corporation => !string.IsNullOrWhiteSpace(corporation.GeonameFileLegacyId));
var packageNameFile = packageCorporation == null ? null : packageCatalog.GetNameFile(packageCorporation);
if (packageCorporation == null)
{
throw new InvalidOperationException("Package catalog store did not read any corporation name-file links.");
}
var packageNameFile = packageCatalog.GetNameFile(packageCorporation);
if (packageNameFile == null || packageNameFile.Names.Count == 0 || packageNameFile.Names.Count != packageNameFile.NameCount)
{
throw new InvalidOperationException("Package catalog store did not read corporation/name-file links with full names.");
}
if (packageCorporation.Allegiances.Count == 0 || packageCorporation.Allegiances.Count != packageCorporation.AllegianceCount)
{
throw new InvalidOperationException("Package catalog store did not read corporation allegiance edges.");
}
if (surface?.Schema != "gamecult.eve.surface.v1" ||
surface.Surface.Id != AetheriaCatalogSurfaceProjector.SurfaceId)
{

View file

@ -107,7 +107,10 @@ internal static class AetheriaRuntimeCatalogSnapshotMapper
corporation.GeonameFileLegacyId,
corporation.BossHullLegacyId,
corporation.InfluenceDistance,
corporation.AllegianceCount);
corporation.AllegianceCount,
corporation.Allegiances.Select(allegiance => new AetheriaRuntimeCorporationAllegiance(
allegiance.CorporationLegacyId,
allegiance.Weight)).ToArray());
}
private static AetheriaRuntimeNameFile FromState(AetheriaNameFile nameFile)

View file

@ -22,7 +22,7 @@ var catalog = node.ReadCatalogSnapshot();
var surface = AetheriaCatalogSurfaceProjector.Build(catalog, DateTimeOffset.UtcNow.ToString("O"));
RequireCount(ledger, "aetheria.item_definition.v1", items.Length);
RequireCount(ledger, "aetheria.corporation.v1", corporations.Length);
RequireCount(ledger, "aetheria.corporation.v2", corporations.Length);
RequireCount(ledger, "aetheria.name_file.v2", nameFiles.Length);
if (items.Length == 0)
@ -68,6 +68,7 @@ var weaponItems = items.Count(item =>
!string.IsNullOrWhiteSpace(item.WeaponCaliber));
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);
if (pricedItems == 0)
{
@ -208,6 +209,11 @@ 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)
{
@ -297,6 +303,7 @@ Console.WriteLine($"Typed catalog trade items: {tradeItems.Length}");
Console.WriteLine($"Eve catalog surface: {surface.Surface.Id} ({surface.Surface.Root.Children.Length} root children)");
Console.WriteLine($"Corporations: {corporations.Length}");
Console.WriteLine($"Described/geoname-linked corporations: {describedCorporations}/{corporationNameLinks}");
Console.WriteLine($"Corporation allegiance edges: {corporationAllegianceEdges}");
Console.WriteLine($"Name files: {nameFiles.Length}");
static void RequireCount(AetheriaMigrationLedger ledger, string documentType, int actual)

View file

@ -3,7 +3,7 @@ using MessagePack;
namespace Aetheria.State.Documents;
[CultDocument("aetheria.corporation", "aetheria.corporation.v1")]
[CultDocument("aetheria.corporation", "aetheria.corporation.v2")]
[MessagePackObject]
public sealed class AetheriaCorporation
{
@ -43,4 +43,17 @@ public sealed class AetheriaCorporation
[Key(10)]
public uint BossMusic { get; set; }
[Key(11)]
public AetheriaCorporationAllegiance[] Allegiances { get; set; } = [];
}
[MessagePackObject]
public sealed class AetheriaCorporationAllegiance
{
[Key(0)]
public string CorporationLegacyId { get; set; } = "";
[Key(1)]
public double Weight { get; set; }
}

View file

@ -58,9 +58,11 @@ stable equipment facets: hardpoint type, hull type, stack size, durability, and
weapon range/caliber/type/fire/modifier classifications.
Typed corporation documents include the legacy short name, the true description
field from key 3, name-file and boss-hull legacy IDs, influence distance,
allegiance count, and music bank IDs. Runtime object graphs, typed behavior
factory construction, and simulation state remain legacy until dedicated typed
runtime documents exist.
allegiance count, full allegiance edges, and music bank IDs. Corporation
documents are `aetheria.corporation.v2` records because runtime generation
needs actual allegiance weights, not only display counts. Runtime object graphs,
typed behavior factory construction, and simulation state remain legacy until
dedicated typed runtime documents exist.
Typed name-file documents are `aetheria.name_file.v2` records. They carry the
legacy ID, display name, count, compact sample names for surfaces, and the full
name array needed to move `Galaxy`/Markov name generation off legacy
@ -94,11 +96,11 @@ published Eve catalog surface. It does not deserialize legacy `DatabaseEntry`
objects and does not write state. Unity can use this as the first package
boundary once CultLib/Eve runtime packaging is available.
`Galaxy` now consumes this package-owned runtime catalog for faction selection
and name generation, so generated sectors use typed corporation and
and name generation, so generated sectors use typed corporation v2 and
`aetheria.name_file.v2` records instead of legacy `Faction`/`NameFile`
catalog documents. It still projects typed corporations into temporary
`Faction` DTOs because the surrounding simulation has not yet been rebuilt
around typed corporation records.
`Faction` DTOs, including allegiance dictionaries, because the surrounding
simulation has not yet been rebuilt around typed corporation records.
`Aetheria.State.Smoke` writes and reopens full typed player settings plus a
typed loadout template in addition to world, catalog, run state, zone state, and

View file

@ -220,7 +220,8 @@ public class Galaxy
private static Faction[] ProjectFactions(AetheriaRuntimeCatalogSnapshot runtimeCatalog)
{
var factions = runtimeCatalog.Corporations
var corporations = runtimeCatalog.Corporations.ToArray();
var factions = corporations
.Select(ProjectFaction)
.ToArray();
if (factions.Length == 0)
@ -228,6 +229,20 @@ public class Galaxy
throw new InvalidOperationException("Typed catalog has no factions for galaxy generation.");
}
var factionsByLegacyId = factions.ToDictionary(faction => faction.ID.ToString("D"), StringComparer.OrdinalIgnoreCase);
for (var index = 0; index < corporations.Length; index++)
{
foreach (var allegiance in corporations[index].Allegiances)
{
if (Guid.TryParse(allegiance.CorporationLegacyId, out var corporationId) &&
corporationId != Guid.Empty &&
factionsByLegacyId.ContainsKey(corporationId.ToString("D")))
{
factions[index].Allegiance[corporationId] = (float) allegiance.Weight;
}
}
}
return factions;
}

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more