diff --git a/Assets/Prefabs/RPG/Planets/Gas Giant.prefab b/Assets/Prefabs/RPG/Planets/Gas Giant.prefab index 46ae7a3b1..cb628d804 100644 --- a/Assets/Prefabs/RPG/Planets/Gas Giant.prefab +++ b/Assets/Prefabs/RPG/Planets/Gas Giant.prefab @@ -31,7 +31,6 @@ Transform: m_Children: - {fileID: 3973935122748960680} - {fileID: 3973935121058605868} - - {fileID: 3973935122114073483} - {fileID: 3973935121358855036} m_Father: {fileID: 0} m_RootOrder: 0 @@ -53,7 +52,6 @@ MonoBehaviour: Icon: {fileID: 3973935121358855037} Generator: {fileID: 0} SunMaterial: {fileID: 3973935122748960685} - GravityWaves: {fileID: 3973935122114073485} --- !u!1 &3973935121058605867 GameObject: m_ObjectHideFlags: 0 @@ -220,89 +218,6 @@ MeshRenderer: m_SortingLayer: 0 m_SortingOrder: 0 m_AdditionalVertexStreams: {fileID: 0} ---- !u!1 &3973935122114073482 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 3973935122114073483} - - component: {fileID: 3973935122114073486} - - component: {fileID: 3973935122114073485} - m_Layer: 10 - m_Name: Gravity Waves - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &3973935122114073483 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3973935122114073482} - m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 3973935120991099173} - m_RootOrder: 2 - m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} ---- !u!33 &3973935122114073486 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3973935122114073482} - m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0} ---- !u!23 &3973935122114073485 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3973935122114073482} - m_Enabled: 1 - m_CastShadows: 1 - m_ReceiveShadows: 1 - m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 - m_MotionVectors: 1 - m_LightProbeUsage: 1 - m_ReflectionProbeUsage: 1 - m_RayTracingMode: 2 - m_RayTraceProcedural: 0 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 2100000, guid: 11fbaa87dcc7a3743a6bd3aa5c86e5ae, type: 2} - m_StaticBatchInfo: - firstSubMesh: 0 - subMeshCount: 0 - m_StaticBatchRoot: {fileID: 0} - m_ProbeAnchor: {fileID: 0} - m_LightProbeVolumeOverride: {fileID: 0} - m_ScaleInLightmap: 1 - m_ReceiveGI: 1 - m_PreserveUVs: 0 - m_IgnoreNormalsForChartDetection: 0 - m_ImportantGI: 0 - m_StitchLightmapSeams: 1 - m_SelectedEditorRenderState: 3 - m_MinimumChartSize: 4 - m_AutoUVMaxDistance: 0.5 - m_AutoUVMaxAngle: 89 - m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 - m_SortingOrder: 0 - m_AdditionalVertexStreams: {fileID: 0} --- !u!1 &3973935122748960663 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/RPG/Planets/Sun.prefab b/Assets/Prefabs/RPG/Planets/Sun.prefab index a7950f448..cf7fb216f 100644 --- a/Assets/Prefabs/RPG/Planets/Sun.prefab +++ b/Assets/Prefabs/RPG/Planets/Sun.prefab @@ -31,7 +31,6 @@ Transform: m_Children: - {fileID: 7852221720971737233} - {fileID: 7852221720969441490} - - {fileID: 7852221720741322892} - {fileID: 7852221720816817928} - {fileID: 7852221720964551245} m_Father: {fileID: 0} @@ -54,92 +53,8 @@ MonoBehaviour: Icon: {fileID: 7852221720964551246} Generator: {fileID: 0} SunMaterial: {fileID: 7852221720971737234} - GravityWaves: {fileID: 7852221720741322894} FogTint: {fileID: 7852221720816817929} Light: {fileID: 8064377225639657765} ---- !u!1 &7852221720741322899 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 7852221720741322892} - - component: {fileID: 7852221720741322895} - - component: {fileID: 7852221720741322894} - m_Layer: 10 - m_Name: Gravity Waves - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &7852221720741322892 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7852221720741322899} - m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 7852221720480299767} - m_RootOrder: 2 - m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} ---- !u!33 &7852221720741322895 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7852221720741322899} - m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0} ---- !u!23 &7852221720741322894 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7852221720741322899} - m_Enabled: 1 - m_CastShadows: 1 - m_ReceiveShadows: 1 - m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 - m_MotionVectors: 1 - m_LightProbeUsage: 1 - m_ReflectionProbeUsage: 1 - m_RayTracingMode: 2 - m_RayTraceProcedural: 0 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 2100000, guid: 11fbaa87dcc7a3743a6bd3aa5c86e5ae, type: 2} - m_StaticBatchInfo: - firstSubMesh: 0 - subMeshCount: 0 - m_StaticBatchRoot: {fileID: 0} - m_ProbeAnchor: {fileID: 0} - m_LightProbeVolumeOverride: {fileID: 0} - m_ScaleInLightmap: 1 - m_ReceiveGI: 1 - m_PreserveUVs: 0 - m_IgnoreNormalsForChartDetection: 0 - m_ImportantGI: 0 - m_StitchLightmapSeams: 1 - m_SelectedEditorRenderState: 3 - m_MinimumChartSize: 4 - m_AutoUVMaxDistance: 0.5 - m_AutoUVMaxAngle: 89 - m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 - m_SortingOrder: 0 - m_AdditionalVertexStreams: {fileID: 0} --- !u!1 &7852221720816817935 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Resources/Settings.asset b/Assets/Resources/Settings.asset index 032d8c898..64c4333d7 100644 --- a/Assets/Resources/Settings.asset +++ b/Assets/Resources/Settings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:667368f42385152984589abb3803aa88a51937286ac2450d890970710ec38e37 -size 13039 +oid sha256:319895214aaeea5d4dfe034c46edca055f62c6b4a9692788e60ce8a5827c4b78 +size 12634 diff --git a/Assets/Scenes/ARPG.unity b/Assets/Scenes/ARPG.unity index b93a7b909..57c793e09 100644 --- a/Assets/Scenes/ARPG.unity +++ b/Assets/Scenes/ARPG.unity @@ -16322,7 +16322,6 @@ MonoBehaviour: Settings: {fileID: 11400000, guid: cd2344e2001a37544aab0960c164d4ff, type: 2} ZoneRoot: {fileID: 2095699057} SectorBrushes: {fileID: 1037490739} - SectorBoundaryBrush: {fileID: 1476765550} SceneCamera: {fileID: 887677622} FogCameras: - {fileID: 559840917} @@ -16864,7 +16863,6 @@ Transform: m_LocalScale: {x: 3000, y: 3000, z: 3000} m_ConstrainProportionsScale: 0 m_Children: - - {fileID: 1476765548} - {fileID: 23420357} - {fileID: 1108843909} - {fileID: 1647230682} @@ -22639,96 +22637,6 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0.5} ---- !u!1 &1476765547 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1476765548} - - component: {fileID: 1476765551} - - component: {fileID: 1476765550} - m_Layer: 9 - m_Name: Boundary - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &1476765548 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1476765547} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 1037490739} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!23 &1476765550 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1476765547} - m_Enabled: 1 - m_CastShadows: 0 - m_ReceiveShadows: 0 - m_DynamicOccludee: 0 - m_StaticShadowCaster: 0 - m_MotionVectors: 2 - m_LightProbeUsage: 0 - m_ReflectionProbeUsage: 0 - m_RayTracingMode: 2 - m_RayTraceProcedural: 0 - m_RayTracingAccelStructBuildFlagsOverride: 0 - m_RayTracingAccelStructBuildFlags: 1 - m_SmallMeshCulling: 1 - m_ForceMeshLod: -1 - m_MeshLodSelectionBias: 0 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 2100000, guid: 8f45fb3a0f420d5428f1528827d55fa2, type: 2} - m_StaticBatchInfo: - firstSubMesh: 0 - subMeshCount: 0 - m_StaticBatchRoot: {fileID: 0} - m_ProbeAnchor: {fileID: 0} - m_LightProbeVolumeOverride: {fileID: 0} - m_ScaleInLightmap: 1 - m_ReceiveGI: 1 - m_PreserveUVs: 0 - m_IgnoreNormalsForChartDetection: 0 - m_ImportantGI: 0 - m_StitchLightmapSeams: 1 - m_SelectedEditorRenderState: 3 - m_MinimumChartSize: 4 - m_AutoUVMaxDistance: 0.5 - m_AutoUVMaxAngle: 89 - m_LightmapParameters: {fileID: 0} - m_GlobalIlluminationMeshLod: 0 - m_SortingLayerID: 0 - m_SortingLayer: 0 - m_SortingOrder: 0 - m_MaskInteraction: 0 - m_AdditionalVertexStreams: {fileID: 0} ---- !u!33 &1476765551 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1476765547} - m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0} --- !u!1 &1485884351 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scenes/Main Menu.unity b/Assets/Scenes/Main Menu.unity index a37cfffba..2d80e45b7 100644 --- a/Assets/Scenes/Main Menu.unity +++ b/Assets/Scenes/Main Menu.unity @@ -1061,16 +1061,6 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} - - target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309, - type: 3} - propertyPath: m_LocalScale.x - value: 512 - objectReference: {fileID: 0} - - target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309, - type: 3} - propertyPath: m_LocalScale.y - value: 512 - objectReference: {fileID: 0} - target: {fileID: 7852221720816817928, guid: 046dde0f4600bce41ad33c476f99b309, type: 3} propertyPath: m_LocalScale.x diff --git a/Assets/Scenes/TestScene.unity b/Assets/Scenes/TestScene.unity index 5288b6845..ffc3f3905 100644 --- a/Assets/Scenes/TestScene.unity +++ b/Assets/Scenes/TestScene.unity @@ -911,16 +911,6 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} - - target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309, - type: 3} - propertyPath: m_LocalScale.x - value: 40 - objectReference: {fileID: 0} - - target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309, - type: 3} - propertyPath: m_LocalScale.y - value: 40 - objectReference: {fileID: 0} - target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309, type: 3} propertyPath: m_LocalScale.x @@ -1778,16 +1768,6 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} - - target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309, - type: 3} - propertyPath: m_LocalScale.x - value: 40 - objectReference: {fileID: 0} - - target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309, - type: 3} - propertyPath: m_LocalScale.y - value: 40 - objectReference: {fileID: 0} - target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309, type: 3} propertyPath: m_LocalScale.x @@ -5297,16 +5277,6 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} - - target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309, - type: 3} - propertyPath: m_LocalScale.x - value: 40 - objectReference: {fileID: 0} - - target: {fileID: 7852221720741322892, guid: 046dde0f4600bce41ad33c476f99b309, - type: 3} - propertyPath: m_LocalScale.y - value: 40 - objectReference: {fileID: 0} - target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309, type: 3} propertyPath: m_LocalScale.x diff --git a/Assets/Scripts/Gameplay/GameSettings.cs b/Assets/Scripts/Gameplay/GameSettings.cs index d2b3df1d8..65e134755 100644 --- a/Assets/Scripts/Gameplay/GameSettings.cs +++ b/Assets/Scripts/Gameplay/GameSettings.cs @@ -15,7 +15,6 @@ public class GameSettings : ScriptableObject public float HeatstrokePhasingFrequency = 5; public float WormholeDistanceRatio; public float DefaultViewDistance; - public float MinimapZoneGravityRange; public float[] MinimapZoomLevels; public int DefaultMinimapZoom; public ExponentialCurve IconSize; diff --git a/Assets/Scripts/ServerShared/Settings.cs b/Assets/Scripts/ServerShared/Settings.cs index 1a04dd2ae..ab422c7fa 100644 --- a/Assets/Scripts/ServerShared/Settings.cs +++ b/Assets/Scripts/ServerShared/Settings.cs @@ -13,15 +13,8 @@ using static CultMath.math; [Serializable, MessagePackObject(keyAsPropertyName:true), JsonObject] public class PlanetSettings { - public float ZoneDepthExponent; - public float ZoneDepth; - public float ZoneBoundaryFog; public ExponentialCurve GravityDepth; public ExponentialCurve GravityRadius; - public ExponentialCurve WaveDepth; - public ExponentialCurve WaveRadius; - public ExponentialCurve WaveFrequency; - public ExponentialCurve WaveSpeed; public ExponentialCurve LightRadius; public ExponentialCurve BodyRadius; public float AsteroidVerticalOffset = -5f; diff --git a/Assets/Scripts/ServerShared/Zone.cs b/Assets/Scripts/ServerShared/Zone.cs index 16373f147..1b3604b54 100644 --- a/Assets/Scripts/ServerShared/Zone.cs +++ b/Assets/Scripts/ServerShared/Zone.cs @@ -161,6 +161,12 @@ public class Zone Orbits.Add(orbit.Key, new Orbit(Settings, cache.Get(orbit))); } + // Aetheria gravity (target invariant zone-valid-before-tick): every orbit is placed at the pack's time + // when the zone is built, so each well, light and body pose read before the first Update is where the + // body is. Velocity stays zero until the first Update differences two placed positions. + foreach (var orbit in Orbits) + GetOrbitPosition(orbit.Key); + foreach (var body in pack.Planets) { var planet = cache.Get(body); @@ -266,18 +272,16 @@ public class Zone private bool IsExpired(ChunkWear wear) => wear.Damage <= 0f && (!wear.BrokenUntil.HasValue || wear.BrokenUntil.Value <= _time); - public void AddOrbit(OrbitData orbit) - { - Orbits.Add(_itemManager.ItemData.RefOf(orbit).Key, new Orbit(Settings, orbit)); - } - public void Update(float deltaTime) { _time += deltaTime; _updatedOrbits.Clear(); + // Every previous position is taken before any orbit is placed: GetOrbitPosition places a parent as a + // side effect of placing its child, so differencing inside one loop would depend on dictionary order. + foreach (var orbit in Orbits) + orbit.Value.PreviousPosition = orbit.Value.Position; foreach (var orbit in Orbits) { - orbit.Value.PreviousPosition = orbit.Value.Position; orbit.Value.Position = GetOrbitPosition(orbit.Key); orbit.Value.Velocity = (orbit.Value.Position - orbit.Value.PreviousPosition) / deltaTime; } @@ -591,26 +595,6 @@ public class Zone into[i] = beltInstance.Pose(i, _time, parentPosition, Settings, ChunkRadius(new ChunkId(belt, i))); } - public OrbitData CreateOrbit(CultRecordKey parent, float2 position) - { - var parentPosition = GetOrbitPosition(parent); - var delta = position - parentPosition; - var distance = length(delta); - var period = Settings.OrbitPeriod.Evaluate(distance); - var phase = atan2(delta.y, delta.x) / (PI * 2); - var currentPhase = frac(_time / period); - var storedPhase = (float) frac(phase - currentPhase); - - var orbit = new OrbitData - { - Distance = distance, - Parent = new CultRecordRef(parent), - Phase = storedPhase - }; - Orbits.Add(_itemManager.ItemData.Upsert(orbit).Key, new Orbit(Settings, orbit)); - return orbit; - } - public SecurityLevel GetSecurityLevel(float2 pos) { if (GalaxyZone.Owner==null) return SecurityLevel.Open; @@ -630,7 +614,7 @@ public class Zone public float GetHeight(float2 position) { - var result = -PowerPulse(length(position)/(Pack.Radius*2), Settings.ZoneDepthExponent) * Settings.ZoneDepth; + var result = 0f; foreach (var body in PlanetInstances.Values) { var p = position - body.Orbit.Position; //GetOrbitPosition(body.BodyData.Orbit) @@ -641,18 +625,6 @@ public class Zone var depth = body.GravityWellDepth; result -= PowerPulse(sqrt(distSqr) / gravityRadius, body.BodyData.GravityDepthExponent) * depth; } - - if (body is GasGiant gas) - { - var waveRadius = gas.GravityWavesRadius; - if(distSqr < waveRadius*waveRadius) - { - var depth = gas.GravityWavesDepth; - var frequency = Settings.WaveFrequency.Evaluate(body.BodyData.Mass); - var speed = gas.GravityWavesSpeed; - result -= RadialWaves(sqrt(distSqr) / waveRadius, 8, 1.25f, frequency, (float) (_time * speed)) * depth; - } - } } return result; @@ -716,12 +688,6 @@ public class Zone return pow((x + 1) * (1 - x), exponent); } - public static float RadialWaves(float x, float maskExponent, float sineExponent, float frequency, float phase) - { - //x *= 2; - return PowerPulse(x, maskExponent) * cos(pow(x*2, sineExponent) * frequency + phase); - } - public float3 GetNormal(float2 pos, float step = .1f, float mul = 1) { float hL = GetHeight(new float2(pos.x - step, pos.y)) * mul; @@ -763,22 +729,10 @@ public class Planet public class GasGiant : Planet { public GasGiantData GasGiantData; - public float GravityWavesDepth; - public float GravityWavesRadius; - public float GravityWavesSpeed; public GasGiant(PlanetSettings settings, GasGiantData data, Orbit orbit) : base(settings, data, orbit) { GasGiantData = data; - CalculateProperties(); - } - - public new void CalculateProperties() - { - base.CalculateProperties(); - GravityWavesDepth = Settings.WaveDepth.Evaluate(BodyData.Mass) * GasGiantData.WaveDepthMultiplier; - GravityWavesRadius = Settings.WaveRadius.Evaluate(BodyData.Mass) * GasGiantData.WaveRadiusMultiplier; - GravityWavesSpeed = Settings.WaveSpeed.Evaluate(BodyData.Mass) * GasGiantData.WaveSpeedMultiplier; } } diff --git a/Assets/Scripts/ServerShared/ZoneData.cs b/Assets/Scripts/ServerShared/ZoneData.cs index 7dc02b878..70448ac40 100644 --- a/Assets/Scripts/ServerShared/ZoneData.cs +++ b/Assets/Scripts/ServerShared/ZoneData.cs @@ -186,18 +186,6 @@ public class GasGiantData : BodyData [JsonProperty("albedoRotationSpeed"), Key(13)] public float AlbedoRotationSpeed = 1; - [JsonProperty("gravRadiusMul")] [Key(14)] - public float WaveRadiusMultiplier = 1; - - [JsonProperty("depthMul")] [Key(15)] - public float WaveDepthMultiplier = 1; - - [JsonProperty("depthExp")] [Key(16)] - public float WaveDepthExponent = 8; - - [JsonProperty("depthExp")] [Key(17)] - public float WaveSpeedMultiplier = 8; - [JsonProperty("materialOverrides"), Key(18)] public List MaterialOverrides = new List(); diff --git a/Assets/Scripts/Zone Display/GasGiantObject.cs b/Assets/Scripts/Zone Display/GasGiantObject.cs index 81fb45a1d..927513648 100644 --- a/Assets/Scripts/Zone Display/GasGiantObject.cs +++ b/Assets/Scripts/Zone Display/GasGiantObject.cs @@ -9,5 +9,4 @@ using UnityEngine; public class GasGiantObject : PlanetObject { public SunMaterial SunMaterial; - public MeshRenderer GravityWaves; } diff --git a/Assets/Scripts/Zone Display/ZoneRenderer.cs b/Assets/Scripts/Zone Display/ZoneRenderer.cs index cccb97ebf..907134dbc 100644 --- a/Assets/Scripts/Zone Display/ZoneRenderer.cs +++ b/Assets/Scripts/Zone Display/ZoneRenderer.cs @@ -27,7 +27,6 @@ public class ZoneRenderer : MonoBehaviour public GameSettings Settings; public Transform ZoneRoot; public Transform SectorBrushes; - public MeshRenderer SectorBoundaryBrush; public CinemachineVirtualCamera SceneCamera; public Camera[] FogCameras; public Camera[] MinimapCameras; @@ -472,17 +471,12 @@ public class ZoneRenderer : MonoBehaviour else planet = Instantiate(GasGiant, ZoneRoot); var gas = (GasGiantObject) planet; - var gasGiant = Zone.PlanetInstances[key] as GasGiant; gas.Body.material.SetTexture("_ColorRamp", gasGiantData.Colors.ToGradient(!(planetData is SunData)).ToTexture()); // gasGiantData.AlbedoRotationSpeed.Subscribe(f => gas.SunMaterial.AlbedoRotationSpeed = f); // gasGiantData.FirstOffsetRotationSpeed.Subscribe(f => gas.SunMaterial.FirstOffsetRotationSpeed = f); // gasGiantData.SecondOffsetRotationSpeed.Subscribe(f => gas.SunMaterial.SecondOffsetRotationSpeed = f); // gasGiantData.FirstOffsetDomainRotationSpeed.Subscribe(f => gas.SunMaterial.FirstOffsetDomainRotationSpeed = f); // gasGiantData.SecondOffsetDomainRotationSpeed.Subscribe(f => gas.SunMaterial.SecondOffsetDomainRotationSpeed = f); - gas.GravityWaves.transform.localScale = gasGiant.GravityWavesRadius * Vector3.one; - gas.GravityWaves.material.SetFloat("_Depth", gasGiant.GravityWavesDepth); - gas.GravityWaves.material.SetFloat("_Frequency", Settings.PlanetSettings.WaveFrequency.Evaluate(planetData.Mass)); - //gas.WaveScroll.Speed = Properties.GravitySettings.WaveSpeed.Evaluate(f); } else { @@ -584,8 +578,6 @@ public class ZoneRenderer : MonoBehaviour planet.Value.Body.transform.localPosition = new Vector3(0, height + planetInstance.BodyRadius * 2, 0); if (planet.Value is GasGiantObject gasGiantObject) { - gasGiantObject.GravityWaves.material.SetFloat("_Phase", - Zone.Time * ((GasGiant) Zone.PlanetInstances[planet.Key]).GravityWavesSpeed); if (!(planet.Value is SunObject)) { var toParent = normalize(Zone.GetOrbitPosition(Zone.Orbits[planetInstance.BodyData.Orbit.Key].Data.Parent.Key) - p); @@ -618,19 +610,13 @@ public class ZoneRenderer : MonoBehaviour } //var fogPos = FogCameraParent.position; - SectorBoundaryBrush.material.SetFloat("_Power", Settings.PlanetSettings.ZoneDepthExponent); - SectorBoundaryBrush.material.SetFloat("_Depth", Settings.PlanetSettings.ZoneDepth + Settings.PlanetSettings.ZoneBoundaryFog); - var startDepth = Zone.PowerPulse(Settings.MinimapZoneGravityRange, Settings.PlanetSettings.ZoneDepthExponent) * - Settings.PlanetSettings.ZoneDepth; - var depthRange = Settings.PlanetSettings.ZoneDepth - startDepth + maxDepth; + var depthRange = Mathf.Max(maxDepth, 1f); foreach (var mat in MapGravityMaterials) { - mat.SetFloat("_StartDepth", startDepth); + mat.SetFloat("_StartDepth", 0f); mat.SetFloat("_DepthRange", depthRange); } - //Shader.SetGlobalFloat("_GridOffset", Settings.PlanetSettings.ZoneBoundaryFog); // var gravPos = MinimapGravityQuad.transform.position; - // gravPos.y = -Settings.PlanetSettings.ZoneDepth - _maxDepth; // MinimapGravityQuad.transform.position = gravPos; // MinimapTintQuad.transform.position = gravPos - Vector3.up*10; } diff --git a/tests/Aetheria.Shared.Tests/EvasionTermTests.cs b/tests/Aetheria.Shared.Tests/EvasionTermTests.cs index 0b0688457..ffd99d16e 100644 --- a/tests/Aetheria.Shared.Tests/EvasionTermTests.cs +++ b/tests/Aetheria.Shared.Tests/EvasionTermTests.cs @@ -81,11 +81,15 @@ public sealed partial class RunStartTests // Idle long enough for stats and power to reach their idle state, then at rest, facing +y, // and, after three seconds of coasting, with nothing observed: the track forgets on its own. + // The ship is stepped alone, so the zone's wells pull it while it idles; settling is about stats and power, so + // it comes back to where it stood. private static void EvSettle(Ship ship) { + var stood = ship.Position.xz; ship.MovementDirection = float2(0, 0); EvFace(ship, float3(0, 0, 1)); for (var step = 0; step < 180; step++) ship.Update(EvDt); + ship.Position.xz = stood; ship.Velocity = float2(0, 0); ship.Direction = float2(0, 1); } diff --git a/tests/Aetheria.Shared.Tests/FireControlTests.cs b/tests/Aetheria.Shared.Tests/FireControlTests.cs index 24919fba9..d4d0ba50d 100644 --- a/tests/Aetheria.Shared.Tests/FireControlTests.cs +++ b/tests/Aetheria.Shared.Tests/FireControlTests.cs @@ -41,7 +41,11 @@ public sealed class FireControlTests : IDisposable // A hull with one weapon hardpoint per (rotation, firingArc) pair, laid out along the X axis of a 1xN // shape so each gets its own cell. FiringArc 0 on a hardpoint means "use GameplaySettings.FiringArc". private (ItemManager items, Ship ship, EquippedItem[] weapons) BuildShipWithHardpoints( - params (ItemRotation rotation, float firingArc)[] hardpoints) + params (ItemRotation rotation, float firingArc)[] hardpoints) => BuildShipWithHardpoints(false, hardpoints); + + // `well` digs the zone one well at the origin (WellFixture), so heights differ across the zone. + private (ItemManager items, Ship ship, EquippedItem[] weapons) BuildShipWithHardpoints( + bool well, (ItemRotation rotation, float firingArc)[] hardpoints) { var hullShape = new Shape(Math.Max(hardpoints.Length, 3), 3); foreach (var cell in hullShape.AllCoordinates) hullShape[cell] = true; @@ -78,13 +82,16 @@ public sealed class FireControlTests : IDisposable buildCache.FlushAsync().Wait(); } - _openCache = AetheriaStores.Open(Catalog, catalogWritable: true); + _openCache = AetheriaStores.Open(Catalog, Path.Combine(_root, "Run.cc"), catalogWritable: true); var ledger = new ProvenanceLedger(); ledger.Lots[1] = new Lot { Origin = new Attributed(), Quality = 1 }; // hull for (var i = 0; i < hardpoints.Length; i++) ledger.Lots[2 + i] = new Lot { Origin = new Attributed(), Quality = 1 }; // guns var items = new ItemManager(_openCache, ledger, TestSettings(), _ => { }); - var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null); + var planets = new PlanetSettings(); + var pack = new ZonePack(); + if (well) WellFixture.Dig(_openCache, pack, planets, pack.Radius, 1000f, 1f); + var zone = new Zone(items, planets, pack, new GalaxyZone { Name = "Test Zone", Owner = null }, null); var hullRef = items.ItemData.RefOf(items.ItemData.GetByName("TestHull")); var hull = new EquippableItem { Data = hullRef, Durability = 1, Lot = 1 }; @@ -182,10 +189,10 @@ public sealed class FireControlTests : IDisposable } // A shooter with one in-range, in-arc gun, a detected target at `targetPosition`, and a Minion driving the shooter. - private (Ship shooter, Ship target, Zone zone) BuildCombatEncounter(float3 targetPosition, float projectileSpeed = 0f, bool twoTargetGuns = false) + private (Ship shooter, Ship target, Zone zone) BuildCombatEncounter(float3 targetPosition, float projectileSpeed = 0f, bool twoTargetGuns = false, bool well = false) { var mounts = twoTargetGuns ? new[] { (ItemRotation.None, 0f), (ItemRotation.None, 0f) } : new[] { (ItemRotation.None, 0f) }; - var (items, shooter, weapons) = BuildShipWithHardpoints(mounts); + var (items, shooter, weapons) = BuildShipWithHardpoints(well, mounts); var weaponData = (InstantWeaponData) weapons[0].Data.Behaviors.Single(b => b is InstantWeaponData); weaponData.Damage = new PerformanceStat { Min = 10, Max = 10 }; weaponData.Cooldown = new PerformanceStat { Min = 1, Max = 1 }; @@ -376,9 +383,7 @@ public sealed class FireControlTests : IDisposable [Fact] public void TheAIMeasuresTheTargetOnTheGroundPlane() { - var (shooter, target, zone) = BuildCombatEncounter(float3(0, 0, 600), twoTargetGuns: true); - zone.Settings.ZoneDepth = 1000f; - zone.Settings.ZoneDepthExponent = 1f; + var (shooter, target, zone) = BuildCombatEncounter(float3(0, 0, 600), twoTargetGuns: true, well: true); var strong = target.Equipment.Single(x => x.Data.Name == "Gun"); var weak = target.Equipment.Single(x => x.Data.Name == "WeakGun"); zone.Update(1f); // heights follow the zone's depth from the first update diff --git a/tests/Aetheria.Shared.Tests/FloatingBodyTests.cs b/tests/Aetheria.Shared.Tests/FloatingBodyTests.cs index 5b98ba649..e4ee47cfe 100644 --- a/tests/Aetheria.Shared.Tests/FloatingBodyTests.cs +++ b/tests/Aetheria.Shared.Tests/FloatingBodyTests.cs @@ -217,7 +217,7 @@ public sealed class FloatingBodyTests : IDisposable // the sign or changing how delta enters the line fails here, not only the Zone function's pin above. private Ship ShipAt(float x, out Zone zone) { - var cache = AetheriaStores.Open(Path.Combine(_root, Guid.NewGuid().ToString("N") + ".cc"), catalogWritable: true); + var cache = AetheriaStores.Open(Path.Combine(_root, Guid.NewGuid().ToString("N") + ".cc"), Path.Combine(_root, Guid.NewGuid().ToString("N") + ".run.cc"), catalogWritable: true); _caches.Add(cache); var hullShape = new Shape(5, 5); foreach (var cell in hullShape.AllCoordinates) hullShape[cell] = true; @@ -230,8 +230,10 @@ public sealed class FloatingBodyTests : IDisposable QualityPriceModifier = new ExponentialLerp() }; var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { }); - var planets = new PlanetSettings { ZoneDepth = 1000, ZoneDepthExponent = 1, GravityStrength = 1 }; - zone = new Zone(items, planets, new ZonePack { Radius = 1000 }, new GalaxyZone { Name = "Test Zone", Owner = null }, null); + var planets = new PlanetSettings { GravityStrength = 1 }; + var pack = new ZonePack { Radius = 1000 }; + WellFixture.Dig(cache, pack, planets, 1000, 1000, 1); + zone = new Zone(items, planets, pack, new GalaxyZone { Name = "Test Zone", Owner = null }, null); var design = cache.GetByName("Skiff"); var hull = new EquippableItem { diff --git a/tests/Aetheria.Shared.Tests/FlowFieldTests.cs b/tests/Aetheria.Shared.Tests/FlowFieldTests.cs index 2c08c3aa1..4924daa81 100644 --- a/tests/Aetheria.Shared.Tests/FlowFieldTests.cs +++ b/tests/Aetheria.Shared.Tests/FlowFieldTests.cs @@ -19,7 +19,7 @@ public sealed class FlowFieldTests : IDisposable public FlowFieldTests() { Directory.CreateDirectory(_root); - _cache = AetheriaStores.Open(Path.Combine(_root, "flow.cc"), catalogWritable: true); + _cache = AetheriaStores.Open(Path.Combine(_root, "flow.cc"), Path.Combine(_root, "flow-run.cc"), catalogWritable: true); _cache.Upsert(new VerseGrammar { Revision = 1 }); _cache.FlushAsync().Wait(); var settings = new GameplaySettings @@ -46,9 +46,13 @@ public sealed class FlowFieldTests : IDisposable }; // A zone at `time` (the pack's clock, as a saved zone restores it), flat unless `depth` digs the zone's own well. - private Zone ZoneWith(FlowSettings flow, double time = 0, float depth = 0f) => new Zone(_items, - new PlanetSettings { Flow = flow, ZoneDepth = depth, ZoneDepthExponent = 2f }, - new ZonePack { Time = time }, new GalaxyZone { Name = "Flow Zone", Owner = null }, null); + private Zone ZoneWith(FlowSettings flow, double time = 0, float depth = 0f) + { + var planets = new PlanetSettings { Flow = flow }; + var pack = new ZonePack { Time = time }; + if (depth > 0f) WellFixture.Dig(_items.ItemData, pack, planets, pack.Radius, depth, 2f); + return new Zone(_items, planets, pack, new GalaxyZone { Name = "Flow Zone", Owner = null }, null); + } private static readonly float3[] Probes = { diff --git a/tests/Aetheria.Shared.Tests/GravitySubtractTests.cs b/tests/Aetheria.Shared.Tests/GravitySubtractTests.cs new file mode 100644 index 000000000..7f89cd0fd --- /dev/null +++ b/tests/Aetheria.Shared.Tests/GravitySubtractTests.cs @@ -0,0 +1,164 @@ +using System; +using System.IO; +using CultMath; +using GameCult.Caching; +using Xunit; +using float2 = CultMath.float2; +using float3 = CultMath.float3; + +// flatspace-no-bowl-no-ripples (aetheria-gravity target r3): a zone's height is the sum of its bodies' wells and +// nothing else. No zone-wide bowl sized by the pack, and no gas-giant or sun ripple that varies with zone time. +public sealed class GravitySubtractTests : IDisposable +{ + private readonly string _root = Path.Combine(Path.GetTempPath(), "aetheria-gravsub-" + Guid.NewGuid().ToString("N")); + private readonly CultCache _cache; + private readonly ItemManager _items; + + public GravitySubtractTests() + { + Directory.CreateDirectory(_root); + _cache = AetheriaStores.Open(Path.Combine(_root, "grav.cc"), Path.Combine(_root, "grav-run.cc"), catalogWritable: true); + _cache.Upsert(new VerseGrammar { Revision = 1 }); + _cache.FlushAsync().Wait(); + var settings = new GameplaySettings + { + DefaultEntitySettings = new EntitySettings(), + Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Rarity = 0, Color = new float3(1, 1, 1) } }, + QualityPriceModifier = new ExponentialLerp(), + TargetDetectionInfoThreshold = .1f, TargetGearInfoThreshold = .8f, + FiringArc = 170, CommitHorizon = .5f, SchematicCellSize = 2f + }; + _items = new ItemManager(_cache, new ProvenanceLedger(), settings, _ => { }); + } + + public void Dispose() + { + _cache.Dispose(); + Directory.Delete(_root, true); + } + + // Curves for a body whose well, light and orbit all vary with mass, so a body that read a different + // mass or multiplier than its twin would show it. + private static PlanetSettings Planets() => new PlanetSettings + { + GravityStrength = 1, + BodyRadius = new ExponentialCurve { Exponent = .25f, Multiplier = 3f }, + GravityRadius = new ExponentialCurve { Exponent = .25f, Multiplier = 500f }, + GravityDepth = new ExponentialCurve { Exponent = .175f, Multiplier = 30f }, + LightRadius = new ExponentialCurve { Exponent = .25f, Multiplier = 300f }, + OrbitPeriod = new ExponentialCurve() + }; + + private Zone Bare(float packRadius) => new Zone(_items, Planets(), new ZonePack { Radius = packRadius }, + new GalaxyZone { Name = "Bare Zone", Owner = null }, null); + + [Fact] + public void AZoneWithNoBodiesIsFlat() + { + foreach (var packRadius in new[] { 1000f, 2500f }) + { + var zone = Bare(packRadius); + foreach (var r in new[] { 0f, 300f, 577f, 900f, 2000f }) + { + var at = new float2(r, 0); + Assert.Equal(0f, zone.GetHeight(at)); + Assert.Equal(0f, zone.GetForce(at).x); + Assert.Equal(0f, zone.GetForce(at).y); + var inPlane = new float2(0, r); + Assert.Equal(0f, zone.GetHeight(inPlane)); + Assert.Equal(0f, zone.GetForce(inPlane).x); + Assert.Equal(0f, zone.GetForce(inPlane).y); + } + } + } + + // The fixture the five bowl tests share reproduces the zone bowl it replaced (map B14): the bowl was + // -PowerPulse(r / (2 R), exponent) x depth, written out here from its definition, deepest at the centre and + // zero from r = R on, so a fixture whose radius or depth drifted from the bowl would change every pull it feeds. + [Fact] + public void TheWellFixtureReproducesTheFormerBowl() + { + foreach (var (radius, depth, exponent, at, expected) in new[] + { + (1000f, 1000f, 1f, 0f, -1000f), (1000f, 1000f, 1f, 400f, -840f), (1000f, 1000f, 1f, 1000f, 0f), + (2000f, 64f, 2f, 1000f, -36f), (2000f, 64f, 2f, 300f, -61.1524f) + }) + { + var planets = Planets(); + var pack = new ZonePack { Radius = radius }; + WellFixture.Dig(_cache, pack, planets, radius, depth, exponent); + var zone = new Zone(_items, planets, pack, new GalaxyZone { Name = "Bowl Zone", Owner = null }, null); + Assert.InRange(zone.GetHeight(new float2(at, 0)), expected - 1e-2f, expected + 1e-2f); + } + } + + // One body of `data` at the origin of a zone whose only body it is. + private Zone WithBody(BodyData data) + { + var orbit = _cache.Upsert(new OrbitData { FixedPosition = float2.zero }).Key; + data.Orbit = new CultRecordRef(orbit); + data.Mass = 100f; + data.GravityRadiusMultiplier = 1.5f; + data.GravityDepthMultiplier = .7f; + data.GravityDepthExponent = 2f; + var body = _cache.Upsert(data).Key; + var pack = new ZonePack { Radius = 1000f }; + pack.Orbits.Add(new CultRecordRef(orbit)); + pack.Planets.Add(new CultRecordRef(body)); + return new Zone(_items, Planets(), pack, new GalaxyZone { Name = "Giant " + data.GetType().Name, Owner = null }, null); + } + + // The wells are the whole of a giant's height at every point, not just at a few radii: a ripple confined to + // any annulus, or to a band derived from the body's radius, shows against the plain body somewhere in a sweep + // that runs from the centre to well past the well's support. + [Fact] + public void AGiantsWellIsAPlainWellAtEveryTime() + { + var plain = WithBody(new PlanetData()); + var giants = new[] { WithBody(new GasGiantData()), WithBody(new SunData()) }; + var radii = new System.Collections.Generic.List { 0.5f }; + for (var r = 1f; r <= 100f; r += 1f) radii.Add(r); + for (var r = 105f; r <= 2000f; r += 5f) radii.Add(r); + var angles = new[] { 0f, 0.7f, 2.1f, 4.0f }; + foreach (var time in new[] { 0f, 37.5f, 91.25f }) + { + // Zone time 0 is the zones as built; each later time is one Update to that time. + var step = time - _clock; + if (step > 0f) + { + plain.Update(step); + foreach (var giant in giants) giant.Update(step); + _clock = time; + } + foreach (var r in radii) + foreach (var a in angles) + { + var at = new float2(r * (float)Math.Cos(a), r * (float)Math.Sin(a)); + foreach (var giant in giants) + { + Assert.Equal(plain.GetHeight(at), giant.GetHeight(at)); + Assert.Equal(plain.GetForce(at).x, giant.GetForce(at).x); + Assert.Equal(plain.GetForce(at).y, giant.GetForce(at).y); + } + } + Assert.True(plain.GetHeight(new float2(50f, 0)) < 0f, "the plain body digs a well here, so there is something to compare"); + } + } + + private float _clock; + + // CultCache has no primitive for reserving a removed MessagePack key; the wave keys of the gas giant (14-17) + // must not come back with a different meaning, because stored records may still carry them. + [Fact] + public void AGasGiantRecordNeverReusesTheRetiredWaveKeys() + { + var keys = new System.Collections.Generic.List(); + for (var t = typeof(GasGiantData); t != null; t = t.BaseType) + foreach (var m in t.GetMembers(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly)) + if (m.GetCustomAttributes(typeof(MessagePack.KeyAttribute), false) is MessagePack.KeyAttribute[] ks) + foreach (var k in ks) if (k.IntKey.HasValue) keys.Add(k.IntKey.Value); + Assert.NotEmpty(keys); + foreach (var retired in new[] { 14, 15, 16, 17 }) Assert.DoesNotContain(retired, keys); + } +} diff --git a/tests/Aetheria.Shared.Tests/LootTests.cs b/tests/Aetheria.Shared.Tests/LootTests.cs index 6b736fa23..5a5bfce7d 100644 --- a/tests/Aetheria.Shared.Tests/LootTests.cs +++ b/tests/Aetheria.Shared.Tests/LootTests.cs @@ -61,7 +61,7 @@ public sealed class LootTests : IDisposable } private Lab Build(string zoneName = "Loot", float probability = .5f, float lifetime = 30f, float velocity = 25f, - PlanetSettings planets = null, float spread = 0f) + bool well = false, float spread = 0f) { var settings = new GameplaySettings { @@ -73,7 +73,7 @@ public sealed class LootTests : IDisposable UnaidedAccuracy = .05f, UnaidedTracking = 10f, UnaidedPrecision = 2f, AgentMinHitProbability = 0f, LootDropProbability = probability, LootDropVelocity = velocity, FloatingItemLifetime = lifetime }; - var cache = AetheriaStores.Open(Path.Combine(_root, Guid.NewGuid().ToString("N") + ".cc"), catalogWritable: true); + var cache = AetheriaStores.Open(Path.Combine(_root, Guid.NewGuid().ToString("N") + ".cc"), Path.Combine(_root, Guid.NewGuid().ToString("N") + ".run.cc"), catalogWritable: true); _caches.Add(cache); cache.Upsert(new VerseGrammar { Revision = 1 }); var hardpoint = new HardpointData { Type = HardpointType.Sensors, Position = new int2(0, 0), Shape = new Shape() }; @@ -108,7 +108,14 @@ public sealed class LootTests : IDisposable var ledger = new ProvenanceLedger(); for (var i = 1; i <= 9000; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 }; var items = new ItemManager(cache, ledger, settings, _ => { }); - var zone = new Zone(items, planets ?? new PlanetSettings(), new ZonePack { Radius = planets == null ? 0 : 1000 }, new GalaxyZone { Name = zoneName, Owner = null }, null); + var planets = new PlanetSettings(); + var pack = new ZonePack { Radius = well ? 1000 : 0 }; + if (well) + { + planets.GravityStrength = 1; + WellFixture.Dig(cache, pack, planets, 1000, 1000, 1); + } + var zone = new Zone(items, planets, pack, new GalaxyZone { Name = zoneName, Owner = null }, null); return new Lab { Items = items, Zone = zone }; } @@ -286,8 +293,7 @@ public sealed class LootTests : IDisposable [Fact] public void AFloatingItemDriftsAsABodyAndExpires() { - var planets = new PlanetSettings { ZoneDepth = 1000, ZoneDepthExponent = 1, GravityStrength = 1 }; - var lab = Build(lifetime: 3f, planets: planets); + var lab = Build(lifetime: 3f, well: true); var settings = lab.Items.GameplaySettings; var start = new float2(40, -25); var launch = new float2(6, 2); diff --git a/tests/Aetheria.Shared.Tests/MineTests.cs b/tests/Aetheria.Shared.Tests/MineTests.cs index c997041a4..4d42e0add 100644 --- a/tests/Aetheria.Shared.Tests/MineTests.cs +++ b/tests/Aetheria.Shared.Tests/MineTests.cs @@ -78,7 +78,7 @@ public sealed class MineTests : IDisposable FiringArc = 170, CommitHorizon = .5f, SchematicCellSize = 2f, UnaidedAccuracy = .05f, UnaidedTracking = 10f, UnaidedPrecision = 1000f, AgentMinHitProbability = 0f }; - var cache = AetheriaStores.Open(Path.Combine(_root, Guid.NewGuid().ToString("N") + ".cc"), catalogWritable: true); + var cache = AetheriaStores.Open(Path.Combine(_root, Guid.NewGuid().ToString("N") + ".cc"), Path.Combine(_root, Guid.NewGuid().ToString("N") + ".run.cc"), catalogWritable: true); _caches.Add(cache); cache.Upsert(new VerseGrammar { Revision = 1 }); HullData Hull(string name, Shape shape) => new HullData @@ -118,8 +118,10 @@ public sealed class MineTests : IDisposable var ledger = new ProvenanceLedger(); for (var i = 1; i <= 500; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 }; var items = new ItemManager(cache, ledger, settings, _ => { }); - var planets = well ? new PlanetSettings { ZoneDepth = 1000, ZoneDepthExponent = 1, GravityStrength = 1 } : new PlanetSettings(); - var zone = new Zone(items, planets, new ZonePack { Radius = well ? 1000 : 0 }, new GalaxyZone { Name = "Mines", Owner = null }, null); + var planets = well ? new PlanetSettings { GravityStrength = 1 } : new PlanetSettings(); + var pack = new ZonePack { Radius = well ? 1000 : 0 }; + if (well) WellFixture.Dig(cache, pack, planets, 1000, 1000, 1); + var zone = new Zone(items, planets, pack, new GalaxyZone { Name = "Mines", Owner = null }, null); var lab = new Lab { Items = items, Zone = zone, Data = data, ItemData = cache.GetByName("Mine Layer") }; var layerRef = items.ItemData.RefOf(items.ItemData.GetByName("Layer")); diff --git a/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs b/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs index 8ad9d8fc3..273b63547 100644 --- a/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs +++ b/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs @@ -58,10 +58,6 @@ public sealed partial class MiningCut3Tests : IDisposable BodyRadius = new ExponentialCurve(), GravityRadius = new ExponentialCurve(), GravityDepth = new ExponentialCurve(), - WaveDepth = new ExponentialCurve(), - WaveRadius = new ExponentialCurve(), - WaveSpeed = new ExponentialCurve(), - WaveFrequency = new ExponentialCurve(), LightRadius = new ExponentialCurve { Constant = lightRadius } }; diff --git a/tests/Aetheria.Shared.Tests/WellFixture.cs b/tests/Aetheria.Shared.Tests/WellFixture.cs new file mode 100644 index 000000000..ab6922cd7 --- /dev/null +++ b/tests/Aetheria.Shared.Tests/WellFixture.cs @@ -0,0 +1,21 @@ +using CultMath; +using GameCult.Caching; + +// The one fixture that gives a test zone a slope: a single body on a parentless orbit at the origin, with +// the curves a body needs. Gravity height is the sum of body wells, so a test that wants ground to slide on +// digs it here and nowhere else. `radius` is the zone radius the well is sized to (its well reaches 2 x radius, +// as the former bowl did); the well is deepest at the centre and zero at and beyond half its radius. +internal static class WellFixture +{ + public static void Dig(CultCache cache, ZonePack pack, PlanetSettings settings, float radius, float depth, float exponent) + { + settings.BodyRadius = new ExponentialCurve(); + settings.OrbitPeriod = new ExponentialCurve(); + settings.GravityRadius = new ExponentialCurve { Constant = 2f * radius }; + settings.GravityDepth = new ExponentialCurve { Constant = depth }; + var orbit = cache.Upsert(new OrbitData { FixedPosition = float2.zero }).Key; + var body = cache.Upsert(new PlanetData { Orbit = new CultRecordRef(orbit), GravityDepthExponent = exponent }).Key; + pack.Orbits.Add(new CultRecordRef(orbit)); + pack.Planets.Add(new CultRecordRef(body)); + } +} diff --git a/tests/Aetheria.Shared.Tests/ZonePlacedAtBuildTests.cs b/tests/Aetheria.Shared.Tests/ZonePlacedAtBuildTests.cs new file mode 100644 index 000000000..002f0f2df --- /dev/null +++ b/tests/Aetheria.Shared.Tests/ZonePlacedAtBuildTests.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.IO; +using CultMath; +using GameCult.Caching; +using Xunit; +using static CultMath.math; +using float2 = CultMath.float2; +using float3 = CultMath.float3; + +// Aetheria gravity, target invariant zone-valid-before-tick: a zone's orbits are placed when it is built, so +// its wells, lights and body poses are right before the first Update (follow-up +// aetheria-release:follow_up:zone-orbits-unplaced-before-first-tick). The zone here is the production +// constructor reading a pack; nothing places an orbit but that constructor and Zone.Update. +public sealed class ZonePlacedAtBuildTests : IDisposable +{ + private static readonly float2 RootAt = new float2(300f, -200f); + private const float Distance = 400f; + private const float Period = 40f; + + private readonly string _root = Path.Combine(Path.GetTempPath(), "aetheria-zoneplaced-" + Guid.NewGuid().ToString("N")); + private readonly List _caches = new List(); + + public ZonePlacedAtBuildTests() => Directory.CreateDirectory(_root); + + public void Dispose() + { + foreach (var cache in _caches) cache.Dispose(); + Directory.Delete(_root, true); + } + + private static GameplaySettings GameSettings() => new GameplaySettings + { + DefaultEntitySettings = new EntitySettings(), + Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Rarity = 0, Color = new float3(1, 1, 1) } }, + QualityPriceModifier = new ExponentialLerp() + }; + + private static ExponentialCurve Flat(float v) => new ExponentialCurve { Constant = v }; + + // Flat space (no zone depth) with one planet whose well reaches 500 u, a fixed root orbit at RootAt and the + // planet orbiting it at Distance from `phase`. Every orbit period is Period seconds. + private static PlanetSettings Settings() => new PlanetSettings + { + OrbitPeriod = Flat(Period), + BodyRadius = Flat(20f), + GravityRadius = Flat(500f), + GravityDepth = Flat(100f), + GravityStrength = 1f, + LightRadius = Flat(0f) + }; + + private sealed class Scene + { + public CultCache Cache; + public ItemManager Items; + public HullData Hull; + public Zone Zone; + public CultRecordKey PlanetOrbit; + } + + private Scene Build(float phase, double time, bool moonFirst = false) + { + var dir = Path.Combine(_root, $"zone{_caches.Count}"); + Directory.CreateDirectory(dir); + var cache = AetheriaStores.Open(Path.Combine(dir, "Aetheria.cc"), Path.Combine(dir, "run.cc"), catalogWritable: true); + _caches.Add(cache); + cache.Upsert(new VerseGrammar { Revision = 1 }); + var shape = new Shape(5, 5); + foreach (var cell in shape.AllCoordinates) shape[cell] = true; + var hull = new HullData { Name = "Skiff", HullType = HullType.Ship, Shape = shape, Durability = 10, Mass = 1000 }; + cache.Upsert(hull); + var items = new ItemManager(cache, new ProvenanceLedger(), GameSettings(), _ => { }); + + var rootOrbit = cache.Upsert(new OrbitData { FixedPosition = RootAt }).Key; + var planetOrbit = cache.Upsert(new OrbitData + { + Parent = new CultRecordRef(rootOrbit), Distance = Distance, Phase = phase + }).Key; + var moonOrbit = cache.Upsert(new OrbitData + { + Parent = new CultRecordRef(planetOrbit), Distance = 100f + }).Key; + var planet = cache.Upsert(new PlanetData { Orbit = new CultRecordRef(planetOrbit), Mass = 1f }).Key; + var pack = new ZonePack + { + Planets = { new CultRecordRef(planet) }, + Radius = 5000f, + Mass = 10000f, + Time = time + }; + // The same three orbits either way; only the order the pack lists them in differs. + var listed = moonFirst ? new[] { moonOrbit, planetOrbit, rootOrbit } : new[] { rootOrbit, planetOrbit, moonOrbit }; + foreach (var key in listed) pack.Orbits.Add(new CultRecordRef(key)); + var zone = new Zone(items, Settings(), pack, new GalaxyZone { Name = "Placed", Owner = null }, null); + return new Scene { Cache = cache, Items = items, Hull = hull, Zone = zone, PlanetOrbit = planetOrbit }; + } + + [Theory] + [InlineData(.1f, 0.0)] + [InlineData(.35f, 0.0)] + [InlineData(.1f, 123.0)] + [InlineData(.35f, 123.0)] + public void ABodysWellSitsOnItsOrbitBeforeTheFirstTick(float phase, double time) + { + var s = Build(phase, time); + var orbit = s.Zone.Orbits[s.PlanetOrbit]; + var turns = (float) frac(time / orbit.Period); + var expected = RootAt + OrbitData.Evaluate(frac(turns + phase)) * Distance; + + Assert.InRange(length(orbit.Position - expected), 0f, .01f); + + // The well pulls toward the body's place: a point 30 u east of it is pushed west. + var force = s.Zone.GetForce(expected + new float2(30f, 0f)); + Assert.True(length(force) > 0f, "the well exerts a force"); + Assert.InRange(normalize(force).x, -1f, -.99f); + } + + // A parent's velocity is its own orbital velocity on the first tick and every tick after, whichever order + // the pack lists a moon and its planet in. dt differs per tick so a velocity taken from a fixed step fails. + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TheFirstTickVelocityIsTheOrbitsSpeed(bool moonFirst) + { + var s = Build(.35f, 0.0, moonFirst); + var expected = 2f * PI * Distance / Period; + foreach (var dt in new[] { 1f / 60f, 1f / 30f, 1f / 60f }) + { + s.Zone.Update(dt); + var speed = length(s.Zone.Orbits[s.PlanetOrbit].Velocity); + Assert.InRange(speed, expected * .99f, expected * 1.01f); + } + } + + [Fact] + public void AShipSteppedAloneFeelsTheBodyWhereItIs() + { + var s = Build(.1f, 0.0); + var planetAt = s.Zone.Orbits[s.PlanetOrbit].Position; + var ship = new Ship(s.Items, s.Zone, new EquippableItem + { + Data = s.Cache.RefOf(s.Hull), + Durability = s.Hull.Durability, + Lot = s.Items.Lots.Add(new Lot + { + Design = s.Cache.RefOf(s.Hull), Origin = new Attributed(), Quality = .5f, Roles = new List() + }) + }, new EntitySettings()); + s.Zone.Admit(ship, piloted: false); + var at = planetAt + new float2(0f, 60f); + ship.Position = float3(at.x, ship.Position.y, at.y); + ship.Velocity = float2.zero; + + ship.Update(1f / 60f); // the ship alone: no Zone.Update has run + + Assert.True(length(ship.Velocity) > 0f, "the ship is pulled"); + var toward = normalize(planetAt - at); + Assert.True(dot(normalize(ship.Velocity), toward) > .99f, "the pull is toward the planet, not toward the origin"); + } +}