Move the ServerShared data model onto CultLib's CultCache

Cut 8b. Aetheria.Shared builds headless against GameCult.Caching and
GameCult.Caching.MessagePack at the pinned CultLib; the pin guard also
checks the Caching project exists.

Documents: [CultDocument("aetheria.<type>", "1")] with [MessagePackObject]
and integer [Key]s on the Q7 concrete types (the eight ItemData leaves,
Faction, FactionProductData, PersonalityAttribute, NameFile, OrbitData, the
four BodyData leaves, SavedZone, SavedGame, PlayerSettings, InputLayout).
SavedGame and PlayerSettings are [CultGlobal]; Name members are [CultName].
ItemData, CraftedItemData, EquippableItemData, BodyData and AgentTask lose
[MessagePackObject]; BodyData and AgentTask lose their [Union]s. InputLayout
moves into ServerShared so AetheriaStores can route it.

References: Guid and DatabaseLink<T> members become CultRecordRef<T>;
Guid-keyed maps become ref-keyed with [CultReference(..., many: true)];
ZonePack holds refs to orbit and body records. Runtime maps (Zone orbits,
bodies and belts, cargo ItemsOfType, agent targets) key by CultRecordKey.
Identity is the record key: ID comparisons become reference or key
comparisons. StatModifierData.RequireBehavior is a simple type name.

AetheriaStores.Open composes the catalog (read-only unless writable), run
and player stores, one home per type. CultRecordRefs carries IsSet, a
null-safe Get(ref), RefOf (key from TryGetHandle) and Upsert.
ItemManager.GetData is the one item resolution path. MathResolver is
declared by [assembly: CultCacheFormatterResolver].

Inspector attributes: text, range and asset path map onto CultLib's
CultInspector attributes (asset path only on string members); every
replaced Aetheria attribute keeps [Inspectable] for the runtime panel.

Builds: Aetheria.Shared. Does not build: AetherDb and the Unity project,
which follow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-14 19:00:42 +02:00
commit a2b54889b4
50 changed files with 537 additions and 542 deletions

View file

