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>
2026-09-14 19:00:42 +02:00
/ * This Source Code Form is subject to the terms of the Mozilla Public
2020-12-22 03:13:43 +00:00
* 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/. */
using System ;
2020-03-22 19:05:26 +00:00
using System.Collections ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
2026-08-10 14:57:50 +01:00
using MessagePack ;
2020-12-01 00:16:11 +00:00
using UniRx ;
2026-09-14 17:39:12 +02:00
using CultMath ;
using static CultMath . math ;
using float2 = CultMath . float2 ;
using Random = CultMath . Random ;
2020-03-22 19:05:26 +00:00
2021-03-18 00:36:34 +00:00
public static class ZoneGenerator
2020-03-22 19:05:26 +00:00
{
2021-03-08 22:58:37 +00:00
private class Circle
{
public float2 Center ;
public float Radius ;
2026-08-10 14:57:50 +01:00
public float Area = > PI * Radius * Radius ;
2021-03-08 22:58:37 +00:00
public Circle ( float2 center , float radius )
{
Center = center ;
Radius = radius ;
}
2026-08-10 14:57:50 +01:00
public float DistanceTo ( float2 point ) = > length ( point - Center ) - Radius ;
public float DistanceTo ( Circle other ) = > length ( other . Center - Center ) - Radius - other . Radius ;
2021-03-08 22:58:37 +00:00
}
private const int MaximumPlacementSamples = 32 ;
2021-03-15 11:40:46 +00:00
2026-08-10 14:57:50 +01:00
public static ZonePack GenerateZone (
2021-03-18 00:36:34 +00:00
ItemManager itemManager ,
ZoneGenerationSettings zoneSettings ,
2021-05-14 03:03:28 +01:00
Galaxy galaxy ,
GalaxyZone galaxyZone ,
2021-04-22 02:53:06 +01:00
bool isTutorial = false )
2021-03-18 00:36:34 +00:00
{
2026-08-10 14:57:50 +01:00
var pack = new ZonePack ( ) ;
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>
2026-09-14 19:00:42 +02:00
var cache = itemManager . ItemData ;
2021-03-18 00:36:34 +00:00
2026-09-14 22:09:37 +02:00
var random = new Random ( galaxyZone . Name . StableHash ( ) ^ ( uint ) pcg3d ( galaxyZone . Position ) . x ) ;
2021-03-18 00:36:34 +00:00
2026-08-10 14:57:50 +01:00
var density = saturate ( galaxy . Background . CloudDensity ( galaxyZone . Position ) / 2 ) ;
pack . Radius = zoneSettings . ZoneRadius . Evaluate ( density ) ;
pack . Mass = zoneSettings . ZoneMass . Evaluate ( density ) ;
2021-03-18 00:36:34 +00:00
var targetSubzoneCount = zoneSettings . SubZoneCount . Evaluate ( density ) ;
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>
2026-09-14 19:00:42 +02:00
2020-03-30 17:54:54 +01:00
//Debug.Log($"Generating zone at position {zone.Position} with radius {zoneRadius} and mass {zoneMass}");
2021-03-08 22:58:37 +00:00
var planets = new List < GeneratorPlanet > ( ) ;
if ( targetSubzoneCount > 1 )
{
2026-08-10 14:57:50 +01:00
var zoneBoundary = new Circle ( float2 . zero , pack . Radius * zoneSettings . ZoneBoundaryRadius ) ;
2021-03-08 22:58:37 +00:00
float boundaryTangentRadius ( float2 point ) = > - zoneBoundary . DistanceTo ( point ) ;
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>
2026-09-14 19:00:42 +02:00
2021-03-08 22:58:37 +00:00
var occupiedAreas = new List < Circle > ( ) ;
2026-08-10 14:57:50 +01:00
float tangentRadius ( float2 point ) = > min ( boundaryTangentRadius ( point ) , occupiedAreas . Min ( circle = > circle . DistanceTo ( point ) ) ) ;
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>
2026-09-14 19:00:42 +02:00
2026-08-10 14:57:50 +01:00
var startPosition = random . NextFloat ( pack . Radius * . 25f , pack . Radius * . 5f ) * random . NextFloat2Direction ( ) ;
2021-03-08 22:58:37 +00:00
occupiedAreas . Add ( new Circle ( startPosition , boundaryTangentRadius ( startPosition ) ) ) ;
int samples = 0 ;
while ( occupiedAreas . Count < targetSubzoneCount & & samples < MaximumPlacementSamples )
{
samples = 0 ;
for ( int i = 0 ; i < MaximumPlacementSamples ; i + + )
{
2026-08-10 14:57:50 +01:00
var samplePos = random . NextFloat2 ( - pack . Radius , pack . Radius ) ;
2021-03-08 22:58:37 +00:00
var rad = tangentRadius ( samplePos ) ;
if ( rad > 0 )
{
occupiedAreas . Add ( new Circle ( samplePos , rad ) ) ;
break ;
}
samples + + ;
}
}
var totalArea = occupiedAreas . Sum ( c = > c . Area ) ;
foreach ( var c in occupiedAreas )
{
2026-08-10 14:57:50 +01:00
planets . AddRange ( GenerateEntities ( zoneSettings , ref random , c . Area / totalArea * pack . Mass , c . Radius , c . Center ) ) ;
2021-03-08 22:58:37 +00:00
}
}
else
2026-08-10 14:57:50 +01:00
planets . AddRange ( GenerateEntities ( zoneSettings , ref random , pack . Mass , pack . Radius , float2 . zero ) ) ;
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>
2026-09-14 19:00:42 +02:00
// Every orbit is a record; parents link to records, so each orbit holds its key before any child names it
2026-08-10 14:57:50 +01:00
var orbitMap = new Dictionary < GeneratorPlanet , OrbitData > ( ) ;
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>
2026-09-14 19:00:42 +02:00
foreach ( var planet in planets )
cache . Upsert ( orbitMap [ planet ] = new OrbitData
2020-03-30 03:11:57 +01:00
{
2021-03-08 22:58:37 +00:00
FixedPosition = planet . FixedPosition ,
2025-07-18 00:21:33 +01:00
Distance = planet . Distance ,
2020-11-27 22:47:33 +00:00
Phase = planet . Phase
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>
2026-09-14 19:00:42 +02:00
} ) ;
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 ( ) ;
2020-03-30 03:11:57 +01:00
2026-08-10 14:57:50 +01:00
pack . Planets = planets . Where ( p = > ! p . Empty ) . Select ( planet = >
2020-03-30 03:11:57 +01:00
{
2020-11-27 22:47:33 +00:00
BodyType bodyType = planet . Belt ? BodyType . Asteroid :
2021-03-18 00:36:34 +00:00
planet . Mass > zoneSettings . SunMass ? BodyType . Sun :
planet . Mass > zoneSettings . GasGiantMass ? BodyType . GasGiant :
planet . Mass > zoneSettings . PlanetMass ? BodyType . Planet : BodyType . Planetoid ;
2020-05-02 01:30:33 +01:00
2026-08-10 14:57:50 +01:00
BodyData planetData ;
2020-11-27 22:47:33 +00:00
switch ( bodyType )
{
case BodyType . Asteroid :
2026-08-10 14:57:50 +01:00
planetData = new AsteroidBeltData ( ) ;
2020-11-27 22:47:33 +00:00
break ;
case BodyType . Planetoid :
case BodyType . Planet :
2026-08-10 14:57:50 +01:00
planetData = new PlanetData ( ) ;
2020-11-27 22:47:33 +00:00
break ;
case BodyType . GasGiant :
2026-08-10 14:57:50 +01:00
planetData = new GasGiantData ( ) ;
2020-11-27 22:47:33 +00:00
break ;
case BodyType . Sun :
2026-08-10 14:57:50 +01:00
planetData = new SunData ( ) ;
2020-11-27 22:47:33 +00:00
break ;
default :
throw new ArgumentOutOfRangeException ( ) ;
}
2025-07-18 00:21:33 +01:00
planetData . Mass = planet . Mass ;
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>
2026-09-14 19:00:42 +02:00
planetData . Orbit = cache . RefOf ( orbitMap [ planet ] ) ;
2026-08-10 14:57:50 +01:00
if ( planetData is AsteroidBeltData beltData )
2020-05-02 01:30:33 +01:00
{
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>
2026-09-14 19:00:42 +02:00
beltData . Asteroids =
2025-07-18 00:21:33 +01:00
Enumerable . Range ( 0 , ( int ) zoneSettings . AsteroidCount . Evaluate ( beltData . Mass * orbitMap [ planet ] . Distance ) )
2020-05-28 04:50:40 +01:00
. Select ( _ = > new Asteroid
{
2025-07-18 00:21:33 +01:00
Distance = orbitMap [ planet ] . Distance + random . NextFloat ( ) * ( random . NextFloat ( ) - . 5f ) * zoneSettings . AsteroidBeltWidth . Evaluate ( orbitMap [ planet ] . Distance ) ,
2020-12-01 00:16:11 +00:00
Phase = random . NextFloat ( ) ,
Size = random . NextFloat ( ) ,
2021-03-18 00:36:34 +00:00
RotationSpeed = zoneSettings . AsteroidRotationSpeed . Evaluate ( random . NextFloat ( ) )
2020-12-01 00:16:11 +00:00
} )
//.OrderByDescending(a=>a.Size)
. ToArray ( ) ;
2020-05-02 01:30:33 +01:00
}
2026-08-10 14:57:50 +01:00
else if ( planetData is GasGiantData gas )
2020-11-27 22:47:33 +00:00
{
2026-08-10 14:57:50 +01:00
if ( gas is SunData sun )
{
2020-12-01 20:38:28 +00:00
float primary = random . NextFloat ( ) ;
2026-08-10 14:57:50 +01:00
float secondary = frac ( primary + 1 + zoneSettings . SunSecondaryColorDistance * ( random . NextFloat ( ) > . 5 ? 1 : - 1 ) ) ;
2025-07-18 00:21:33 +01:00
gas . Colors = new [ ]
2020-12-01 20:38:28 +00:00
{
2026-08-10 14:57:50 +01:00
float4 ( ColorMath . HsvToRgb ( float3 ( primary , zoneSettings . SunColorSaturation , . 5f ) ) , 0 ) ,
float4 ( ColorMath . HsvToRgb ( float3 ( secondary , zoneSettings . SunColorSaturation , 1 ) ) , 1 )
2020-12-01 20:38:28 +00:00
} ;
2026-08-10 14:57:50 +01:00
sun . FogTintColor = ColorMath . HsvToRgb ( float3 ( primary , zoneSettings . SunFogTintSaturation , 1 ) ) ;
sun . LightColor = ColorMath . HsvToRgb ( float3 ( primary , zoneSettings . SunLightSaturation , 1 ) ) ;
2025-07-18 00:21:33 +01:00
gas . FirstOffsetDomainRotationSpeed = 5 ;
2020-12-01 20:38:28 +00:00
}
else
{
// Define primary color and two adjacent colors
float primary = random . NextFloat ( ) ;
2026-08-10 14:57:50 +01:00
float right = frac ( primary + zoneSettings . GasGiantBandColorSeparation ) ;
float left = frac ( primary + 1 - zoneSettings . GasGiantBandColorSeparation ) ;
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>
2026-09-14 19:00:42 +02:00
2020-12-01 20:38:28 +00:00
// Create n time keys from 0 to 1
2021-03-18 00:36:34 +00:00
var bandCount = ( int ) ( zoneSettings . GasGiantBandCount . Evaluate ( random . NextFloat ( ) ) + . 5f ) ;
2020-12-01 20:38:28 +00:00
var times = Enumerable . Range ( 0 , bandCount )
. Select ( i = > ( float ) i / ( bandCount - 1 ) ) ;
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>
2026-09-14 19:00:42 +02:00
2020-12-01 20:38:28 +00:00
// Each band has a chance of being either the primary or one of the adjacent hues
// Saturation and Value are random with curves applied
2025-07-18 00:21:33 +01:00
gas . Colors = times
2026-08-10 14:57:50 +01:00
. Select ( time = > float4 ( ColorMath . HsvToRgb ( float3 (
2021-03-18 00:36:34 +00:00
random . NextFloat ( ) > zoneSettings . GasGiantBandAltColorChance ? primary : ( random . NextFloat ( ) > . 5f ? right : left ) ,
zoneSettings . GasGiantBandSaturation . Evaluate ( random . NextFloat ( ) ) ,
2026-08-10 14:57:50 +01:00
zoneSettings . GasGiantBandSaturation . Evaluate ( random . NextFloat ( ) ) ) ) , time ) )
2020-12-01 20:38:28 +00:00
. ToArray ( ) ;
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>
2026-09-14 19:00:42 +02:00
2025-07-18 00:21:33 +01:00
gas . FirstOffsetDomainRotationSpeed = 0 ;
2020-12-01 20:38:28 +00:00
}
2025-07-18 00:21:33 +01:00
gas . AlbedoRotationSpeed = - 3 ;
gas . FirstOffsetRotationSpeed = 5 ;
gas . SecondOffsetRotationSpeed = 10 ;
gas . SecondOffsetDomainRotationSpeed = - 25 ;
2020-11-27 22:47:33 +00:00
}
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>
2026-09-14 19:00:42 +02:00
// 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 ) ;
2020-11-27 22:47:33 +00:00
} ) . ToList ( ) ;
2020-03-30 03:11:57 +01:00
2021-05-14 03:03:28 +01:00
var nearestFaction = galaxy . Factions . MinBy ( f = > galaxy . HomeZones [ f ] . Distance [ galaxyZone ] ) ;
var nearestFactionHomeZone = galaxy . HomeZones [ nearestFaction ] ;
var factionPresence = nearestFaction . InfluenceDistance - nearestFactionHomeZone . Distance [ galaxyZone ] + 1 ;
2021-05-02 20:59:45 +01:00
2021-05-16 05:24:51 +01:00
var storyStations = galaxyZone . Locations . Where ( story = > story . Type = = LocationType . Station ) . ToArray ( ) ;
var stationCount = ( int ) ( random . NextFloat ( ) * ( factionPresence + 1 ) ) + storyStations . Length ;
Tutorial station: ZoneGenerator.GenerateZone always seats the entrance zone (operator ruling)
Operator, 2026-09-25 (docs/locomotion-cut.md): the tutorial's entrance zone always gets a
generated faction station with a docking bay, deterministically. Structurally, three defects made
that false on every seed: Galaxy.cs halves influence and picks the lowest-influence unowned zone
for Entrance, so stationCount rolled 0; a Lagrange orbit is only available when a planet has an
orbiting sibling, which a boring or empty system never has; and a null station loadout silently
skipped the slot.
GenerateZone now identifies the entrance zone by reference against the galaxy parameter it already
takes (isTutorial && galaxyZone == galaxy.Entrance), never by name:
- stationCount is floored to 1 for the entrance zone, after the roll that produced it.
- When no Lagrange orbit exists, a fallback orbit fixed at half the zone radius from centre is used
instead of leaving selectedStationOrbits empty. CreateLagrangeOrbit now carries FixedPosition
through so a Parent-less base orbit (the fallback) still positions correctly.
- A null station loadout throws for the entrance zone instead of continuing past it.
- The enemy count is based on the pre-override station count, so forcing the entrance's station
doesn't also hand it a bonus enemy.
The story-station path (Locations/LocationStory) is not used: it isn't saved, and
EntitySerializer.cs:77 would break Continue, per the map's own ruling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:40:31 +02:00
// Enemy count below is based on this pre-override value, so the tutorial entrance doesn't gain an enemy
// just because its station got forced.
var baseStationCount = stationCount ;
// Operator ruling, 2026-09-25 (docs/locomotion-cut.md, "Tutorial station"): the entrance zone always
// gets a generated faction station with a docking bay, deterministically. Galaxy.cs halves influence and
// picks the lowest-influence unowned zone for Entrance, which otherwise rolls stationCount 0 on every
// seed. galaxy.Entrance is reference-identity, not a name comparison.
var isTutorialEntrance = isTutorial & & galaxyZone = = galaxy . Entrance ;
if ( isTutorialEntrance & & stationCount < 1 ) stationCount = 1 ;
2021-05-02 20:59:45 +01:00
var potentialLagrangePoints = planets
. Where ( p = > p . Parent ! = null & & p . Parent . Children
2026-08-10 14:57:50 +01:00
. TrueForAll ( c = > ! ( c ! = p & & abs ( c . Distance - p . Distance ) < . 1f ) ) ) // Filter Rosettes
2021-05-02 20:59:45 +01:00
. OrderBy ( p = > p . Distance )
. ToArray ( ) ;
2026-09-25 20:15:00 +02:00
// The entrance's own station count is the floor above; its orbit always comes from this same selection.
// Ordinarily that selection is potentialLagrangePoints (non-rosette planet orbits). If the entrance zone
// has none of those (a boring single-body system, or a system that is entirely one rosette), widen to
// every planet orbit ordinary placement's own candidate pool is drawn from before the rosette filter --
// still a real, parented planet orbit with a positive Distance -- rather than inventing an unparented,
// zero-distance orbit (that gave PlaceTurret's phase = 20*multiplier/orbit.Distance a division by zero,
// and left an orbit record with no planet behind it).
var lagrangeCandidates = potentialLagrangePoints ;
if ( isTutorialEntrance & & lagrangeCandidates . Length = = 0 )
lagrangeCandidates = planets . Where ( p = > p . Parent ! = null ) . OrderBy ( p = > p . Distance ) . ToArray ( ) ;
2021-05-02 20:59:45 +01:00
// Pick a selection from the middle of the distribution
2026-09-25 20:15:00 +02:00
var selectedStationOrbits = lagrangeCandidates
. Skip ( lagrangeCandidates . Length / 2 )
2021-05-02 20:59:45 +01:00
. Take ( stationCount )
. Select ( p = > orbitMap [ p ] )
. ToArray ( ) ;
Tutorial station: ZoneGenerator.GenerateZone always seats the entrance zone (operator ruling)
Operator, 2026-09-25 (docs/locomotion-cut.md): the tutorial's entrance zone always gets a
generated faction station with a docking bay, deterministically. Structurally, three defects made
that false on every seed: Galaxy.cs halves influence and picks the lowest-influence unowned zone
for Entrance, so stationCount rolled 0; a Lagrange orbit is only available when a planet has an
orbiting sibling, which a boring or empty system never has; and a null station loadout silently
skipped the slot.
GenerateZone now identifies the entrance zone by reference against the galaxy parameter it already
takes (isTutorial && galaxyZone == galaxy.Entrance), never by name:
- stationCount is floored to 1 for the entrance zone, after the roll that produced it.
- When no Lagrange orbit exists, a fallback orbit fixed at half the zone radius from centre is used
instead of leaving selectedStationOrbits empty. CreateLagrangeOrbit now carries FixedPosition
through so a Parent-less base orbit (the fallback) still positions correctly.
- A null station loadout throws for the entrance zone instead of continuing past it.
- The enemy count is based on the pre-override station count, so forcing the entrance's station
doesn't also hand it a bonus enemy.
The story-station path (Locations/LocationStory) is not used: it isn't saved, and
EntitySerializer.cs:77 would break Continue, per the map's own ruling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:40:31 +02:00
if ( isTutorialEntrance & & selectedStationOrbits . Length = = 0 )
2026-09-25 20:15:00 +02:00
throw new InvalidOperationException (
"The tutorial entrance zone must always get a station, but it has no orbit at all -- every planet in the zone is a rootless rosette member with no parent." ) ;
2021-05-16 05:24:51 +01:00
var loadoutGenerators = new Dictionary < Faction , LoadoutGenerator > ( ) ;
LoadoutGenerator GetLoadoutGenerator ( Faction faction )
{
if ( ! loadoutGenerators . ContainsKey ( faction ) )
2026-08-10 14:57:50 +01:00
loadoutGenerators [ faction ] = new LoadoutGenerator ( ref random , itemManager , galaxy , galaxyZone , faction , . 5f ) ;
2021-05-16 05:24:51 +01:00
return loadoutGenerators [ faction ] ;
}
2026-08-10 14:57:50 +01:00
OrbitData CreateLagrangeOrbit ( OrbitData baseOrbit )
2021-03-18 00:36:34 +00:00
{
2026-08-10 14:57:50 +01:00
var lagrangeOrbit = new OrbitData
2021-05-02 20:59:45 +01:00
{
2026-08-10 14:57:50 +01:00
Parent = baseOrbit . Parent ,
2025-07-18 00:21:33 +01:00
Distance = baseOrbit . Distance ,
Tutorial station: ZoneGenerator.GenerateZone always seats the entrance zone (operator ruling)
Operator, 2026-09-25 (docs/locomotion-cut.md): the tutorial's entrance zone always gets a
generated faction station with a docking bay, deterministically. Structurally, three defects made
that false on every seed: Galaxy.cs halves influence and picks the lowest-influence unowned zone
for Entrance, so stationCount rolled 0; a Lagrange orbit is only available when a planet has an
orbiting sibling, which a boring or empty system never has; and a null station loadout silently
skipped the slot.
GenerateZone now identifies the entrance zone by reference against the galaxy parameter it already
takes (isTutorial && galaxyZone == galaxy.Entrance), never by name:
- stationCount is floored to 1 for the entrance zone, after the roll that produced it.
- When no Lagrange orbit exists, a fallback orbit fixed at half the zone radius from centre is used
instead of leaving selectedStationOrbits empty. CreateLagrangeOrbit now carries FixedPosition
through so a Parent-less base orbit (the fallback) still positions correctly.
- A null station loadout throws for the entrance zone instead of continuing past it.
- The enemy count is based on the pre-override station count, so forcing the entrance's station
doesn't also hand it a bonus enemy.
The story-station path (Locations/LocationStory) is not used: it isn't saved, and
EntitySerializer.cs:77 would break Continue, per the map's own ruling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:40:31 +02:00
Phase = baseOrbit . Phase + PI / 3 * sign ( random . NextFloat ( ) - . 5f ) ,
// Carried through for the tutorial-entrance fallback orbit below, which has no Parent and is
// positioned by FixedPosition alone; every other base orbit here has a Parent and ignores it.
FixedPosition = baseOrbit . FixedPosition ,
2021-05-02 20:59:45 +01:00
} ;
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>
2026-09-14 19:00:42 +02:00
pack . Orbits . Add ( cache . Upsert ( lagrangeOrbit ) ) ;
2021-05-16 05:24:51 +01:00
return lagrangeOrbit ;
}
2026-08-10 14:57:50 +01:00
void PlaceTurret ( OrbitData orbit , LoadoutGenerator loadoutGenerator , int distanceMultiplier )
2021-05-16 05:24:51 +01:00
{
2025-07-18 00:21:33 +01:00
var phase = 20f * distanceMultiplier / orbit . Distance ;
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>
2026-09-14 19:00:42 +02:00
2026-08-10 14:57:50 +01:00
var turretOrbit = new OrbitData
2021-05-02 20:59:45 +01:00
{
2026-08-10 14:57:50 +01:00
Parent = orbit . Parent ,
2025-07-18 00:21:33 +01:00
Distance = orbit . Distance ,
2021-05-16 05:24:51 +01:00
Phase = orbit . Phase + phase
2021-05-02 20:59:45 +01:00
} ;
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>
2026-09-14 19:00:42 +02:00
pack . Orbits . Add ( cache . Upsert ( turretOrbit ) ) ;
2021-05-16 05:24:51 +01:00
var turret = loadoutGenerator . GenerateTurretLoadout ( ) ;
2022-07-12 14:22:23 +01:00
if ( turret = = null ) return ;
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>
2026-09-14 19:00:42 +02:00
turret . Orbit = cache . RefOf ( turretOrbit ) ;
2026-08-10 14:57:50 +01:00
pack . Entities . Add ( turret ) ;
2021-05-16 05:24:51 +01:00
}
2026-08-10 14:57:50 +01:00
void PlaceTurrets ( OrbitData orbit , LoadoutGenerator loadoutGenerator , int count )
2021-05-16 05:24:51 +01:00
{
for ( int t = 0 ; t < count ; t + + )
2021-05-02 20:59:45 +01:00
{
2021-05-16 05:24:51 +01:00
var dist = t / 2 ;
if ( t % 2 = = 0 ) dist = - dist ;
PlaceTurret ( orbit , loadoutGenerator , dist ) ;
}
2021-03-18 00:36:34 +00:00
}
2021-05-16 05:24:51 +01:00
for ( var i = 0 ; i < storyStations . Length ; i + + )
{
var story = storyStations [ i ] ;
var orbit = selectedStationOrbits [ i ] ;
var lagrangeOrbit = CreateLagrangeOrbit ( orbit ) ;
var station = GetLoadoutGenerator ( story . Faction ) . GenerateStationLoadout ( ) ;
2026-09-12 23:22:36 +02:00
if ( station = = null )
{
itemManager . Log ( $"Story station for {story.Faction?.Name} could not be generated; its story has nowhere to live!" ) ;
continue ;
}
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>
2026-09-14 19:00:42 +02:00
station . Orbit = cache . RefOf ( lagrangeOrbit ) ;
2021-05-16 05:24:51 +01:00
station . SecurityLevel = story . Security ;
2026-08-10 14:57:50 +01:00
station . SecurityRadius = pack . Radius ;
2021-05-16 05:24:51 +01:00
station . Story = i ;
2026-08-10 14:57:50 +01:00
pack . Entities . Add ( station ) ;
2021-05-16 05:24:51 +01:00
PlaceTurrets ( lagrangeOrbit , GetLoadoutGenerator ( story . Faction ) , story . Turrets ) ;
}
for ( var i = storyStations . Length ; i < selectedStationOrbits . Length ; i + + )
{
var orbit = selectedStationOrbits [ i ] ;
2026-08-10 14:57:50 +01:00
var security = ( SecurityLevel ) ( ( int ) ( ( 1f - pow ( random . NextFloat ( ) , factionPresence / 2f ) ) * 3f ) ) ;
2021-05-16 05:24:51 +01:00
var lagrangeOrbit = CreateLagrangeOrbit ( orbit ) ;
var station = GetLoadoutGenerator ( nearestFaction ) . GenerateStationLoadout ( ) ;
Tutorial station: ZoneGenerator.GenerateZone always seats the entrance zone (operator ruling)
Operator, 2026-09-25 (docs/locomotion-cut.md): the tutorial's entrance zone always gets a
generated faction station with a docking bay, deterministically. Structurally, three defects made
that false on every seed: Galaxy.cs halves influence and picks the lowest-influence unowned zone
for Entrance, so stationCount rolled 0; a Lagrange orbit is only available when a planet has an
orbiting sibling, which a boring or empty system never has; and a null station loadout silently
skipped the slot.
GenerateZone now identifies the entrance zone by reference against the galaxy parameter it already
takes (isTutorial && galaxyZone == galaxy.Entrance), never by name:
- stationCount is floored to 1 for the entrance zone, after the roll that produced it.
- When no Lagrange orbit exists, a fallback orbit fixed at half the zone radius from centre is used
instead of leaving selectedStationOrbits empty. CreateLagrangeOrbit now carries FixedPosition
through so a Parent-less base orbit (the fallback) still positions correctly.
- A null station loadout throws for the entrance zone instead of continuing past it.
- The enemy count is based on the pre-override station count, so forcing the entrance's station
doesn't also hand it a bonus enemy.
The story-station path (Locations/LocationStory) is not used: it isn't saved, and
EntitySerializer.cs:77 would break Continue, per the map's own ruling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:40:31 +02:00
if ( station = = null )
{
if ( isTutorialEntrance )
throw new InvalidOperationException (
$"The tutorial entrance zone must always get a station, but {nearestFaction?.Name} has no station loadout to generate." ) ;
continue ;
}
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>
2026-09-14 19:00:42 +02:00
station . Orbit = cache . RefOf ( lagrangeOrbit ) ;
2021-05-16 05:24:51 +01:00
station . SecurityLevel = security ;
2026-08-10 14:57:50 +01:00
station . SecurityRadius = pack . Radius ;
pack . Entities . Add ( station ) ;
2021-05-16 05:24:51 +01:00
PlaceTurrets ( lagrangeOrbit , GetLoadoutGenerator ( nearestFaction ) , 2 ) ;
}
Tutorial station: ZoneGenerator.GenerateZone always seats the entrance zone (operator ruling)
Operator, 2026-09-25 (docs/locomotion-cut.md): the tutorial's entrance zone always gets a
generated faction station with a docking bay, deterministically. Structurally, three defects made
that false on every seed: Galaxy.cs halves influence and picks the lowest-influence unowned zone
for Entrance, so stationCount rolled 0; a Lagrange orbit is only available when a planet has an
orbiting sibling, which a boring or empty system never has; and a null station loadout silently
skipped the slot.
GenerateZone now identifies the entrance zone by reference against the galaxy parameter it already
takes (isTutorial && galaxyZone == galaxy.Entrance), never by name:
- stationCount is floored to 1 for the entrance zone, after the roll that produced it.
- When no Lagrange orbit exists, a fallback orbit fixed at half the zone radius from centre is used
instead of leaving selectedStationOrbits empty. CreateLagrangeOrbit now carries FixedPosition
through so a Parent-less base orbit (the fallback) still positions correctly.
- A null station loadout throws for the entrance zone instead of continuing past it.
- The enemy count is based on the pre-override station count, so forcing the entrance's station
doesn't also hand it a bonus enemy.
The story-station path (Locations/LocationStory) is not used: it isn't saved, and
EntitySerializer.cs:77 would break Continue, per the map's own ruling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:40:31 +02:00
var enemyCount = ( int ) ( random . NextFloat ( ) * factionPresence * 2 ) + baseStationCount ;
2021-05-02 20:59:45 +01:00
for ( int i = 0 ; i < enemyCount ; i + + )
{
2022-07-12 14:22:23 +01:00
var ship = GetLoadoutGenerator ( nearestFaction ) . GenerateShipLoadout ( ) ;
if ( ship = = null ) continue ;
2026-08-10 14:57:50 +01:00
pack . Entities . Add ( ship ) ;
2021-05-02 20:59:45 +01:00
}
2021-03-18 00:36:34 +00:00
2026-09-17 14:00:22 +02:00
// Neutral wanderers: ships belonging to a faction that neither owns the zone nor is its
// nearest faction, so existing hostility rules leave them non-hostile to the player.
// Testing affordance for hand-testing combat/targeting; none spawn if no faction qualifies.
var eligibleWandererFactions = EligibleWandererFactions ( galaxy . Factions , galaxyZone . Owner , nearestFaction ) ;
if ( zoneSettings . NeutralWandererCount > 0 & & eligibleWandererFactions . Length > 0 )
{
var wandererFaction = eligibleWandererFactions [ random . NextInt ( eligibleWandererFactions . Length ) ] ;
var wandererLoadoutGenerator = GetLoadoutGenerator ( wandererFaction ) ;
for ( int i = 0 ; i < zoneSettings . NeutralWandererCount ; i + + )
{
var ship = wandererLoadoutGenerator . GenerateShipLoadout ( ) ;
if ( ship = = null ) continue ;
pack . Entities . Add ( ship ) ;
}
}
2026-08-10 14:57:50 +01:00
return pack ;
2020-03-30 03:11:57 +01:00
}
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>
2026-09-14 19:00:42 +02:00
2026-09-17 14:00:22 +02:00
// The neutral-wanderer faction pool: every galaxy faction except the zone owner and the nearest
// faction (whose ships are already the zone's hostile-when-trespassing/enemy spawns). Extracted so
// the selection rule is testable without running the full zone-generation pipeline.
public static Faction [ ] EligibleWandererFactions ( Faction [ ] factions , Faction owner , Faction nearest ) = >
factions . Where ( f = > f ! = owner & & f ! = nearest ) . ToArray ( ) ;
2026-08-10 14:57:50 +01:00
// static float ResourceValue(ref Random random, ZoneGenerationSettings settings, SimpleCommodityData resource, float density)
// {
// return random.NextPowerDistribution(resource.Minimum, resource.Maximum, resource.Exponent,
// 1 / lerp(settings.ResourceDensityMinimum, settings.ResourceDensityMaximum, density));
// }
2026-06-13 04:56:50 +01:00
2021-03-08 22:58:37 +00:00
public static GeneratorPlanet [ ] GenerateEntities ( ZoneGenerationSettings settings , ref Random random , float mass , float radius , float2 fixedPosition )
2020-03-22 19:05:26 +00:00
{
2020-11-27 22:47:33 +00:00
var root = new GeneratorPlanet
2020-03-22 19:05:26 +00:00
{
2021-03-08 22:58:37 +00:00
FixedPosition = fixedPosition ,
2020-12-01 00:16:11 +00:00
Settings = settings ,
2021-03-08 22:58:37 +00:00
Mass = mass ,
ChildDistanceMaximum = radius * . 75f ,
ChildDistanceMinimum = settings . PlanetSafetyRadius . Evaluate ( mass )
2020-03-22 19:05:26 +00:00
} ;
// There is some chance of generating a rosette or binary system
2020-03-28 18:36:12 +00:00
// Probabilities which are fixed for the entire galaxy are in GlobalData, contained in the GameContext
2020-12-01 00:16:11 +00:00
var rosette = random . NextFloat ( ) < settings . RosetteProbability ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
if ( rosette )
{
// Create a rosette with a number of vertices between 2 and 9 inclusive
2020-12-01 00:16:11 +00:00
root . ExpandRosette ( ref random , ( int ) ( random . NextFloat ( 1 , 5 ) + random . NextFloat ( 1 , 5 ) ) ) ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
// Create a small number of less massive "captured" planets orbiting past the rosette
root . ExpandSolar (
2020-12-01 00:16:11 +00:00
ref random ,
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>
2026-09-14 19:00:42 +02:00
count : ( int ) ( random . NextFloat ( 1 , 3 ) * random . NextFloat ( 1 , 2 ) ) ,
massMulMin : . 6f ,
massMulMax : . 8f ,
distMulMin : 1.25f ,
distMulMax : 1.75f ,
2020-03-22 19:05:26 +00:00
jupiterJump : 1 ,
massFraction : . 1f ) ;
var averageChildMass = root . Children . Sum ( p = > p . Mass ) / root . Children . Count ;
2020-12-01 00:16:11 +00:00
foreach ( var p in root . Children . Where ( c = > c . Mass > settings . GasGiantMass ) )
2020-03-22 19:05:26 +00:00
{
var m = p . Mass / averageChildMass ;
// Give each child in the rosette its own mini solar system
p . ExpandSolar (
2020-12-01 00:16:11 +00:00
ref random ,
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>
2026-09-14 19:00:42 +02:00
count : ( int ) ( random . NextFloat ( 1 , 3 * m ) + random . NextFloat ( 1 , 3 * m ) ) ,
massMulMin : 0.75f ,
massMulMax : 2.5f ,
2020-03-30 03:11:57 +01:00
distMulMin : 1 + m * . 25f ,
distMulMax : 1.05f + m * . 5f ,
2020-03-22 19:05:26 +00:00
jupiterJump : random . NextFloat ( ) * random . NextFloat ( ) * 10 + 1 ,
massFraction : . 5f
) ;
}
}
else
{
// Create a regular old boring solar system
root . ExpandSolar (
2020-12-01 00:16:11 +00:00
ref random ,
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>
2026-09-14 19:00:42 +02:00
count : random . NextInt ( 5 , 15 ) ,
massMulMin : 0.75f ,
massMulMax : 2.5f ,
2020-03-22 19:05:26 +00:00
distMulMin : 1.1f ,
2020-03-30 03:11:57 +01:00
distMulMax : 1.25f ,
2020-03-22 19:05:26 +00:00
jupiterJump : random . NextFloat ( ) * random . NextFloat ( ) * 10 + 1 ,
massFraction : . 25f
) ;
}
2020-12-01 00:16:11 +00:00
var alreadyExpanded = new List < GeneratorPlanet > ( ) ;
2020-11-27 22:47:33 +00:00
var binaries = new List < GeneratorPlanet > ( ) ;
2020-12-01 00:16:11 +00:00
for ( int i = 0 ; i < settings . SatellitePasses ; i + + )
2020-03-22 19:05:26 +00:00
{
2020-12-01 00:16:11 +00:00
// Get all children that are above the satellite creation mass floor and not rosette members
var satelliteCandidates = rosette
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>
2026-09-14 19:00:42 +02:00
? root . AllPlanets ( ) . Where ( p = >
p ! = root & &
p . Parent ! = root & &
2020-12-01 00:16:11 +00:00
p . Mass > settings . SatelliteCreationMassFloor & &
! alreadyExpanded . Contains ( p ) )
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>
2026-09-14 19:00:42 +02:00
: root . AllPlanets ( ) . Where ( p = >
p ! = root & &
2020-12-01 00:16:11 +00:00
p . Mass > settings . SatelliteCreationMassFloor & &
! alreadyExpanded . Contains ( p ) ) ;
foreach ( var planet in satelliteCandidates )
2020-03-22 19:05:26 +00:00
{
2020-12-01 00:16:11 +00:00
// There's a chance of generating satellites for each qualified planet
if ( random . NextFloat ( ) < settings . SatelliteCreationProbability )
2020-03-30 17:54:54 +01:00
{
2020-12-01 00:16:11 +00:00
// Sometimes the satellite is so massive that it forms a binary system (like Earth!)
if ( random . NextFloat ( ) < settings . BinaryCreationProbability )
{
planet . ExpandRosette ( ref random , 2 ) ;
binaries . AddRange ( planet . Children ) ;
}
// Otherwise, terrestrial planets get a couple satellites while gas giants get many
else
{
planet . ExpandSolar (
ref random ,
count : planet . Mass < settings . GasGiantMass ? random . NextInt ( 1 , 3 ) : random . NextInt ( 2 , 6 ) ,
massMulMin : . 75f ,
massMulMax : 1.5f ,
distMulMin : 1.05f ,
distMulMax : 1.25f ,
jupiterJump : 1 ,
massFraction : . 15f ) ; // Planetary satellites are not nearly as massive as planets themselves
}
alreadyExpanded . Add ( planet ) ;
2020-03-30 17:54:54 +01:00
}
2020-03-22 19:05:26 +00:00
}
}
2020-03-30 17:54:54 +01:00
// Get all children that are below the belt creation mass floor and not rosette members, also exclude binaries
var beltCandidates = rosette
2020-12-05 23:43:02 +00:00
? 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 ) ;
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>
2026-09-14 19:00:42 +02:00
2020-03-31 13:44:18 +01:00
foreach ( var planet in beltCandidates . Reverse ( ) )
2020-12-01 00:16:11 +00:00
if ( random . NextFloat ( ) < settings . BeltProbability & & ! planet . Parent . Children . Any ( p = > p . Belt ) )
2020-03-30 17:54:54 +01:00
planet . Belt = true ;
2020-12-01 00:16:11 +00:00
var totalMass = root . AllPlanets ( ) . Sum ( p = > p . Mass ) ;
var rootMass = root . Mass ;
foreach ( var planet in root . AllPlanets ( ) )
planet . Mass = ( planet . Mass / totalMass ) * rootMass ;
2020-03-22 19:05:26 +00:00
return root . AllPlanets ( ) . ToArray ( ) ;
}
}
2020-03-28 18:36:12 +00:00
// All orbiting bodies are referred to here as "Planets"
// This goes back to the original etymology for the word planet, which is traveler,
// Because they were objects we observed to move in the sky instead of remaining fixed like the stars
2020-11-27 22:47:33 +00:00
public class GeneratorPlanet
2020-03-22 19:05:26 +00:00
{
2020-12-01 00:16:11 +00:00
public ZoneGenerationSettings Settings ;
2020-03-22 19:05:26 +00:00
public float Distance ;
public float Phase ;
public float Mass ;
2020-12-01 00:16:11 +00:00
//public float Period;
2020-03-22 19:05:26 +00:00
public float ChildDistanceMinimum ;
public float ChildDistanceMaximum ;
public bool Empty = false ;
2020-03-30 17:54:54 +01:00
public bool Belt = false ;
2020-11-27 22:47:33 +00:00
public List < GeneratorPlanet > Children = new List < GeneratorPlanet > ( ) ; // Planets orbiting this one are referred to as children
public GeneratorPlanet Parent ;
2021-03-08 22:58:37 +00:00
public float2 FixedPosition ;
2020-03-22 19:05:26 +00:00
2020-03-28 18:36:12 +00:00
// Recursively gather all planets in the hierarchy
2020-11-27 22:47:33 +00:00
public IEnumerable < GeneratorPlanet > AllPlanets ( )
2020-03-22 19:05:26 +00:00
{
return new [ ] { this } . Concat ( Children . SelectMany ( c = > c . AllPlanets ( ) ) ) ;
}
// Create children that fill a single orbit, equally spaced
// https://en.wikipedia.org/wiki/Klemperer_rosette
2020-12-01 00:16:11 +00:00
public void ExpandRosette ( ref Random random , int vertices )
2020-03-22 19:05:26 +00:00
{
2020-03-30 17:54:54 +01:00
//Debug.Log("Expanding Rosette");
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
// Rosette children replace the parent, parent orbital node is left empty
Empty = true ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
// Masses in a rosette alternate, so every sequential pair has the same shared mass
var sharedMass = Mass / vertices * 2 ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
// Proportion of the masses of sequential pairs is random, but only for even vertex counts
2020-12-01 00:16:11 +00:00
var proportion = vertices % 2 = = 0 ? random . NextFloat ( . 5f , . 95f ) : . 5f ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
// Place children at a fixed distance in the center of the range
var dist = ( ChildDistanceMinimum + ChildDistanceMaximum ) / 2 ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
// Position of first child
var p0 = new float2 ( 0 , dist ) ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
// Position of second child
2026-08-10 14:57:50 +01:00
var p1 = OrbitData . Evaluate ( 1.0f / vertices ) * dist ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
// Maximum child distance is half the distance to the neighbor minus the neighbor's radius
2026-08-10 14:57:50 +01:00
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 ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
for ( int i = 0 ; i < vertices ; i + + )
{
2020-11-27 22:47:33 +00:00
var child = new GeneratorPlanet
2020-03-22 19:05:26 +00:00
{
2020-12-01 00:16:11 +00:00
Settings = Settings ,
2020-03-22 19:05:26 +00:00
Parent = this ,
Mass = sharedMass * ( i % 2 = = 0 ? proportion : 1 - proportion ) , // Masses alternate
Distance = dist ,
Phase = ( float ) i / vertices ,
ChildDistanceMaximum = ( i % 2 = = 0 ? p0ChildDist : p1ChildDist )
} ;
2020-12-01 00:16:11 +00:00
child . ChildDistanceMinimum = Settings . PlanetSafetyRadius . Evaluate ( child . Mass ) * 2 ;
//child.Period = Settings..Evaluate(child.Distance);
2020-03-22 19:05:26 +00:00
Children . Add ( child ) ;
}
ChildDistanceMinimum = dist + p0ChildDist ;
}
// Create children that mimic the distribution of planetary masses in the solar system
2020-12-01 00:16:11 +00:00
public void ExpandSolar ( ref Random random , int count , float massMulMin , float massMulMax , float distMulMin , float distMulMax , float jupiterJump , float massFraction )
2020-03-22 19:05:26 +00:00
{
2020-03-30 17:54:54 +01:00
//Debug.Log("Expanding Solar");
2020-03-22 19:05:26 +00:00
// Expansion is impossible when child space is full
if ( count = = 0 | | ChildDistanceMaximum < ChildDistanceMinimum )
return ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
var masses = new float [ count ] ;
var distances = new float [ count ] ;
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>
2026-09-14 19:00:42 +02:00
// Accumulate total mass
2020-03-22 19:05:26 +00:00
// Initialize first mass and distance, actual number doesn't matter since total mass will be divided proportionally
float massTotal = distances [ 0 ] = masses [ 0 ] = 1 ;
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
// 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
for ( var i = 1 ; i < count ; i + + )
{
2020-12-01 00:16:11 +00:00
massTotal + = masses [ i ] = masses [ i - 1 ] * random . NextFloat ( massMulMin , massMulMax ) * ( count / 2 = = i ? jupiterJump : 1 ) ;
distances [ i ] = distances [ i - 1 ] * random . NextFloat ( distMulMin , distMulMax ) ;
2020-03-22 19:05:26 +00:00
}
// Add some randomness to child masses
for ( var i = 0 ; i < masses . Length ; i + + )
2020-12-01 00:16:11 +00:00
masses [ i ] * = random . NextFloat ( . 1f , 1f ) ;
2020-03-22 19:05:26 +00:00
// Normalize the masses of the children
for ( var i = 0 ; i < count ; i + + )
masses [ i ] = masses [ i ] / massTotal * Mass * massFraction ;
// Map child distances to range between minimum and maximum
2020-03-29 04:19:24 +01:00
if ( count > 1 )
{
var oldDistances = ( float [ ] ) distances . Clone ( ) ;
for ( var i = 0 ; i < count ; i + + )
2026-08-10 14:57:50 +01:00
distances [ i ] = lerp ( ChildDistanceMinimum , ChildDistanceMaximum ,
2020-12-01 00:16:11 +00:00
( oldDistances [ i ] - oldDistances [ 0 ] ) / ( oldDistances [ count - 1 ] - oldDistances [ 0 ] ) ) + Settings . PlanetSafetyRadius . Evaluate ( masses [ i ] ) ;
2020-03-29 04:19:24 +01:00
}
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>
2026-09-14 19:00:42 +02:00
2020-03-22 19:05:26 +00:00
for ( var i = 0 ; i < count ; i + + )
{
// Only instantiate children above the mass floor
2020-12-01 00:16:11 +00:00
if ( masses [ i ] > Settings . MassFloor )
2020-03-22 19:05:26 +00:00
{
2020-11-27 22:47:33 +00:00
var child = new GeneratorPlanet
2020-03-22 19:05:26 +00:00
{
2020-12-01 00:16:11 +00:00
Settings = Settings ,
2020-03-22 19:05:26 +00:00
Parent = this ,
Mass = masses [ i ] ,
Distance = distances [ i ] ,
2020-12-01 00:16:11 +00:00
Phase = random . NextFloat ( )
2020-03-22 19:05:26 +00:00
} ;
2020-12-01 00:16:11 +00:00
//child.Period = Context.GlobalData.OrbitalPeriod(child.Distance);
child . ChildDistanceMinimum = Settings . PlanetSafetyRadius . Evaluate ( child . Mass ) * 2 ;
2020-03-22 19:05:26 +00:00
// Maximum child distance of child is the smallest distance to either of its neighbors
2026-08-10 14:57:50 +01:00
child . ChildDistanceMaximum = min ( i = = 0 ? child . Distance - ChildDistanceMinimum : child . Distance - distances [ i - 1 ] ,
2020-03-22 19:05:26 +00:00
i < count - 1 ? distances [ i + 1 ] - child . Distance : float . PositiveInfinity ) ;
if ( float . IsNaN ( child . Distance ) )
throw new NotFiniteNumberException ( $"Planet created with NaN distance, something went very wrong!" ) ;
if ( child . ChildDistanceMaximum > child . ChildDistanceMinimum )
Children . Add ( child ) ;
}
}
}
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>
2026-09-14 19:00:42 +02:00
}