Recover ordinary hulls and rare drive generation
This commit is contained in:
parent
141689dd8e
commit
6504050207
10 changed files with 199 additions and 41 deletions
|
|
@ -1043,14 +1043,20 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
.Where(item => item.Hardpoints.Any(point => point.Type == "AetherDrive"))
|
||||
.ToArray();
|
||||
Require(drives.Length == 1 && drives[0].Name == "Traction" &&
|
||||
drives[0].HardpointType == "AetherDrive" && drives[0].Price == 250000,
|
||||
drives[0].HardpointType == "AetherDrive" && drives[0].Price == 250000 &&
|
||||
drives[0].Tags.Contains("rarity:rare", StringComparer.Ordinal),
|
||||
"canonical AetherDrive must remain the singular expensive Traction retrofit");
|
||||
Require(driveHulls.Length == 1 && driveHulls[0].Name == "LonginusX" &&
|
||||
driveHulls[0].Price == 7500000,
|
||||
driveHulls[0].Price == 7500000 &&
|
||||
driveHulls[0].Tags.Contains("rarity:rare", StringComparer.Ordinal),
|
||||
"canonical AetherDrive compatibility must remain confined to the rare modified LonginusX hull");
|
||||
Require(catalog.Items.Count(item => !string.IsNullOrWhiteSpace(item.HullType) &&
|
||||
item.Hardpoints.All(point => point.Type != "AetherDrive")) > driveHulls.Length,
|
||||
"ordinary hull variants must outnumber and exclude the AetherDrive retrofit path");
|
||||
var ordinaryShipHulls = catalog.Items.Where(item => item.HullType == "Ship" &&
|
||||
item.Hardpoints.All(point => point.Type != "AetherDrive") &&
|
||||
!item.Tags.Contains("rarity:rare", StringComparer.Ordinal))
|
||||
.ToArray();
|
||||
Require(ordinaryShipHulls.Any(item => item.Name == "Longinus") &&
|
||||
ordinaryShipHulls.Any(item => item.Name == "Djinni"),
|
||||
"the recovered common Longinus and Djinni must own ordinary ship generation");
|
||||
var mine = catalog.Items.Single(item =>
|
||||
item.ItemKey == "aetheria.item_definition:legacy:e78d3670-3ac6-4d9b-834c-1da4228ac311");
|
||||
RequireEqual("Mine Launcher", mine.Name, "recovered item must retain fossil identity");
|
||||
|
|
@ -1666,6 +1672,19 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
var homes = new Dictionary<string, int> { ["forge"] = 0, ["foreign"] = 1 };
|
||||
var adjacency = new Dictionary<int, IReadOnlyList<int>> { [0] = [1], [1] = [0] };
|
||||
|
||||
var rareHull = Item("rare-hull", AetheriaRuntimeItemCategories.Hull, "forge", 100);
|
||||
rareHull.HardpointType = availableHull.HardpointType;
|
||||
rareHull.HullType = availableHull.HullType;
|
||||
rareHull.ShapeWidth = availableHull.ShapeWidth;
|
||||
rareHull.ShapeHeight = availableHull.ShapeHeight;
|
||||
rareHull.OccupiedCells = availableHull.OccupiedCells;
|
||||
rareHull.ShapeCells = availableHull.ShapeCells;
|
||||
rareHull.Hardpoints = availableHull.Hardpoints;
|
||||
rareHull.Tags = ["rarity:rare"];
|
||||
var rarityCatalog = new AetheriaRuntimeCatalogSnapshot(
|
||||
[availableHull, rareHull, cockpit, wrongController, weapon, shipSensor, cargo, capacitor, reactor],
|
||||
[faction], Array.Empty<AetheriaRuntimeNameFile>());
|
||||
|
||||
var first = new AetheriaDaemonLoadoutGenerator(catalog, 42, 0, homes, adjacency).Build("ship", "forge");
|
||||
var second = new AetheriaDaemonLoadoutGenerator(catalog, 42, 0, homes, adjacency).Build("ship", "forge");
|
||||
var station = new AetheriaDaemonLoadoutGenerator(catalog, 84, 0, homes, adjacency).Build("station", "forge");
|
||||
|
|
@ -1682,6 +1701,11 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
var interleavedFirst = interleavedForge.Build("ship", "forge");
|
||||
foreignStream.Build("ship", "foreign");
|
||||
var interleavedSecond = interleavedForge.Build("ship", "forge");
|
||||
var rarityGenerator = new AetheriaDaemonLoadoutGenerator(
|
||||
rarityCatalog, 9001, 0, homes, adjacency);
|
||||
var rareSelections = Enumerable.Range(0, 2000)
|
||||
.Select(_ => rarityGenerator.Build("ship", "forge").HullItemKey)
|
||||
.Count(key => key == "rare-hull");
|
||||
|
||||
RequireEqual("available-hull", first.HullItemKey,
|
||||
"loadout generation must exclude manufacturers outside the faction allegiance graph");
|
||||
|
|
@ -1706,6 +1730,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
first.Receipt.Selections.Any(value => value.Role == "hull" && value.ItemKey == "available-hull" &&
|
||||
value.ManufacturerKey == "forge" && value.ManufacturerDistance == 1 && value.Allegiance == 1),
|
||||
"generated loadouts must carry daemon-authored source and selection provenance");
|
||||
Require(rareSelections > 0 && rareSelections < 200,
|
||||
$"rare catalog candidates must remain possible without becoming a default loadout path (selected {rareSelections}/2000)");
|
||||
Require(station.Equipment.Any(value => value.ItemKey == "docking-bay") &&
|
||||
station.Equipment.Any(value => value.ItemKey == "cargo-bay") &&
|
||||
station.Equipment.Any(value => value.ItemKey == "capacitor") &&
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ using Random = CultMath.Random;
|
|||
|
||||
public sealed class AetheriaDaemonLoadoutGenerator
|
||||
{
|
||||
private const double RareGenerationWeight = 0.05;
|
||||
private readonly AetheriaRuntimeCatalogSnapshot _catalog;
|
||||
private readonly Random _random;
|
||||
private Random _random;
|
||||
private readonly uint _seed;
|
||||
private readonly int _zoneIndex;
|
||||
private readonly IReadOnlyDictionary<string, int> _homeZones;
|
||||
|
|
@ -274,9 +275,14 @@ public sealed class AetheriaDaemonLoadoutGenerator
|
|||
: faction.Allegiances.First(value => value.CorporationKey == item.ManufacturerKey).Weight /
|
||||
Math.Max(1, Distance(_zoneIndex, _homeZones[item.ManufacturerKey]));
|
||||
return allegiance * Math.Pow(Math.Max(1, item.OccupiedCells), sizeExponent) /
|
||||
Math.Pow(item.Price, _priceExponent);
|
||||
Math.Pow(item.Price, _priceExponent) * RarityWeight(item);
|
||||
}
|
||||
|
||||
private static double RarityWeight(AetheriaRuntimeCatalogItem item) =>
|
||||
(item.Tags ?? Array.Empty<string>()).Contains("rarity:rare", StringComparer.Ordinal)
|
||||
? RareGenerationWeight
|
||||
: 1;
|
||||
|
||||
private int Distance(int start, int target)
|
||||
{
|
||||
if (start == target) return 1;
|
||||
|
|
|
|||
|
|
@ -15,11 +15,13 @@ var outputStatePath = Path.GetRelativePath(root, statePath).Replace('\\', '/');
|
|||
var gameData = Path.Combine(root, "GameData");
|
||||
var catalogPath = Path.Combine(gameData, "AetherDB.msgpack");
|
||||
var supplementalCatalogPath = Path.Combine(gameData, "Legacy", "AetherDB.2021-03-05.msgpack");
|
||||
var recoveredHullCatalogPath = Path.Combine(gameData, "Legacy", "AetherDB.2021-04-14.msgpack");
|
||||
var nameFilesRoot = Path.Combine(gameData, "NameFile");
|
||||
var capturedAtUtc = DateTimeOffset.UtcNow.ToString("O");
|
||||
|
||||
var catalog = CaptureFile(root, catalogPath);
|
||||
var supplementalCatalog = CaptureFile(root, supplementalCatalogPath);
|
||||
var recoveredHullCatalog = CaptureFile(root, recoveredHullCatalogPath);
|
||||
var nameFiles = Directory.Exists(nameFilesRoot)
|
||||
? Directory.EnumerateFiles(nameFilesRoot, "*.msgpack", SearchOption.TopDirectoryOnly)
|
||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
|
||||
|
|
@ -32,7 +34,16 @@ var supplementalMineEntries = LegacyCatalogReader.Read(supplementalCatalogPath)
|
|||
.Where(entry => entry.ItemDefinition != null && IsMineItem(entry.ItemDefinition))
|
||||
.Where(entry => !currentLegacyIds.Contains(entry.Summary.LegacyId))
|
||||
.ToArray();
|
||||
var entries = currentEntries.Concat(supplementalMineEntries).ToArray();
|
||||
var recoveredGameplayEntries = LegacyCatalogReader.Read(
|
||||
recoveredHullCatalogPath,
|
||||
LegacyCatalogLayout.April2021)
|
||||
.Where(entry => entry.ItemDefinition != null && IsRecoveredGameplayItem(entry.ItemDefinition))
|
||||
.Where(entry => !currentLegacyIds.Contains(entry.Summary.LegacyId))
|
||||
.ToArray();
|
||||
var entries = currentEntries
|
||||
.Concat(supplementalMineEntries)
|
||||
.Concat(recoveredGameplayEntries)
|
||||
.ToArray();
|
||||
var nameFileEntries = Directory.Exists(nameFilesRoot)
|
||||
? Directory.EnumerateFiles(nameFilesRoot, "*.msgpack", SearchOption.TopDirectoryOnly)
|
||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
|
||||
|
|
@ -41,7 +52,7 @@ var nameFileEntries = Directory.Exists(nameFilesRoot)
|
|||
: [];
|
||||
var itemDefinitions = entries
|
||||
.Where(entry => entry.ItemDefinition != null)
|
||||
.Select(entry => ProjectStationSensorMount(ProjectDeployableWeapon(entry.ItemDefinition!)))
|
||||
.Select(entry => ProjectCatalogRarity(ProjectStationSensorMount(ProjectDeployableWeapon(entry.ItemDefinition!))))
|
||||
.ToArray();
|
||||
var corporations = entries
|
||||
.Where(entry => entry.Corporation != null)
|
||||
|
|
@ -145,6 +156,29 @@ static bool IsMineItem(AetheriaItemDefinition item)
|
|||
return string.Equals(item.WeaponType, "Mine", StringComparison.Ordinal) || hasMineEffect;
|
||||
}
|
||||
|
||||
static bool IsRecoveredGameplayItem(AetheriaItemDefinition item)
|
||||
{
|
||||
return string.Equals(item.LegacyId, "c4b20032-8cd3-4206-807f-97b296797425", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.LegacyId, "bb5d9f73-612f-4060-8fb4-f72d6b93b616", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.HardpointType, "Thruster", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
static AetheriaItemDefinition ProjectCatalogRarity(AetheriaItemDefinition item)
|
||||
{
|
||||
var tags = (item.Tags ?? []).AsEnumerable();
|
||||
if (IsRecoveredGameplayItem(item))
|
||||
tags = tags.Append("provenance:recovered:2021-04-14");
|
||||
var isRareHull = string.Equals(item.Name, "LonginusX", StringComparison.Ordinal) &&
|
||||
string.Equals(item.HullType, "Ship", StringComparison.Ordinal);
|
||||
var isRareDrive = (item.BehaviorKinds ?? []).Contains("AetherDrive", StringComparer.Ordinal);
|
||||
if (isRareHull || isRareDrive)
|
||||
tags = tags.Append("rarity:rare");
|
||||
item.Tags = tags
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
return item;
|
||||
}
|
||||
|
||||
static AetheriaBehaviorField NumberBehaviorField(int key, double value) => new()
|
||||
{
|
||||
Key = key,
|
||||
|
|
@ -224,12 +258,15 @@ await node.MutableDocument<AetheriaLegacyCatalogQuarantine>(AetheriaStateNode.Le
|
|||
CatalogFingerprint = catalog.Fingerprint,
|
||||
CatalogBytes = catalog.Bytes,
|
||||
NameFiles = nameFiles,
|
||||
SupplementalCatalogFiles = supplementalCatalog.Bytes > 0 ? [supplementalCatalog] : [],
|
||||
SupplementalCatalogFiles = new[] { supplementalCatalog, recoveredHullCatalog }
|
||||
.Where(file => file.Bytes > 0)
|
||||
.ToArray(),
|
||||
Notes =
|
||||
[
|
||||
"This document quarantines legacy catalog file facts and records the bounded raw payload mapping pass. It does not grant old MessagePack files runtime state authority.",
|
||||
"AetherDB.msgpack and NameFile/*.msgpack remain migration inputs until Unity runtime reads typed Aetheria catalog documents directly.",
|
||||
"The 2021-03-05 supplemental catalog contributes only the missing Mine Launcher item and remains quarantined provenance, not runtime authority."
|
||||
"The 2021-03-05 supplemental catalog contributes only the missing Mine Launcher item and remains quarantined provenance, not runtime authority.",
|
||||
"The 2021-04-14 catalog contributes only the missing common Longinus and Djinni hull rows plus missing Thruster equipment required to outfit their authored mounts. It preserves the post-drive catalog's distinct Longinus/LonginusX identities after the later modular database lost the common rows."
|
||||
]
|
||||
});
|
||||
|
||||
|
|
@ -277,6 +314,7 @@ await node.MutableDocument<AetheriaMigrationLedger>(AetheriaStateNode.MigrationL
|
|||
[
|
||||
"Legacy catalog quarantine captured file fingerprints and mapped stable old MessagePack union payload fields into typed CultCache documents.",
|
||||
"The mapper intentionally reads only stable scalar/catalog fields. Runtime object graphs, behavior payloads, and Unity-specific shapes remain legacy until dedicated typed documents exist.",
|
||||
"Recovered common Longinus and Djinni hulls and their missing Thruster equipment come from the fingerprinted 2021-04-14 catalog; LonginusX and AetherDrive items are explicitly tagged as rare generation candidates.",
|
||||
$"State path: {outputStatePath}"
|
||||
]
|
||||
});
|
||||
|
|
@ -405,6 +443,12 @@ static void DeleteDirectoryIfExists(string path)
|
|||
}
|
||||
}
|
||||
|
||||
internal enum LegacyCatalogLayout
|
||||
{
|
||||
Current,
|
||||
April2021
|
||||
}
|
||||
|
||||
internal static class LegacyCatalogReader
|
||||
{
|
||||
private static readonly Dictionary<int, string> UnionNames = new()
|
||||
|
|
@ -548,7 +592,9 @@ internal static class LegacyCatalogReader
|
|||
[38] = "AutoWeapon"
|
||||
};
|
||||
|
||||
public static IReadOnlyList<LegacyCatalogEntry> Read(string catalogPath)
|
||||
public static IReadOnlyList<LegacyCatalogEntry> Read(
|
||||
string catalogPath,
|
||||
LegacyCatalogLayout layout = LegacyCatalogLayout.Current)
|
||||
{
|
||||
if (!File.Exists(catalogPath))
|
||||
{
|
||||
|
|
@ -561,7 +607,7 @@ internal static class LegacyCatalogReader
|
|||
var entries = new List<LegacyCatalogEntry>(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
entries.Add(ReadEntry(ref reader));
|
||||
entries.Add(ReadEntry(ref reader, layout));
|
||||
}
|
||||
|
||||
return entries;
|
||||
|
|
@ -571,10 +617,12 @@ internal static class LegacyCatalogReader
|
|||
{
|
||||
var bytes = File.ReadAllBytes(path);
|
||||
var reader = new MessagePackReader(bytes);
|
||||
return ReadEntry(ref reader);
|
||||
return ReadEntry(ref reader, LegacyCatalogLayout.Current);
|
||||
}
|
||||
|
||||
private static LegacyCatalogEntry ReadEntry(ref MessagePackReader reader)
|
||||
private static LegacyCatalogEntry ReadEntry(
|
||||
ref MessagePackReader reader,
|
||||
LegacyCatalogLayout layout)
|
||||
{
|
||||
var unionHeader = reader.ReadArrayHeader();
|
||||
if (unionHeader != 2)
|
||||
|
|
@ -591,7 +639,9 @@ internal static class LegacyCatalogReader
|
|||
var shape = ReadShape(payload, 5);
|
||||
var interiorShape = ReadInteriorShape(unionKey, payload, shape);
|
||||
var dockingMaxSize = unionKey == 30 ? ReadPoint(payload, 25) : PointFacts.Empty;
|
||||
var hardpoints = unionKey == 3 ? ReadHardpoints(payload, 23) : [];
|
||||
var hardpoints = unionKey == 3
|
||||
? ReadHardpoints(payload, layout == LegacyCatalogLayout.April2021 ? 20 : 23)
|
||||
: [];
|
||||
var behaviorKey = unionKey == 31 || unionKey is 2 or 3 or 29 or 30 ? 11 : 10;
|
||||
var behaviorPayloads = ReadBehaviorPayloads(payload, behaviorKey);
|
||||
var itemCategory = GetItemCategory(unionKey);
|
||||
|
|
@ -636,8 +686,10 @@ internal static class LegacyCatalogReader
|
|||
InteriorShapeCells = interiorShape.Cells,
|
||||
Hardpoints = hardpoints,
|
||||
BehaviorPayloads = behaviorPayloads,
|
||||
HardpointType = GetHardpointType(unionKey, payload),
|
||||
HullType = unionKey == 3 ? GetEnumName(payload, 25, HullTypes) : "",
|
||||
HardpointType = GetHardpointType(unionKey, payload, layout),
|
||||
HullType = unionKey == 3
|
||||
? GetEnumName(payload, layout == LegacyCatalogLayout.April2021 ? 22 : 25, HullTypes)
|
||||
: "",
|
||||
BehaviorKinds = behaviorKinds,
|
||||
BehaviorCount = behaviorPayloads.Length,
|
||||
MaxStack = unionKey == 0 ? GetInt(payload, 9) : 0,
|
||||
|
|
@ -648,16 +700,28 @@ internal static class LegacyCatalogReader
|
|||
MaximumTemperature = unionKey is 2 or 3 or 29 or 30 or 31 ? GetDouble(payload, 14) : 0,
|
||||
ThermalPerformanceCurveKeys = unionKey is 2 or 3 or 29 or 30 or 31 ? ReadCurveKeys(payload, 17) : [],
|
||||
ThermalResilience = unionKey is 2 or 3 or 29 or 30 or 31 ? GetPositiveDoubleOrDefault(payload, 18, 1) : 1,
|
||||
AudioStats = unionKey is 2 or 3 or 29 or 30 or 31 ? ReadAudioStats(payload, 22) : [],
|
||||
AudioStats = layout == LegacyCatalogLayout.Current && unionKey is 2 or 3 or 29 or 30 or 31
|
||||
? ReadAudioStats(payload, 22)
|
||||
: [],
|
||||
EffectivenessCurveKeys = isConsumable ? ReadCurveKeys(payload, 14) : [],
|
||||
HullPrefab = unionKey == 3 ? GetString(payload, 24) : "",
|
||||
HullGridOffset = unionKey == 3 ? GetDouble(payload, 26) : 0,
|
||||
HullArmor = unionKey == 3 ? GetDouble(payload, 27) : 0,
|
||||
HullDrag = unionKey == 3 ? GetDouble(payload, 28) : 0,
|
||||
HullCanTow = unionKey == 3 && GetBool(payload, 29),
|
||||
HullPrefab = unionKey == 3
|
||||
? GetString(payload, layout == LegacyCatalogLayout.April2021 ? 21 : 24)
|
||||
: "",
|
||||
HullGridOffset = unionKey == 3
|
||||
? GetDouble(payload, layout == LegacyCatalogLayout.April2021 ? 23 : 26)
|
||||
: 0,
|
||||
HullArmor = unionKey == 3
|
||||
? GetDouble(payload, layout == LegacyCatalogLayout.April2021 ? 24 : 27)
|
||||
: 0,
|
||||
HullDrag = unionKey == 3
|
||||
? GetDouble(payload, layout == LegacyCatalogLayout.April2021 ? 25 : 28)
|
||||
: 0,
|
||||
HullCanTow = layout == LegacyCatalogLayout.Current && unionKey == 3 && GetBool(payload, 29),
|
||||
DockingMaxSizeX = dockingMaxSize.X,
|
||||
DockingMaxSizeY = dockingMaxSize.Y,
|
||||
ActionBarIcon = unionKey is 2 or 3 or 29 or 30 or 31 ? GetString(payload, 20) : "",
|
||||
ActionBarIcon = layout == LegacyCatalogLayout.Current && unionKey is 2 or 3 or 29 or 30 or 31
|
||||
? GetString(payload, 20)
|
||||
: "",
|
||||
SimpleCommodityCategory = unionKey == 0 ? GetEnumName(payload, 10, SimpleCommodityCategories) : "",
|
||||
CompoundCommodityCategory = unionKey == 1 ? GetEnumName(payload, 11, CompoundCommodityCategories) : "",
|
||||
WeaponRange = unionKey == 31 ? GetEnumName(payload, 24, WeaponRanges) : "",
|
||||
|
|
@ -913,13 +977,19 @@ internal static class LegacyCatalogReader
|
|||
return payload.TryGetValue(key, out var value) && value is object?[] array ? array.Length : 0;
|
||||
}
|
||||
|
||||
private static string GetHardpointType(int unionKey, IReadOnlyDictionary<int, object?> payload)
|
||||
private static string GetHardpointType(
|
||||
int unionKey,
|
||||
IReadOnlyDictionary<int, object?> payload,
|
||||
LegacyCatalogLayout layout)
|
||||
{
|
||||
return unionKey switch
|
||||
{
|
||||
3 => "Hull",
|
||||
29 or 30 => "Tool",
|
||||
2 or 31 => GetEnumName(payload, 23, HardpointTypes),
|
||||
2 or 31 => GetEnumName(
|
||||
payload,
|
||||
layout == LegacyCatalogLayout.April2021 ? 20 : 23,
|
||||
HardpointTypes),
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ RequireTypedStatRecipeOperations(root);
|
|||
RequireTypedDaemonCommandPayloads(root);
|
||||
RequireUnityPublicRequestVocabulary(root);
|
||||
RequireCatalogSurfaceUsesManagedRuntimeCatalog(root);
|
||||
RequireDaemonLoadoutRarityAuthority(root);
|
||||
RequireDaemonVersePublication(root);
|
||||
RequireUnityRuntimeCatalogClientUsesManagedDocument(root);
|
||||
RequireAetheriaRuntimeVerseClientContract(root);
|
||||
|
|
@ -12998,7 +12999,12 @@ static void RequireCatalogSurfaceUsesManagedRuntimeCatalog(string root)
|
|||
"node.MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey)",
|
||||
"AetheriaRuntimeStateMapper.ToTradeValuePolicy(",
|
||||
"TryReadRectangularShape(columns, out var rectangularShape)",
|
||||
"ProjectStationSensorMount(ProjectDeployableWeapon(entry.ItemDefinition!))",
|
||||
"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",
|
||||
"await node.CatalogSurface().LatestAsync().ConfigureAwait(false);"
|
||||
};
|
||||
|
|
@ -13035,6 +13041,27 @@ static void RequireCatalogSurfaceUsesManagedRuntimeCatalog(string root)
|
|||
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");
|
||||
|
|
|
|||
BIN
GameData/Legacy/AetherDB.2021-04-14.msgpack
(Stored with Git LFS)
Normal file
BIN
GameData/Legacy/AetherDB.2021-04-14.msgpack
(Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -188,7 +188,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
IReadOnlyList<AetheriaRuntimeCurveKey> effectivenessCurveKeys,
|
||||
string simpleCommodityCategory,
|
||||
string compoundCommodityCategory,
|
||||
AetheriaRuntimeBehaviorValue? hullCapacity = null)
|
||||
AetheriaRuntimeBehaviorValue? hullCapacity = null,
|
||||
IReadOnlyList<string>? tags = null)
|
||||
{
|
||||
ItemKey = itemKey;
|
||||
Name = name;
|
||||
|
|
@ -238,6 +239,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
EffectivenessCurveKeys = effectivenessCurveKeys;
|
||||
SimpleCommodityCategory = simpleCommodityCategory;
|
||||
CompoundCommodityCategory = compoundCommodityCategory;
|
||||
Tags = tags ?? Array.Empty<string>();
|
||||
HullCapacity = hullCapacity ?? new AetheriaRuntimeBehaviorValue(
|
||||
"", "", 0, false, "", "",
|
||||
Array.Empty<AetheriaRuntimeBehaviorValue>(),
|
||||
|
|
@ -247,6 +249,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public string ItemKey { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Category { get; set; }
|
||||
public IReadOnlyList<string> Tags { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string ManufacturerKey { get; set; }
|
||||
public int Price { get; set; }
|
||||
|
|
|
|||
|
|
@ -699,7 +699,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
var description = ReadFieldString(ref reader, fields, 3);
|
||||
var mass = ReadFieldDouble(ref reader, fields, 4);
|
||||
var volume = ReadFieldDouble(ref reader, fields, 5);
|
||||
SkipField(ref reader, fields, 6);
|
||||
var tags = ReadFieldStringArray(ref reader, fields, 6);
|
||||
var manufacturerLegacyId = ReadFieldString(ref reader, fields, 7);
|
||||
var price = ReadFieldInt32(ref reader, fields, 8);
|
||||
var shapeWidth = ReadFieldInt32(ref reader, fields, 9);
|
||||
|
|
@ -795,7 +795,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
effectivenessCurveKeys,
|
||||
simpleCommodityCategory,
|
||||
compoundCommodityCategory,
|
||||
hullCapacity);
|
||||
hullCapacity,
|
||||
tags);
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeCorporation ReadCorporation(byte[] payload)
|
||||
|
|
|
|||
|
|
@ -953,9 +953,28 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
- Variant invariant: AetherDrive is not baseline propulsion. The canonical
|
||||
catalog contains the single 250,000-credit `Traction` drive with hardpoint
|
||||
type `AetherDrive`, and only the 7,500,000-credit modified `LonginusX` hull
|
||||
exposes that hardpoint. Its rotor RPM, coupling, torque curve, energy, heat,
|
||||
and thrust run only when that rare equipment is actually installed. Ordinary
|
||||
flight fixtures explicitly prove that no AetherDrive state is manufactured.
|
||||
exposes that hardpoint. The fingerprinted 2021-04-14 catalog restores the
|
||||
separate common `Longinus` row under its original GUID, including four
|
||||
directional-thruster mounts, and restores `Djinni`; `LonginusX` and every
|
||||
AetherDrive item carry typed `rarity:rare` catalog tags. The daemon applies a
|
||||
0.05 generation multiplier to those tags. Its rotor RPM, coupling, torque
|
||||
curve, energy, heat, and thrust run only when that rare equipment is actually
|
||||
installed. Ordinary flight fixtures explicitly prove that no AetherDrive
|
||||
state is manufactured.
|
||||
- Catalog recovery owner: `Aetheria.State.Import` alone turns the quarantined
|
||||
April 2021 MessagePack fossil into typed hull documents. Its allowed inputs
|
||||
are the fingerprinted historical blob and the explicit `April2021` field
|
||||
layout; its outputs are ordinary typed item documents. The blob, Unity
|
||||
database classes, and current fixture catalog are not runtime authorities.
|
||||
Missing-row recovery is limited to the historical `Longinus` and `Djinni`
|
||||
GUIDs plus missing `Thruster` equipment required to fill their authored
|
||||
mounts, so the supplemental catalog cannot overwrite live rows or silently
|
||||
revive unrelated obsolete content.
|
||||
- Generation owner: one daemon loadout generator owns one advancing mutable
|
||||
CultMath random stream. A readonly struct field is forbidden because it
|
||||
mutates a defensive copy and restarts every weighted draw. Price, allegiance,
|
||||
graph distance, footprint, and typed rarity are inputs; selected hull and
|
||||
immutable selection receipt are outputs. Eve and Unity only inspect them.
|
||||
- Derived state: Eve actuator rows, RPM gauges, plume/effect intensity, sound
|
||||
parameters, and interpolated transforms are presentation projections. They
|
||||
cannot write velocity, direction, heat, energy, or equipment state back.
|
||||
|
|
@ -976,10 +995,13 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
|
|||
unfunded no-motion/no-plume behavior, progressive hardware turning before
|
||||
weapon lock, autonomous navigation and cross-zone travel through Ymir, and
|
||||
isolated rare AetherDrive spool/force on a modified-hull fixture. Canonical
|
||||
catalog assertions lock the `Traction`/`LonginusX` rarity boundary.
|
||||
catalog assertions lock the restored common hull identities and the
|
||||
`Traction`/`LonginusX` rarity boundary; a 2,000-loadout deterministic witness
|
||||
proves rare candidates remain possible without becoming the default path.
|
||||
- Open parity: hardpoint transform directions, exact per-equipment behavior
|
||||
execution ordering, float32/CultMath parity against the fossil, production
|
||||
generation-frequency evidence, and generic native thrust/turn feedback still
|
||||
execution ordering, float32/CultMath parity against the fossil, live
|
||||
production-world generation-frequency evidence, and generic native
|
||||
thrust/turn feedback still
|
||||
require proof.
|
||||
|
||||
### Flight Post-Processing Contract
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity.
|
|||
|---|---|---|---|---|---|---|
|
||||
| Run/session state | `ServerShared`, `ActionGameManager` | Aetheria daemon/CultCache | provider/session advertisement | all clients | owner-exists; wired; projected; not proven | restart and multi-client convergence |
|
||||
| Entity identity/loadout | `ServerShared/Entity*` | daemon | typed entity and inventory surfaces | EveUnity/Electron | owner-exists; wired; projected; not proven | loadout round trip and reconnect |
|
||||
| Loadout generation | map/faction availability, item cost, hull hardpoints and role constraints | daemon using canonical galaxy, faction knowledge/economy and typed catalog | generated loadout plus provenance/availability facts | Eve inspection and refit surfaces | daemon now uses the fossil candidate filter and seeded weighted selection: manufacturer must be present and allied, ally weight decays by home-zone graph distance, price uses the authored exponent, and hardpoints require matching semantic type, rotated footprint containment, and controller role; the exact placement rotation used by hardpoint/free-space/cargo fitting now survives generated state, durable loadouts, runtime commits, viewport snapshots, cloning, and reconnect instead of disappearing after occupancy reservation; generation fills the largest compatible footprint before applying economic weighting, so small gear remains a valid large-mount fallback without displacing available station-scale gear; cargo/capacitor plus station docking equipment must fit remaining hull space; stations draw up to sixteen faction-available equipment rows and pack largest-first into the selected cargo bay's actual interior shape; ordinary ships begin without generated cargo, and the deleted scenario-string path can no longer silently invent non-catalog item identities; station cargo slots and receipts preserve the same canonical keys; zone generation derives one continuing deterministic RNG stream per availability corporation, so cross-faction calls cannot perturb another faction; every generated entity persists an immutable source/selection receipt that reaches Eve as `loadout.item` semantics; Terminus explicitly maps combat-role factions to catalog availability corporations | multi-zone generated-world parity and exact root-to-zone stream sequencing against the fossil |
|
||||
| Flight intent | `Ship`, `Thruster`, `AetherDrive`, input paths | daemon actuator simulation + Ymir integration | analog local helm/look/tractor plus actuator state | generic Eve input and scene lowerers | direct faction-speed velocity ownership is deleted; daemon persists local strafe/forward helm and desired look, allocates ordinary directional thruster banks by authored rotation, derives torque from hull/item placement, compensates lateral torque, gates execution through the shared energy ledger, deposits equipment-cell heat, publishes plume visibility and actuator state, applies hull drag, and gives resulting velocity/direction to Ymir. Autonomous tasks translate world travel into the same local helm axes and issue look commands instead of mutating heading. AetherDrive rotor/coupling simulation is additive and explicitly non-default: the canonical catalog has one expensive `Traction` drive and one compatible rare `LonginusX` modified hull, while ordinary smoke flight contains no drive behavior | hardpoint-transform direction parity, exact behavior execution ordering, CultMath/float32 parity harness, production loadout-frequency witness, generic thrust/turn feedback lowering, and live handling screenshots |
|
||||
| Loadout generation | map/faction availability, item cost, hull hardpoints and role constraints | daemon using canonical galaxy, faction knowledge/economy and typed catalog | generated loadout plus provenance/availability facts | Eve inspection and refit surfaces | daemon now uses the fossil candidate filter and seeded weighted selection: manufacturer must be present and allied, ally weight decays by home-zone graph distance, price uses the authored exponent, and hardpoints require matching semantic type, rotated footprint containment, and controller role; the April 2021 catalog fossil restores the distinct common `Longinus`, rare AetherDrive-compatible `LonginusX`, and `Djinni` rows with their original IDs, shapes, hardpoints, prices, drag, and prefabs, plus missing Thruster rows required to fill their authored mounts; typed `rarity:rare` tags apply a 0.05 generation multiplier to `LonginusX` and AetherDrive equipment; the generator owns one mutable CultMath stream, fixing the readonly-struct defensive-copy bug that restarted every weighted draw; placement rotation survives durable state and reconnect; generation fills the largest compatible footprint before economic weighting; cargo/capacitor plus station docking equipment must fit remaining hull space; stations pack faction-available inventory largest-first; ordinary ships begin without invented scenario cargo; every generated entity persists a source/selection receipt that reaches Eve as `loadout.item` semantics | multi-zone generated-world parity and exact root-to-zone stream sequencing against the fossil |
|
||||
| Flight intent | `Ship`, `Thruster`, `AetherDrive`, input paths | daemon actuator simulation + Ymir integration | analog local helm/look/tractor plus actuator state | generic Eve input and scene lowerers | direct faction-speed velocity ownership is deleted; daemon persists local strafe/forward helm and desired look, allocates ordinary directional thruster banks by authored rotation, derives torque from hull/item placement, compensates lateral torque, gates execution through the shared energy ledger, deposits equipment-cell heat, publishes plume visibility and actuator state, applies hull drag, and gives resulting velocity/direction to Ymir. Autonomous tasks translate world travel into the same local helm axes and issue look commands instead of mutating heading. AetherDrive rotor/coupling simulation is additive and explicitly non-default: common `Longinus` and `Djinni` hulls use ordinary thrusters; the rare `LonginusX` replaces those mounts with the single rare `Traction` AetherDrive path | hardpoint-transform direction parity, exact behavior execution ordering, CultMath/float32 parity harness, live production-frequency evidence, generic thrust/turn feedback lowering, and live handling screenshots |
|
||||
| Collision | Unity physics and entity handlers | Ymir | collision/result facts | visual interpolation only | Ymir.Core world stepping wired for entity bodies, radial fields and contacts; missing-owner negative proof passes | publish authoritative collision events and prove Unity cannot write transforms back |
|
||||
| Targeting/contacts | sensors + `ActionGameManager` | daemon | contact/target semantics | target HUD and indicators | simplified owner wired/projected; every authored instant lock behavior now evaluates on every daemon step independently of trigger/burst selection, so an idle weapon cannot retain a stale completed lock; acquisition and decay derive from facing, elapsed time, and observer-local contact confidence instead of granting an immediate perfect lock; target changes and invalid targets clear the daemon state; `weapon.lock.started`, `weapon.lock.acquired`, and `weapon.lock.lost` publish stable one-time chronology with target, weapon identity, reason, and prior/current progress through the generic feedback stream; the provider cockpit and generic combat reticle consume the same continuous selected-target lock without client smoothing or reconstruction | angular loss fixture, visibility/nearest/explicit targeting cases, provider audio/effect roles, and authored reticle parity |
|
||||
| Projectile flight | weapon/projectile classes | Eve presentation for ordinary tracers; Ymir for persistent physical payloads | receipt trajectory/effect facts or persistent payload state | native projectile visual | ordinary instant, charged, and constant execution creates no projectile body; each weapon state owns a stable shot sequence and receipts carry origin, endpoint, nominal duration, and effect identity for Eve presentation; `SpawnProjectile` and its alternate writer are deleted; Ymir projectile contacts are feedback-only for explicitly persisted payload rows and cannot write damage; Ymir.Core remains embedded for world integration, radial fields, circle contacts, pickups, and genuinely persistent/interceptable payloads | split/rename the remaining physical-payload interface and remove ordinary projectile terminology from runtime contracts |
|
||||
|
|
@ -35,10 +35,10 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity.
|
|||
| Render channels | Unity layers/culling masks | provider surface policy + runtime asset variant | semantic channel exclusions | EveUnity native layer mapping | wired, exclusive, and lowered: the pilot surface excludes the semantic `map` channel; the Unity asset variant maps `map` to its authored native layer; EveUnity subtracts that layer from the configured camera mask; portable state contains no Unity layer or mask; provider conversion preserves the source icon shader's unlit lighting model; the released witness proves the pilot camera excludes map glyphs while the map camera retains readable white glyphs without a scene light | preserve the channel proof while world presentation and transport continue to evolve |
|
||||
| Minimap/tactical map | `ZoneRenderer`, map cameras | daemon world facts | explicit map projections | Unity and Electron | facts fragmented; generic lowerers absent | same state in both runtimes |
|
||||
| Celestial world | `ZoneRenderer`, body/asteroid prefab scripts | daemon render projection plus provider assets | ordinary generic Eve entity SoA rows | EveUnity generic entity/prefab lowerer | wired, exclusive, projected, and lowered: daemon orbit and terrain queries emit stable presentation-only identities for suns, planets, gas giants, and live asteroid instances; provider packaging extracts only each advertised visual child and strips fossil scripts/physics; released EveUnity `0.3.51` applies semantic scale around provider-authored local scale and contains no celestial or Aetheria branch. The released warm witness records eleven bodies and eighteen asteroids with provider refs and enabled renderers, including seven celestial instances intersecting the pilot frustum | body material/light readability, close-range composition matching the fossil, asteroid variation, and Electron lowering |
|
||||
| Fields/fog/gravity | zone renderer and shaders | daemon/plugin semantics | Fields plugin documents | EveUnity fields lowerer | daemon publishes canonical gravity/fog splat layers through `field.volume3d`; Eve Fields `0.2.3` owns portable `PowerPulse` and `AnimatedRadialCosine` semantics; Aetheria emits the fossil global animated simplex/cellular/ambient stack, body-owned radial waves, and separate negative gameplay `gravity.height` and positive visual `fog.surface_height`; the shared camera-relative lattice spans 1536 world units as 256 six-unit Stardust cells, rasterizes gravity at eight texels per cell into 2048 texels, and snaps the viewport by one six-unit cell; daemon entities follow fossil terrain height plus hull grid offset after Ymir XZ motion; released EveUnity `0.3.53` lowers the authored 2048/256/512 layer topology, provider shader ABI, pre-postprocess URP pass, authoritative flow time, explicit camera matrix, and float32 packed-output diagnostics without provider code. The provider bundle runs the actual Stardust compute shader across X, Y, and diagonal one-cell remaps and proves 705 overlapping world cells remain bit-identical after buffer reassignment. The released witness proves the same snapped grid drives 65,536 rendered particles while the map camera remains isolated. The flight post-process bundle gate now loads and verifies serialized ACES, exposure/contrast, bloom, vignette, and film-grain components rather than accepting an empty profile | restore readable patch structure, gravity waves, surrounding world geometry, and final luminance/composition parity; visual parity remains explicitly open |
|
||||
| Fields/fog/gravity | zone renderer and shaders | daemon/plugin semantics | Fields plugin documents | EveUnity fields lowerer | daemon publishes canonical gravity/fog splat layers through `field.volume3d`; Eve Fields `0.2.3` owns portable `PowerPulse` and `AnimatedRadialCosine` semantics; Aetheria emits the fossil global animated simplex/cellular/ambient stack, body-owned radial waves, and separate negative gameplay `gravity.height` and positive visual `fog.surface_height`; the shared camera-relative lattice spans 1536 world units as 256 six-unit Stardust cells, rasterizes gravity at eight texels per cell into 2048 texels, and snaps the viewport by one six-unit cell; daemon entities follow fossil terrain height plus hull grid offset after Ymir XZ motion; released EveUnity `0.3.55` lowers the authored topology, provider shader ABI, pre-postprocess URP pass, authoritative flow time, explicit camera matrix, and float32 packed-output diagnostics without provider code. The provider bundle runs the actual Stardust compute shader across X, Y, and diagonal one-cell remaps and proves 705 overlapping world cells remain bit-identical after buffer reassignment. The released warm witness proves the same snapped grid drives 65,536 rendered particles while the map camera remains isolated. The flight post-process bundle gate verifies serialized ACES, exposure/contrast, bloom, vignette, and film-grain components | restore readable patch structure, gravity waves, surrounding world geometry, and final luminance/composition parity; visual parity remains explicitly open |
|
||||
| HUD/schematic | ARPG HUD and `SchematicDisplay` | Eve composition from daemon facts | semantic status/components | EveUnity UI | provider-authored transparent cockpit is wired, projected, and lowered through generic UI Toolkit panes, metrics, and progress components for hull, shield, capacitor, temperature, cargo, weapon cooldown/groups, selected target, target lock, target shield, and target hull; the original armor/equipment schematic and transient warning choreography remain reference-only | schematic topology, warning timelines, dynamic action state, and composited live capture |
|
||||
| Hit/lock/warnings | `ActionGameManager` feedback | daemon event facts | transient feedback stream | VFX/UI/audio | bounded deduplicated game-event ledger and Eve `feedback.stream` wired for pickup outcomes, projectile launch/impact, entity damage, alive-to-dead destruction, authoritative weapon reload chronology, lock start/acquired/lost chronology, and episode-deduplicated `weapon.fire.refused` facts for insufficient energy or missing ammunition; every lock transition carries authored weapon identity, target, reason, and prior/current progress, while reconnect priming prevents retained events from replaying as new feedback | add thermal and docking event families, negative transaction fixtures, provider lock audio/effect roles, and broader runtime effect consumption |
|
||||
| Assets | `Assets/Resources` | Aetheria provider | asset catalog + CultMesh CDN | runtime cache/load | provider manifests name deterministic bundle-internal paths; the authoring build derives presentation-only prefabs, strips missing and non-Eve `MonoBehaviour` components, externalizes embedded tractor and shield event visuals, preserves particle/unlit/lit source semantics during URP conversion, verifies saved prefabs and the loaded bundle including native program/profile ABI, and publishes the immutable roughly 55 MB bundle through CultMesh; the released cold profile starts empty, receives and verifies the 55,539,565-byte body through `cultmesh.content.v1`, atomically promotes the exact hash with no partial, negotiates `SharedFileMapping` over that transfer-owned path, and lowers rendered provider identity without another process-sized bundle allocation; the warm profile reuses the committed body | provider-to-client network zero-copy, shared-memory delivery, and GPU zero-copy remain open; local verified-file mapping is proven |
|
||||
| Assets | `Assets/Resources` | Aetheria provider | asset catalog + CultMesh CDN | runtime cache/load | provider manifests name deterministic bundle-internal paths and now project each typed hull prefab through the same asset key in Eve scene and SoA identity state; the authoring build derives presentation-only prefabs, strips non-Eve gameplay scripts, externalizes embedded effects, preserves source material semantics, and verifies the loaded bundle ABI; released EveUnity `0.3.55` resolves Unity's lowercase bundle names against authored-case advertised keys and the warm cache witness lowers the verified provider body through local file mapping | cold CDN transfer is failing: the bundle still times out while transported through batched snapshot records instead of mapped/network body transport; provider-to-client network zero-copy, shared-memory delivery, and GPU zero-copy remain open |
|
||||
| Save/resume | original persistence paths | daemon/CultCache | published current state | clients rehydrate | owner-exists; not parity-proven | mid-loop restart |
|
||||
| Terminus loop | original game composition | daemon/Ymir/Eve | pilot surface | Aetheria.Unity | live minimum-loop proof: generic Unity discovers the provider, lowers the pilot world/assets, single-steps dock and undock while paused, buys daemon-priced station stock, single-steps Ymir salvage collection, resumes, moves, targets, fires, and presents authoritative receipts/shot outcome; the released full-session profile starts with no pickup, destroys a deterministic target, observes its canonical cargo drop, tractors it into a Ymir Begin contact, and proves exactly one `ymir-fact:` collection receipt plus cargo `0` to `1` | richer trade content, docking transitions, and sustained combat encounters |
|
||||
| Starbridge | original/shared architecture intent | one daemon session | pilot + commander surfaces | Unity + Electron | commander task board and autonomous explore execution wired; Electron Eve lowering and split-target proof missing | commander orders plus four pilots and autonomous workers |
|
||||
|
|
|
|||
Loading…
Reference in a new issue