@ -1,4 +1,4 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
@ -43,6 +43,7 @@ public static class ZoneGenerator
bool isTutorial = false)
{
var pack = new ZonePack();
var cache = itemManager.ItemData;
var random = new Random(unchecked((uint) galaxyZone.Name.GetHashCode()) ^ (uint) pcg3d(galaxyZone.Position).x);
@ -50,7 +51,7 @@ public static class ZoneGenerator
pack.Radius = zoneSettings.ZoneRadius.Evaluate(density);
pack.Mass = zoneSettings.ZoneMass.Evaluate(density);
var targetSubzoneCount = zoneSettings.SubZoneCount.Evaluate(density);
//Debug.Log($"Generating zone at position {zone.Position} with radius {zoneRadius} and mass {zoneMass}");
var planets = new List<GeneratorPlanet>();
@ -58,10 +59,10 @@ public static class ZoneGenerator
{
var zoneBoundary = new Circle(float2.zero, pack.Radius * zoneSettings.ZoneBoundaryRadius);
float boundaryTangentRadius(float2 point) => -zoneBoundary.DistanceTo(point);
var occupiedAreas = new List<Circle>();
float tangentRadius(float2 point) => min(boundaryTangentRadius(point), occupiedAreas.Min(circle => circle.DistanceTo(point)));
var startPosition = random.NextFloat(pack.Radius * .25f, pack.Radius * .5f) * random.NextFloat2Direction();
occupiedAreas.Add(new Circle(startPosition, boundaryTangentRadius(startPosition)));
@ -91,52 +92,29 @@ public static class ZoneGenerator
}
else
planets.AddRange(GenerateEntities(zoneSettings, ref random, pack.Mass, pack.Radius, float2.zero));
// Create collections to map between zone generator output and database entries
// Every orbit is a record; parents link to records, so each orbit holds its key before any child names it
var orbitMap = new Dictionary<GeneratorPlanet, OrbitData>();
var orbitInverseMap = new Dictionary<OrbitData, GeneratorPlanet>();
// Create orbit database entries
pack.Orbits = planets.Select(planet =>
{
var data = new OrbitData
foreach (var planet in planets)
cache.Upsert(orbitMap[planet] = new OrbitData
{
FixedPosition = planet.FixedPosition,
Distance = planet.Distance,
//Period = planet.Period,
Phase = planet.Phase
};
orbitMap[planet] = data;
orbitInverseMap[data] = planet;
return data;
}).ToList();
});
foreach (var planet in planets.Where(p => p.Parent != null))
{
orbitMap[planet].Parent = cache.RefOf(orbitMap[planet.Parent]);
cache.Upsert(orbitMap[planet]);
}
pack.Orbits = planets.Select(planet => cache.RefOf(orbitMap[planet])).ToList();
// Link OrbitData parents to database GUIDs
foreach (var data in pack.Orbits)
data.Parent = orbitInverseMap[data].Parent != null
? orbitMap[orbitInverseMap[data].Parent].ID
: Guid.Empty;
// Cache resource densities
// var resourceMaps = mapLayers.Values
// .ToDictionary(m => m.ID, m => m.Evaluate(zone.Position, settings.ShapeSettings));
pack.Planets = planets.Where(p=>!p.Empty).Select(planet =>
{
// Dictionary<Guid, float> planetResources = new Dictionary<Guid, float>();
BodyType bodyType = planet.Belt ? BodyType.Asteroid :
planet.Mass > zoneSettings.SunMass ? BodyType.Sun :
planet.Mass > zoneSettings.GasGiantMass ? BodyType.GasGiant :
planet.Mass > zoneSettings.PlanetMass ? BodyType.Planet : BodyType.Planetoid;
// foreach (var r in resources)
// {
// if ((bodyType & r.ResourceBodyType) != 0)
// {
// float quantity = ResourceValue(ref random, settings, r, r.ResourceDensity.Aggregate(1f, (m, rdm) => m * resourceMaps[rdm]));
// if (r.Floor < quantity) planetResources.Add(r.ID, quantity);
// }
// }
BodyData planetData;
switch (bodyType)
@ -159,12 +137,10 @@ public static class ZoneGenerator
}
planetData.Mass = planet.Mass;
planetData.Orbit = orbitMap[planet].ID;
// planetData.Resources = planetResources;
planetData.Name = planetData.ID.ToString().Substring(0, 8);
planetData.Orbit = cache.RefOf(orbitMap[planet]);
if (planetData is AsteroidBeltData beltData)
{
beltData.Asteroids =
beltData.Asteroids =
Enumerable.Range(0, (int) zoneSettings.AsteroidCount.Evaluate(beltData.Mass * orbitMap[planet].Distance))
.Select(_ => new Asteroid
{
@ -197,12 +173,12 @@ public static class ZoneGenerator
float primary = random.NextFloat();
float right = frac(primary + zoneSettings.GasGiantBandColorSeparation);
float left = frac(primary + 1 - zoneSettings.GasGiantBandColorSeparation);
// Create n time keys from 0 to 1
var bandCount = (int) (zoneSettings.GasGiantBandCount.Evaluate(random.NextFloat()) + .5f);
var times = Enumerable.Range(0, bandCount)
.Select(i => (float) i / (bandCount-1));
// Each band has a chance of being either the primary or one of the adjacent hues
// Saturation and Value are random with curves applied
gas.Colors = times
@ -211,7 +187,7 @@ public static class ZoneGenerator
zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()),
zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()))),time))
.ToArray();
gas.FirstOffsetDomainRotationSpeed = 0;
}
gas.AlbedoRotationSpeed = -3;
@ -219,7 +195,10 @@ public static class ZoneGenerator
gas.SecondOffsetRotationSpeed = 10;
gas.SecondOffsetDomainRotationSpeed = -25;
}
return planetData;
// A body is named for the first eight characters of the key the cache mints for it
planetData.Name = cache.Upsert(planetData).Key.Value.Substring(0, 8);
return cache.Upsert(planetData);
}).ToList();
var nearestFaction = galaxy.Factions.MinBy(f => galaxy.HomeZones[f].Distance[galaxyZone]);
@ -253,30 +232,28 @@ public static class ZoneGenerator
{
var lagrangeOrbit = new OrbitData
{
ID = Guid.NewGuid(),
Parent = baseOrbit.Parent,
Distance = baseOrbit.Distance,
Phase = baseOrbit.Phase + PI / 3 * sign(random.NextFloat() - .5f)
};
pack.Orbits.Add(lagrangeOrbit);
pack.Orbits.Add(cache.Upsert(lagrangeOrbit));
return lagrangeOrbit;
}
void PlaceTurret(OrbitData orbit, LoadoutGenerator loadoutGenerator, int distanceMultiplier)
{
var phase = 20f * distanceMultiplier / orbit.Distance;
var turretOrbit = new OrbitData
{
ID = Guid.NewGuid(),
Parent = orbit.Parent,
Distance = orbit.Distance,
Phase = orbit.Phase + phase
};
pack.Orbits.Add(turretOrbit);
pack.Orbits.Add(cache.Upsert(turretOrbit));
var turret = loadoutGenerator.GenerateTurretLoadout();
if (turret == null) return;
turret.Orbit = turretOrbit.ID;
turret.Orbit = cache.RefOf(turretOrbit);
pack.Entities.Add(turret);
}
@ -301,7 +278,7 @@ public static class ZoneGenerator
itemManager.Log($"Story station for {story.Faction?.Name} could not be generated; its story has nowhere to live!");
continue;
}
station.Orbit = lagrangeOrbit.ID;
station.Orbit = cache.RefOf(lagrangeOrbit);
station.SecurityLevel = story.Security;
station.SecurityRadius = pack.Radius;
station.Story = i;
@ -318,7 +295,7 @@ public static class ZoneGenerator
var lagrangeOrbit = CreateLagrangeOrbit(orbit);
var station = GetLoadoutGenerator(nearestFaction).GenerateStationLoadout();
if (station == null) continue;
station.Orbit = lagrangeOrbit.ID;
station.Orbit = cache.RefOf(lagrangeOrbit);
station.SecurityLevel = security;
station.SecurityRadius = pack.Radius;
pack.Entities.Add(station);
@ -336,7 +313,7 @@ public static class ZoneGenerator
return pack;
}
// static float ResourceValue(ref Random random, ZoneGenerationSettings settings, SimpleCommodityData resource, float density)
// {
// return random.NextPowerDistribution(resource.Minimum, resource.Maximum, resource.Exponent,
@ -357,20 +334,20 @@ public static class ZoneGenerator
// There is some chance of generating a rosette or binary system
// Probabilities which are fixed for the entire galaxy are in GlobalData, contained in the GameContext
var rosette = random.NextFloat() < settings.RosetteProbability;
if (rosette)
{
// Create a rosette with a number of vertices between 2 and 9 inclusive
root.ExpandRosette(ref random, (int)(random.NextFloat(1, 5) + random.NextFloat(1, 5)));
// Create a small number of less massive "captured" planets orbiting past the rosette
root.ExpandSolar(
ref random,
count: (int)(random.NextFloat(1, 3) * random.NextFloat(1, 2)),
massMulMin: .6f,
massMulMax: .8f,
distMulMin: 1.25f,
distMulMax: 1.75f,
count: (int)(random.NextFloat(1, 3) * random.NextFloat(1, 2)),
massMulMin: .6f,
massMulMax: .8f,
distMulMin: 1.25f,
distMulMax: 1.75f,
jupiterJump: 1,
massFraction: .1f);
@ -381,9 +358,9 @@ public static class ZoneGenerator
// Give each child in the rosette its own mini solar system
p.ExpandSolar(
ref random,
count: (int) (random.NextFloat(1, 3 * m) + random.NextFloat(1, 3 * m)),
massMulMin: 0.75f,
massMulMax: 2.5f,
count: (int) (random.NextFloat(1, 3 * m) + random.NextFloat(1, 3 * m)),
massMulMin: 0.75f,
massMulMax: 2.5f,
distMulMin: 1 + m * .25f,
distMulMax: 1.05f + m * .5f,
jupiterJump: random.NextFloat() * random.NextFloat() * 10 + 1,
@ -396,9 +373,9 @@ public static class ZoneGenerator
// Create a regular old boring solar system
root.ExpandSolar(
ref random,
count: random.NextInt(5, 15),
massMulMin: 0.75f,
massMulMax: 2.5f,
count: random.NextInt(5, 15),
massMulMin: 0.75f,
massMulMax: 2.5f,
distMulMin: 1.1f,
distMulMax: 1.25f,
jupiterJump: random.NextFloat() * random.NextFloat() * 10 + 1,
@ -412,13 +389,13 @@ public static class ZoneGenerator
{
// Get all children that are above the satellite creation mass floor and not rosette members
var satelliteCandidates = rosette
? root.AllPlanets().Where(p =>
p != root &&
p.Parent != root &&
? root.AllPlanets().Where(p =>
p != root &&
p.Parent != root &&
p.Mass > settings.SatelliteCreationMassFloor &&
!alreadyExpanded.Contains(p))
: root.AllPlanets().Where(p =>
p != root &&
: root.AllPlanets().Where(p =>
p != root &&
p.Mass > settings.SatelliteCreationMassFloor &&
!alreadyExpanded.Contains(p));
@ -455,7 +432,7 @@ public static class ZoneGenerator
var beltCandidates = rosette
? root.AllPlanets().Where(p => p != root && p.Parent != root && p.Mass < settings.BeltMassCeiling && !binaries.Contains(p) && p.Children.Count == 0)
: root.AllPlanets().Where(p => p != root && p.Mass < settings.BeltMassCeiling && !binaries.Contains(p) && p.Children.Count == 0);
foreach(var planet in beltCandidates.Reverse())
if (random.NextFloat() < settings.BeltProbability && !planet.Parent.Children.Any(p=>p.Belt))
planet.Belt = true;
@ -498,29 +475,29 @@ public class GeneratorPlanet
public void ExpandRosette(ref Random random, int vertices)
{
//Debug.Log("Expanding Rosette");
// Rosette children replace the parent, parent orbital node is left empty
Empty = true;
// Masses in a rosette alternate, so every sequential pair has the same shared mass
var sharedMass = Mass / vertices * 2;
// Proportion of the masses of sequential pairs is random, but only for even vertex counts
var proportion = vertices % 2 == 0 ? random.NextFloat(.5f,.95f) : .5f;
// Place children at a fixed distance in the center of the range
var dist = (ChildDistanceMinimum + ChildDistanceMaximum) / 2;
// Position of first child
var p0 = new float2(0, dist);
// Position of second child
var p1 = OrbitData.Evaluate(1.0f / vertices) * dist;
// Maximum child distance is half the distance to the neighbor minus the neighbor's radius
var p0ChildDist = (distance(p0, p1) * proportion - Settings.PlanetSafetyRadius.Evaluate(sharedMass * (1 - proportion))) * .75f;
var p1ChildDist = (distance(p0, p1) * (1 - proportion) - Settings.PlanetSafetyRadius.Evaluate(sharedMass * proportion)) * .75f;
for (int i = 0; i < vertices; i++)
{
var child = new GeneratorPlanet
@ -547,14 +524,14 @@ public class GeneratorPlanet
// Expansion is impossible when child space is full
if (count == 0 || ChildDistanceMaximum < ChildDistanceMinimum)
return;
var masses = new float[count];
var distances = new float[count];
// Accumulate total mass
// Accumulate total mass
// Initialize first mass and distance, actual number doesn't matter since total mass will be divided proportionally
float massTotal = distances[0] = masses[0] = 1;
// Masses and distances multiply from one planet to the next
// Mass typically increases exponentially as you go further out
// jupiterJump is an additional mass multiplier applied after half of the planets
@ -580,7 +557,7 @@ public class GeneratorPlanet
distances[i] = lerp(ChildDistanceMinimum, ChildDistanceMaximum,
(oldDistances[i] - oldDistances[0]) / (oldDistances[count - 1] - oldDistances[0])) + Settings.PlanetSafetyRadius.Evaluate(masses[i]);
}
for (var i = 0; i < count; i++)
{
// Only instantiate children above the mass floor
@ -606,4 +583,4 @@ public class GeneratorPlanet
}
}
}
}
}