diff --git a/Aetheria.State.Daemon.Smoke/Program.cs b/Aetheria.State.Daemon.Smoke/Program.cs index 9e9f9a8f1..2ba43ef73 100644 --- a/Aetheria.State.Daemon.Smoke/Program.cs +++ b/Aetheria.State.Daemon.Smoke/Program.cs @@ -235,8 +235,13 @@ internal sealed class AetheriaDaemonYmirSmokeChecks shader.Ref.Metadata["unity.volume.texturePort.currentSample"] == "_UndersampleCloudTex" && shader.Ref.Metadata["unity.volume.texturePort.history"] == "_MainTex" && shader.Ref.Metadata["unity.volume.matrixPort.previousViewProjection"] == "_PrevVP" && - shader.Ref.Metadata["unity.volume.floatPort.resetHistory"] == "_ResetHistory", + shader.Ref.Metadata["unity.volume.floatPort.resetHistory"] == "_ResetHistory", "provider asset metadata must own the concrete Unity volume-program ABI projected into runtime variants"); + var fogVolume = Flatten(surface.Surface.Root) + .Single(component => string.Equals(component.Kind, "field.volume3d", StringComparison.Ordinal)); + Require(fogVolume.Props.TryGetValue("floatParameters", out var fogParameters) && + fogParameters.Contains("compositeOpacity=1", StringComparison.Ordinal), + "the portable fog surface must retain the fossil cloud shader's full-strength composite default"); } private static AetheriaYmirWorldPhysics NewPhysics() @@ -265,10 +270,15 @@ internal sealed class AetheriaDaemonYmirSmokeChecks var body = new AetheriaRuntimeBodySnapshotCommit { BodyKey = "gravity-owner", + Kind = "gas_giant", GravityInfluenceCenterX = 0, GravityInfluenceCenterZ = 0, GravityInfluenceRadius = 100, - GravityWellDepth = 20 + GravityWellDepth = 20, + GravityWaveRadius = 80, + GravityWaveDepth = 3, + GravityWaveSpeed = 2, + GravityWaveFrequency = 6 }; var actor = Entity(0, 10, "player"); actor.PositionZ = 0; @@ -311,6 +321,15 @@ internal sealed class AetheriaDaemonYmirSmokeChecks var fogSurfaceSplatIndex = splats.Splats.SourceKey .Select((sourceKey, index) => (sourceKey, index)) .Single(value => value.sourceKey == "environment.gravity_terrain:fog.surface_height").index; + var gravityWaveIndex = splats.Splats.SourceKey + .Select((sourceKey, index) => (sourceKey, index)) + .Single(value => value.sourceKey == body.BodyKey + ":gravity.wave").index; + var fogSurfaceWaveIndex = splats.Splats.SourceKey + .Select((sourceKey, index) => (sourceKey, index)) + .Single(value => value.sourceKey == body.BodyKey + ":fog.surface_height.wave").index; + var fogPatchWaveIndex = splats.Splats.SourceKey + .Select((sourceKey, index) => (sourceKey, index)) + .Single(value => value.sourceKey == body.BodyKey + ":fog.patch_height.wave").index; Require(splats.Splats.ValueR[bodySplatIndex] < 0, "render-splat projection must convert positive canonical body depth into a negative gravity-height value"); Require(splats.Splats.Falloff[terrainSplatIndex] == EveFieldsSplatFalloffs.PowerPulse && @@ -328,6 +347,81 @@ internal sealed class AetheriaDaemonYmirSmokeChecks splats.Splats.ValueR[fogSurfaceSplatIndex] > 0 && splats.Splats.Falloff[fogSurfaceSplatIndex] == EveFieldsSplatFalloffs.PowerPulse, "the fog surface must project positive fossil PowerBrush depth without changing negative gameplay terrain height"); + foreach (var waveIndex in new[] { gravityWaveIndex, fogSurfaceWaveIndex, fogPatchWaveIndex }) + { + Require(splats.Splats.SourceKind[waveIndex] == EveFieldsSplatSourceKinds.AnimatedRadialCosine && + splats.Splats.HalfExtentX[waveIndex] == body.GravityWaveRadius * 0.5 && + splats.Splats.FrequencyX[waveIndex] == body.GravityWaveFrequency && + splats.Splats.FrequencyY[waveIndex] == 1.25 && + splats.Splats.AnimationSpeed[waveIndex] == body.GravityWaveSpeed && + splats.Splats.Falloff[waveIndex] == EveFieldsSplatFalloffs.PowerPulse && + splats.Splats.FalloffExponent[waveIndex] == 8, + "gravity, fog surface, and fog patch height must share the exact fossil radial-wave source"); + } + var patchLayerIndex = splats.Layers + .Select((layer, index) => (layer, index)) + .Single(value => value.layer.LayerKey == EveFieldsSplatLayerKeys.FogPatch).index; + var patchLayer = splats.Layers[patchLayerIndex]; + var patchMicroIndex = splats.Splats.SourceKey + .Select((sourceKey, index) => (sourceKey, index)) + .Single(value => value.sourceKey == "environment.fog_patch.depth_micro").index; + var patchMacroIndex = splats.Splats.SourceKey + .Select((sourceKey, index) => (sourceKey, index)) + .Single(value => value.sourceKey == "environment.fog_patch.depth_macro").index; + var ambientTintIndex = splats.Splats.SourceKey + .Select((sourceKey, index) => (sourceKey, index)) + .Single(value => value.sourceKey == "environment.fog_tint.ambient").index; + Require(patchLayer.BlendMode == EveFieldsSplatBlendModes.Add, + "fossil patch depth brushes must add instead of taking the maximum"); + Require(splats.Splats.SourceKind[patchMicroIndex] == EveFieldsSplatSourceKinds.AnimatedSimplexNoise && + splats.Splats.SourceFlags[patchMicroIndex] == EveFieldsSplatSourceFlags.AbsoluteValue && + splats.Splats.FrequencyX[patchMicroIndex] == 0.005 && + splats.Splats.AnimationSpeed[patchMicroIndex] == 0.025 && + splats.Splats.HalfExtentX[patchMicroIndex] == 1500, + "fog micro-patch must retain the fossil world-space animated absolute-simplex brush"); + Require(splats.Splats.SourceKind[patchMacroIndex] == EveFieldsSplatSourceKinds.AnimatedCellNoiseB && + splats.Splats.FrequencyX[patchMacroIndex] == 0.001 && + splats.Splats.AnimationSpeed[patchMacroIndex] == 0.025 && + splats.Splats.ValueR[patchMacroIndex] == 20, + "fog macro-patch must retain the fossil moving cellular-B brush"); + Require(splats.Splats.ValueR[ambientTintIndex] > 0 && + splats.Splats.ValueB[ambientTintIndex] > splats.Splats.ValueG[ambientTintIndex] && + splats.Splats.ValueG[ambientTintIndex] > splats.Splats.ValueR[ambientTintIndex], + "fog tint must include the fossil ambient blue brush before local body lights"); + Require(!splats.Splats.SourceKey.Contains("environment.fog_patch") && + !splats.Splats.SourceKey.Contains("environment.fog_patch_height"), + "the viewport-local placeholder fog producers must remain deleted"); + var ordinaryPlanetSplats = AetheriaRuntimeGameDocuments.RenderSplatsViewport( + new AetheriaRuntimeDaemonFrameDocument + { + Run = new AetheriaRuntimeRunCheckpointCommit + { + CurrentZoneIndex = 0, + Zones = + [ + new AetheriaRuntimeZoneSnapshotCommit + { + ZoneIndex = 0, + Bodies = + [ + new AetheriaRuntimeBodySnapshotCommit + { + BodyKey = "ordinary-planet", + Kind = "planet", + GravityInfluenceRadius = 20, + GravityWaveRadius = 20, + GravityWaveDepth = 4 + } + ] + } + ] + } + }, + new AetheriaRuntimeViewportBounds { MinX = -30, MinY = -30, MaxX = 30, MaxY = 30 }); + Require(!ordinaryPlanetSplats.Splats.SourceKey.Any(key => + key.StartsWith("ordinary-planet:", StringComparison.Ordinal) && + key.EndsWith(".wave", StringComparison.Ordinal)), + "ordinary planets must not acquire gas-giant gravity-wave visuals merely because compatibility rows contain wave values"); var run = new AetheriaRuntimeRunCheckpointCommit { diff --git a/Aetheria.State.Daemon/Program.cs b/Aetheria.State.Daemon/Program.cs index eed967d5b..deea5910e 100644 --- a/Aetheria.State.Daemon/Program.cs +++ b/Aetheria.State.Daemon/Program.cs @@ -2875,6 +2875,7 @@ static AetheriaRuntimeBodySnapshotCommit ToRuntimeBody( GravityWaveRadius = body.GravityWaveRadius, GravityWaveDepth = body.GravityWaveDepth, GravityWaveSpeed = body.GravityWaveSpeed, + GravityWaveFrequency = renderSettings.ResolveGravityWaveFrequency(body.Mass), IconSize = renderSettings.ResolveBodyIconSize(body.Mass) }; } @@ -2886,7 +2887,10 @@ static void ApplyDaemonRenderSettings( foreach (var zone in run.Zones ?? Array.Empty()) { foreach (var body in zone.Bodies ?? Array.Empty()) + { body.IconSize = renderSettings.ResolveBodyIconSize(body.Mass); + body.GravityWaveFrequency = renderSettings.ResolveGravityWaveFrequency(body.Mass); + } } } diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index e6af9034d..d920f852c 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -792,9 +792,9 @@ static void RequireSharedEvePackagesImportedFromEveRepo(string root) var requiredManifestSymbols = new[] { "\"org.gamecult.cultlib\": \"https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.13\"", - "\"org.gamecult.eve.plugin-fields\": \"https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.2.0\"", + "\"org.gamecult.eve.plugin-fields\": \"https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.2.3\"", "\"org.gamecult.eve.surface\": \"https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.2\"", - "\"org.gamecult.eve.unity-scene\": \"https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.38\"" + "\"org.gamecult.eve.unity-scene\": \"https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.44\"" }; var missingManifestSymbols = requiredManifestSymbols @@ -811,9 +811,9 @@ static void RequireSharedEvePackagesImportedFromEveRepo(string root) var requiredLockSymbols = new[] { "cultlib-unity-v1.0.13", - "eve-plugin-fields-unity-v0.2.0", + "eve-plugin-fields-unity-v0.2.3", "eveunity-surface-v0.2.2", - "eveunity-scene-v0.3.38", + "eveunity-scene-v0.3.44", "\"source\": \"git\"" }; @@ -2400,7 +2400,7 @@ static void RequireDaemonRenderQueryAuthority(string root) "public static double ResolveZoneRenderRadius(", "public static AetheriaRuntimeGravityTerrainBand QueryGravityTerrainBand(", "zone.GravityTerrainRadius", - "zone.GravityTerrainWaveFrequency", + "brush.WaveFrequency", "public static AetheriaRuntimeDaemonBodyPose[] QueryBodyPoses(", "public static int QueryBodyPoses(", "new AetheriaRuntimeDaemonBodyPose(", @@ -15730,6 +15730,15 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) "ActionGameManager must not expose presentation environment as manager-global gameplay state; visual sampling should read presentation settings directly."); } + var sceneSplatSourcePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaSceneRenderSplatSource.cs"); + if (File.Exists(sceneSplatSourcePath) || + volumeSampling.Contains("AetheriaSceneRenderSplatSource", StringComparison.Ordinal) || + volumeSampling.Contains("ResolveSceneSplatDocument", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Unity scene mocks must not publish a competing fog-splat document; VolumeSampling may lower daemon-owned runtime splats only."); + } + var mapRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MapRenderer.cs"); var mapRenderer = File.Exists(mapRendererPath) ? File.ReadAllText(mapRendererPath) diff --git a/Aetheria.Unity/Packages/manifest.json b/Aetheria.Unity/Packages/manifest.json index d0c53efca..258eb92fe 100644 --- a/Aetheria.Unity/Packages/manifest.json +++ b/Aetheria.Unity/Packages/manifest.json @@ -7,8 +7,8 @@ "com.unity.modules.uielements": "1.0.0", "com.unity.render-pipelines.universal": "17.4.0", "org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.13", - "org.gamecult.eve.plugin-fields": "https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.2.1", + "org.gamecult.eve.plugin-fields": "https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.2.3", "org.gamecult.eve.surface": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.2", - "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.42" + "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.44" } } diff --git a/Aetheria.Unity/Packages/packages-lock.json b/Aetheria.Unity/Packages/packages-lock.json index e86b6ed7a..5b29ec668 100644 --- a/Aetheria.Unity/Packages/packages-lock.json +++ b/Aetheria.Unity/Packages/packages-lock.json @@ -126,13 +126,13 @@ "hash": "feb5c71513e71d681699f462fe3682b3168c6f73" }, "org.gamecult.eve.plugin-fields": { - "version": "https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.2.1", + "version": "https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.2.3", "depth": 0, "source": "git", "dependencies": { "org.gamecult.cultlib": "1.0.13" }, - "hash": "59924e324cd10f293c214c8e6bb2c794a32d39a0" + "hash": "c5a4a75c1b727499b16c2dae1895f29e2a9f72f0" }, "org.gamecult.eve.surface": { "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.2", @@ -144,17 +144,17 @@ "hash": "140e1bd963a0033e66777a3b2c5fe6e9c97dfe32" }, "org.gamecult.eve.unity-scene": { - "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.42", + "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.44", "depth": 0, "source": "git", "dependencies": { "org.gamecult.cultlib": "1.0.13", - "org.gamecult.eve.plugin-fields": "0.2.0", + "org.gamecult.eve.plugin-fields": "0.2.3", "org.gamecult.eve.surface": "0.2.2", "com.unity.mathematics": "1.3.2", "com.unity.modules.particlesystem": "1.0.0" }, - "hash": "441471949a22a0183da1ecac4a4a1d0dc2a7618e" + "hash": "03ce78031f28d7bfae4dcdc477a7273c6ecae55b" }, "com.unity.modules.assetbundle": { "version": "1.0.0", diff --git a/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs b/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs deleted file mode 100644 index 5c0689ca2..000000000 --- a/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System; -using System.Collections.Generic; -using GameCult.Aetheria.State.Verse; -using GameCult.Eve.PluginFields; -using UnityEngine; - -public sealed class AetheriaSceneRenderSplatSource : MonoBehaviour -{ - [SerializeField] - private string documentName = "Scene Mock"; - - [SerializeField] - private AetheriaSceneRenderSplat[] splats = - { - AetheriaSceneRenderSplat.Animated( - AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight, - "scene.fog_surface", - Vector2.zero, - new Vector2(512, 512), - Color.white, - 4, - 4, - 0.015f), - AetheriaSceneRenderSplat.Animated( - AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight, - "scene.fog_patch_height", - Vector2.zero, - new Vector2(512, 512), - Color.white, - 9, - 9, - 0.02f), - AetheriaSceneRenderSplat.Animated( - AetheriaRuntimeRenderSplatLayerKeys.FogPatch, - "scene.fog_patch", - Vector2.zero, - new Vector2(512, 512), - Color.white, - 6, - 6, - 0.01f) - }; - - [SerializeField] - private AetheriaSceneRenderSplatFixture[] fixtures = Array.Empty(); - - public EveFieldsSplatsDocument BuildDocument(AetheriaRuntimeViewportBounds viewport) - { - viewport ??= new AetheriaRuntimeViewportBounds(); - var normalizedViewport = Normalize(viewport); - var layers = BuildLayers(); - var layerIndices = new Dictionary(StringComparer.Ordinal); - for (var index = 0; index < layers.Length; index++) - layerIndices[layers[index].LayerKey] = index; - - var builder = new Builder(); - foreach (var splat in splats ?? Array.Empty()) - splat.AddTo(builder, layerIndices); - foreach (var fixture in fixtures ?? Array.Empty()) - fixture.AddTo(builder, layerIndices); - - return new EveFieldsSplatsDocument - { - PublishedAtUtc = DateTimeOffset.UtcNow.ToString("O"), - SimulationTimeSeconds = Application.isPlaying ? Time.timeAsDouble : 0, - RunId = "scene", - ZoneName = string.IsNullOrWhiteSpace(documentName) ? gameObject.scene.name : documentName, - Viewport = new EveFieldsViewport - { - MinX = normalizedViewport.MinX, - MinY = normalizedViewport.MinY, - MaxX = normalizedViewport.MaxX, - MaxY = normalizedViewport.MaxY - }, - Layers = layers, - Splats = builder.Build() - }; - } - - private static AetheriaRuntimeViewportBounds Normalize(AetheriaRuntimeViewportBounds viewport) - { - return new AetheriaRuntimeViewportBounds - { - MinX = Math.Min(viewport.MinX, viewport.MaxX), - MinY = Math.Min(viewport.MinY, viewport.MaxY), - MaxX = Math.Max(viewport.MinX, viewport.MaxX), - MaxY = Math.Max(viewport.MinY, viewport.MaxY) - }; - } - - private static EveFieldsSplatLayer[] BuildLayers() - { - return new[] - { - Layer(AetheriaRuntimeRenderSplatLayerKeys.GravityHeight, "Gravity Height", AetheriaRuntimeRenderSplatChannels.Gravity), - Layer(AetheriaRuntimeRenderSplatLayerKeys.GravityWave, "Gravity Wave", AetheriaRuntimeRenderSplatChannels.GravityWave), - Layer(AetheriaRuntimeRenderSplatLayerKeys.Visibility, "Visibility Mask", AetheriaRuntimeRenderSplatChannels.Visibility, AetheriaRuntimeRenderSplatBlendModes.Max), - Layer(AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight, "Fog Surface Height", AetheriaRuntimeRenderSplatChannels.Tint), - Layer(AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight, "Fog Patch Height", AetheriaRuntimeRenderSplatChannels.Tint), - Layer(AetheriaRuntimeRenderSplatLayerKeys.FogPatch, "Fog Patch", AetheriaRuntimeRenderSplatChannels.Tint, AetheriaRuntimeRenderSplatBlendModes.Max), - Layer(AetheriaRuntimeRenderSplatLayerKeys.FogTint, "Fog Tint", AetheriaRuntimeRenderSplatChannels.Tint, graphicsFormat: "B10G11R11_UFloatPack32"), - Layer(AetheriaRuntimeRenderSplatLayerKeys.Influence, "Influence", AetheriaRuntimeRenderSplatChannels.Influence) - }; - } - - private static EveFieldsSplatLayer Layer( - string key, - string displayName, - int channel, - string blendMode = AetheriaRuntimeRenderSplatBlendModes.Add, - string graphicsFormat = "R16_SFloat") - { - return new EveFieldsSplatLayer - { - LayerKey = key, - DisplayName = displayName, - Channel = channel, - BlendMode = blendMode, - GraphicsFormat = graphicsFormat - }; - } - - public sealed class Builder - { - private readonly List _centerX = new List(); - private readonly List _centerY = new List(); - private readonly List _halfExtentX = new List(); - private readonly List _halfExtentY = new List(); - private readonly List _rotationCos = new List(); - private readonly List _rotationSin = new List(); - private readonly List _channel = new List(); - private readonly List _falloff = new List(); - private readonly List _valueR = new List(); - private readonly List _valueG = new List(); - private readonly List _valueB = new List(); - private readonly List _valueA = new List(); - private readonly List _sourceKey = new List(); - private readonly List _layerIndex = new List(); - private readonly List _sourceKind = new List(); - private readonly List _frequencyX = new List(); - private readonly List _frequencyY = new List(); - private readonly List _phaseX = new List(); - private readonly List _phaseY = new List(); - private readonly List _animationSpeed = new List(); - private readonly List _sourceFlags = new List(); - - public void Add(AetheriaSceneRenderSplat splat, int layerIndex) - { - var rotationRadians = splat.RotationDegrees * Mathf.Deg2Rad; - _layerIndex.Add(layerIndex); - _centerX.Add(splat.Center.x); - _centerY.Add(splat.Center.y); - _halfExtentX.Add(Math.Max(0, splat.HalfExtent.x)); - _halfExtentY.Add(Math.Max(0, splat.HalfExtent.y)); - _rotationCos.Add(Math.Cos(rotationRadians)); - _rotationSin.Add(Math.Sin(rotationRadians)); - _channel.Add(splat.Channel); - _falloff.Add(splat.Falloff); - _valueR.Add(splat.Value.r); - _valueG.Add(splat.Value.g); - _valueB.Add(splat.Value.b); - _valueA.Add(splat.Value.a); - _sourceKey.Add(splat.SourceKey ?? ""); - _sourceKind.Add(splat.SourceKind); - _frequencyX.Add(splat.Frequency.x); - _frequencyY.Add(splat.Frequency.y); - _phaseX.Add(splat.Phase.x); - _phaseY.Add(splat.Phase.y); - _animationSpeed.Add(splat.AnimationSpeed); - _sourceFlags.Add(splat.SourceFlags); - } - - public EveFieldsSplatSoa Build() - { - return new EveFieldsSplatSoa - { - Count = _centerX.Count, - CenterX = _centerX.ToArray(), - CenterY = _centerY.ToArray(), - HalfExtentX = _halfExtentX.ToArray(), - HalfExtentY = _halfExtentY.ToArray(), - RotationCos = _rotationCos.ToArray(), - RotationSin = _rotationSin.ToArray(), - Channel = _channel.ToArray(), - Falloff = _falloff.ToArray(), - ValueR = _valueR.ToArray(), - ValueG = _valueG.ToArray(), - ValueB = _valueB.ToArray(), - ValueA = _valueA.ToArray(), - SourceKey = _sourceKey.ToArray(), - LayerIndex = _layerIndex.ToArray(), - SourceKind = _sourceKind.ToArray(), - FrequencyX = _frequencyX.ToArray(), - FrequencyY = _frequencyY.ToArray(), - PhaseX = _phaseX.ToArray(), - PhaseY = _phaseY.ToArray(), - AnimationSpeed = _animationSpeed.ToArray(), - SourceFlags = _sourceFlags.ToArray() - }; - } - } -} - -[Serializable] -public sealed class AetheriaSceneRenderSplat -{ - public string LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogTint; - public string SourceKey = ""; - public Vector2 Center; - public Vector2 HalfExtent = Vector2.one * 32; - public float RotationDegrees; - public int Channel = AetheriaRuntimeRenderSplatChannels.Tint; - public int Falloff = AetheriaRuntimeRenderSplatFalloffs.Smooth; - public Color Value = Color.white; - public int SourceKind = AetheriaRuntimeRenderSplatSourceKinds.Constant; - public Vector2 Frequency = Vector2.one; - public Vector2 Phase; - public float AnimationSpeed; - public float SourceFlags; - - public static AetheriaSceneRenderSplat Animated( - string layerKey, - string sourceKey, - Vector2 center, - Vector2 halfExtent, - Color value, - float frequencyX, - float frequencyY, - float animationSpeed) - { - return new AetheriaSceneRenderSplat - { - LayerKey = layerKey, - SourceKey = sourceKey, - Center = center, - HalfExtent = halfExtent, - Channel = AetheriaRuntimeRenderSplatChannels.Tint, - Falloff = AetheriaRuntimeRenderSplatFalloffs.Solid, - Value = value, - SourceKind = AetheriaRuntimeRenderSplatSourceKinds.AnimatedSimplexNoise, - Frequency = new Vector2(frequencyX, frequencyY), - AnimationSpeed = animationSpeed, - SourceFlags = 1 - }; - } - - public void AddTo(AetheriaSceneRenderSplatSource.Builder builder, IReadOnlyDictionary layerIndices) - { - if (builder == null || !layerIndices.TryGetValue(LayerKey ?? "", out var layerIndex)) - return; - - builder.Add(this, layerIndex); - } -} - -[Serializable] -public sealed class AetheriaSceneRenderSplatFixture -{ - public Transform Transform; - public AetheriaSceneRenderSplat Splat = new AetheriaSceneRenderSplat(); - - public void AddTo(AetheriaSceneRenderSplatSource.Builder builder, IReadOnlyDictionary layerIndices) - { - if (Transform == null || Splat == null) - return; - - var previousCenter = Splat.Center; - Splat.Center = new Vector2(Transform.position.x, Transform.position.z); - try - { - Splat.AddTo(builder, layerIndices); - } - finally - { - Splat.Center = previousCenter; - } - } -} diff --git a/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs.meta b/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs.meta deleted file mode 100644 index ea46b8942..000000000 --- a/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6c9073f8e2eb4b52b9a11ec60dcfc451 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index f7402fac4..8bcb5617b 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -1339,11 +1339,41 @@ public class DaemonRuntimeDocumentTests Assert.AreEqual(9, brushes[0].WaveRadius); Assert.AreEqual(3, brushes[0].WaveDepth); Assert.AreEqual(2, brushes[0].WaveSpeed); + Assert.AreEqual(1, brushes[0].WaveFrequency); var height = AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight(zone, 4, 0, 0); Assert.AreEqual(-24.9968, height, 0.0001); } + [Test] + public void GravityTerrainUsesBodyOwnedWaveFrequency() + { + var body = new AetheriaRuntimeBodySnapshotCommit + { + Kind = "gas_giant", + GravityInfluenceCenterX = 0, + GravityInfluenceCenterZ = 0, + GravityWaveRadius = 20, + GravityWaveDepth = 3, + GravityWaveSpeed = 0, + GravityWaveFrequency = 7 + }; + var zone = new AetheriaRuntimeZoneSnapshotCommit + { + GravityTerrainWaveFrequency = 0.125, + Bodies = new[] { body } + }; + const double x = 3; + var normalized = x / body.GravityWaveRadius; + var doubled = normalized * 2; + var envelope = Math.Pow((doubled + 1) * (1 - doubled), 8); + var expected = -envelope * Math.Cos(Math.Pow(doubled, 1.25) * body.GravityWaveFrequency) * body.GravityWaveDepth; + + Assert.AreEqual(expected, + AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight(zone, x, 0, 0), + 0.0001); + } + [Test] public void DaemonRenderQueriesResolveZoneRenderRadiusFromDaemonTerrain() { diff --git a/Assets/Scripts/Zone Display/VolumeSampling.cs b/Assets/Scripts/Zone Display/VolumeSampling.cs index 379ab061a..809955a16 100644 --- a/Assets/Scripts/Zone Display/VolumeSampling.cs +++ b/Assets/Scripts/Zone Display/VolumeSampling.cs @@ -32,7 +32,6 @@ public class VolumeSampling : MonoBehaviour public RenderTexture NebulaTint; public AetheriaUnityRenderSplatViewportSource SplatViewportSource; public AetheriaRenderSplatLayerRenderer SplatLayerRenderer; - public AetheriaSceneRenderSplatSource SceneSplatSource; [SerializeField] private bool useRuntimeRenderSplats; @@ -191,11 +190,11 @@ public class VolumeSampling : MonoBehaviour ResolveSplatRenderers(); if (SplatLayerRenderer == null) return; + if (!useRuntimeRenderSplats) + return; ResolveSplatViewport(out var center, out var size); - var document = ResolveSceneSplatDocument(center, size); - if (document == null && useRuntimeRenderSplats) - document = ResolveRuntimeSplatDocument(center, size); + var document = ResolveRuntimeSplatDocument(center, size); if (document == null) return; @@ -223,15 +222,6 @@ public class VolumeSampling : MonoBehaviour NebulaTint = ResolveLayerTexture(AetheriaRuntimeRenderSplatLayerKeys.FogTint, NebulaTint); } - private EveFieldsSplatsDocument ResolveSceneSplatDocument(Vector2 center, Vector2 size) - { - if (SceneSplatSource == null) - SceneSplatSource = GetComponent(); - return SceneSplatSource != null - ? SceneSplatSource.BuildDocument(BuildViewportBounds(center, size)) - : null; - } - private EveFieldsSplatsDocument ResolveRuntimeSplatDocument(Vector2 center, Vector2 size) { if (SplatViewportSource == null) @@ -251,18 +241,6 @@ public class VolumeSampling : MonoBehaviour : Vector2.zero; } - private static AetheriaRuntimeViewportBounds BuildViewportBounds(Vector2 center, Vector2 size) - { - var halfSize = size * 0.5f; - return new AetheriaRuntimeViewportBounds - { - MinX = center.x - halfSize.x, - MinY = center.y - halfSize.y, - MaxX = center.x + halfSize.x, - MaxY = center.y + halfSize.y - }; - } - private static bool Approximately(Vector2 lhs, Vector2 rhs) { return Mathf.Abs(lhs.x - rhs.x) < 0.001f && diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonGameSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonGameSurfaceBuilder.cs index cf8cede47..d78656211 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonGameSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonGameSurfaceBuilder.cs @@ -705,7 +705,7 @@ namespace GameCult.Aetheria.State.Verse "tintLodExponent=-0.45;safetyDistance=30;flowScale=512;flowAmplitude=15;flowPeriod=8;" + "flowSlopeAmplitude=0;flowSwirlAmplitude=0;noiseScale=414.2167;noiseAmplitude=-36.17;" + "noiseExponent=-0.25;noiseSpeed=0.0025;noiseSlopeExponent=0.15;dynamicSkyBoost=2;" + - "dynamicLodHigh=7;dynamicLodLow=2;dynamicIntensity=0.5;compositeOpacity=0.3" + "dynamicLodHigh=7;dynamicLodLow=2;dynamicIntensity=0.5;compositeOpacity=1" }; return new AetheriaRuntimeSurfaceComponent( id, diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs index 3bf623257..e69af8fcb 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs @@ -378,7 +378,8 @@ namespace GameCult.Aetheria.State.Verse double gravityDepthExponent, double waveRadius, double waveDepth, - double waveSpeed) + double waveSpeed, + double waveFrequency) { BodyKey = bodyKey ?? ""; OrbitKey = orbitKey ?? ""; @@ -391,6 +392,7 @@ namespace GameCult.Aetheria.State.Verse WaveRadius = Math.Max(0, waveRadius); WaveDepth = waveDepth; WaveSpeed = waveSpeed; + WaveFrequency = waveFrequency; } public string BodyKey { get; } @@ -404,6 +406,7 @@ namespace GameCult.Aetheria.State.Verse public double WaveRadius { get; } public double WaveDepth { get; } public double WaveSpeed { get; } + public double WaveFrequency { get; } } public readonly struct AetheriaRuntimeDaemonBodyPose @@ -829,7 +832,8 @@ namespace GameCult.Aetheria.State.Verse body.GravityDepthExponent, waveRadius, ResolveWaveDepth(body), - ResolveWaveSpeed(body))); + ResolveWaveSpeed(body), + ResolveWaveFrequency(body))); } return brushes.Count; @@ -1769,7 +1773,7 @@ namespace GameCult.Aetheria.State.Verse distance / brush.WaveRadius, 8.0, 1.25, - zone.GravityTerrainWaveFrequency, + brush.WaveFrequency, simulationTimeSeconds * brush.WaveSpeed) * brush.WaveDepth; } } @@ -2060,6 +2064,13 @@ namespace GameCult.Aetheria.State.Verse return body.GravityWaveSpeed; } + private static double ResolveWaveFrequency(AetheriaRuntimeBodySnapshotCommit body) + { + return body != null && IsFinite(body.GravityWaveFrequency) && body.GravityWaveFrequency > 0 + ? body.GravityWaveFrequency + : 1.0; + } + private static double ResolveAsteroidBeltRadius(AetheriaRuntimeBodySnapshotCommit body) { var radius = 0.0; diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameDocuments.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameDocuments.cs index 515cf6d76..d47751968 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameDocuments.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameDocuments.cs @@ -9,6 +9,11 @@ namespace GameCult.Aetheria.State.Verse { public static class AetheriaRuntimeGameDocuments { + // Unity built-in mesh 10210 is a one-unit Quad; canonical ARPG.unity + // scales the gameplay Zone Brushes parent to 3000. + private const double FossilZoneBrushHalfExtent = 1500.0; + private const double FossilGlobalBrushExponent = 0.2; + public static AetheriaRuntimeGameViewportDocument Viewport( AetheriaRuntimeDaemonFrameDocument frame, AetheriaRuntimeViewportBounds viewport) @@ -126,10 +131,6 @@ namespace GameCult.Aetheria.State.Verse .Select((layer, index) => (layer.LayerKey, index)) .ToDictionary(pair => pair.LayerKey, pair => pair.index, StringComparer.Ordinal); var splats = new RenderSplatBuilder(); - var viewportCenterX = (normalizedViewport.MinX + normalizedViewport.MaxX) * 0.5; - var viewportCenterY = (normalizedViewport.MinY + normalizedViewport.MaxY) * 0.5; - var viewportHalfX = Math.Max(0.0001, (normalizedViewport.MaxX - normalizedViewport.MinX) * 0.5); - var viewportHalfY = Math.Max(0.0001, (normalizedViewport.MaxY - normalizedViewport.MinY) * 0.5); if (zone.GravityTerrainRadius > 0 && zone.GravityTerrainDepth != 0) { splats.Add( @@ -162,44 +163,24 @@ namespace GameCult.Aetheria.State.Verse "environment.gravity_terrain:fog.surface_height", falloffScale: 1, falloffExponent: Math.Max(0.0001, zone.GravityTerrainDepthExponent)); + splats.Add( + layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight], + 0, + 0, + zone.GravityTerrainRadius, + zone.GravityTerrainRadius, + AetheriaRuntimeRenderSplatChannels.Tint, + EveFieldsSplatFalloffs.PowerPulse, + zone.GravityTerrainDepth, + 0, + 0, + 1, + "environment.gravity_terrain:fog.patch_height", + falloffScale: 1, + falloffExponent: Math.Max(0.0001, zone.GravityTerrainDepthExponent)); } - splats.Add( - layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight], - viewportCenterX, - viewportCenterY, - viewportHalfX, - viewportHalfY, - AetheriaRuntimeRenderSplatChannels.Tint, - AetheriaRuntimeRenderSplatFalloffs.Solid, - 1, - 0, - 0, - 1, - "environment.fog_patch_height", - sourceKind: AetheriaRuntimeRenderSplatSourceKinds.AnimatedSimplexNoise, - frequencyX: 9.0, - frequencyY: 9.0, - animationSpeed: 0.02, - sourceFlags: 1); - splats.Add( - layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogPatch], - viewportCenterX, - viewportCenterY, - viewportHalfX, - viewportHalfY, - AetheriaRuntimeRenderSplatChannels.Tint, - AetheriaRuntimeRenderSplatFalloffs.Solid, - 1, - 0, - 0, - 1, - "environment.fog_patch", - sourceKind: AetheriaRuntimeRenderSplatSourceKinds.AnimatedSimplexNoise, - frequencyX: 6.0, - frequencyY: 6.0, - animationSpeed: 0.01, - sourceFlags: 1); + AddFossilGlobalFogBrushes(splats, layerIndices); foreach (var body in zone.Bodies ?? Array.Empty()) { @@ -237,43 +218,65 @@ namespace GameCult.Aetheria.State.Verse $"{body.BodyKey ?? ""}:fog.surface_height", falloffScale: 2, falloffExponent: Math.Max(0.0001, body.GravityDepthExponent)); + splats.Add( + layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight], + body.GravityInfluenceCenterX, + body.GravityInfluenceCenterZ, + radius, + radius, + AetheriaRuntimeRenderSplatChannels.Tint, + EveFieldsSplatFalloffs.PowerPulse, + body.GravityWellDepth, + 0, + 0, + 1, + $"{body.BodyKey ?? ""}:fog.patch_height", + falloffScale: 2, + falloffExponent: Math.Max(0.0001, body.GravityDepthExponent)); - if (body.GravityWaveRadius > 0 && body.GravityWaveDepth != 0) + if ((IsBodyKind(body, "gas_giant") || IsBodyKind(body, "sun")) && + body.GravityWaveRadius > 0 && body.GravityWaveDepth != 0) { - splats.Add( + AddFossilRadialWaveBrush( + splats, layerIndices[AetheriaRuntimeRenderSplatLayerKeys.GravityWave], - body.GravityInfluenceCenterX, - body.GravityInfluenceCenterZ, - body.GravityWaveRadius, - body.GravityWaveRadius, + body, AetheriaRuntimeRenderSplatChannels.GravityWave, - AetheriaRuntimeRenderSplatFalloffs.Smooth, - body.GravityWaveDepth, - body.GravityWaveSpeed, - 0, - 1, - body.BodyKey ?? ""); + $"{body.BodyKey ?? ""}:gravity.wave"); + AddFossilRadialWaveBrush( + splats, + layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight], + body, + AetheriaRuntimeRenderSplatChannels.Tint, + $"{body.BodyKey ?? ""}:fog.surface_height.wave"); + AddFossilRadialWaveBrush( + splats, + layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight], + body, + AetheriaRuntimeRenderSplatChannels.Tint, + $"{body.BodyKey ?? ""}:fog.patch_height.wave"); } if (IsBodyKind(body, "sun")) { - var tintRadius = Math.Max( - radius, - Math.Pow(Math.Max(0, body.Mass), 0.25) * 300 * - Math.Max(0.01, body.SunVisual?.LightRadiusMultiplier ?? 1.0)); + // The tint prefab also uses the one-unit built-in Quad. + var tintHalfExtent = Math.Pow(Math.Max(0, body.Mass), 0.25) * 300 * + Math.Max(0.01, body.SunVisual?.LightRadiusMultiplier ?? 1.0) * 0.5; splats.Add( layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogTint], body.GravityInfluenceCenterX, body.GravityInfluenceCenterZ, - tintRadius, - tintRadius, + tintHalfExtent, + tintHalfExtent, AetheriaRuntimeRenderSplatChannels.Tint, - AetheriaRuntimeRenderSplatFalloffs.Smooth, - body.SunVisual?.FogTintColorX ?? 0, - body.SunVisual?.FogTintColorY ?? 0, - body.SunVisual?.FogTintColorZ ?? 0, + EveFieldsSplatFalloffs.PowerPulse, + (body.SunVisual?.FogTintColorX ?? 0) * 0.25, + (body.SunVisual?.FogTintColorY ?? 0) * 0.25, + (body.SunVisual?.FogTintColorZ ?? 0) * 0.25, 1, - body.BodyKey ?? ""); + $"{body.BodyKey ?? ""}:fog.tint", + falloffScale: 1, + falloffExponent: 16); } } @@ -375,7 +378,7 @@ namespace GameCult.Aetheria.State.Verse LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogPatch, DisplayName = "Fog Patch", Channel = AetheriaRuntimeRenderSplatChannels.Tint, - BlendMode = AetheriaRuntimeRenderSplatBlendModes.Max, + BlendMode = AetheriaRuntimeRenderSplatBlendModes.Add, GraphicsFormat = "R16_SFloat" }, new EveFieldsSplatLayer @@ -397,6 +400,152 @@ namespace GameCult.Aetheria.State.Verse }; } + private static void AddFossilGlobalFogBrushes( + RenderSplatBuilder splats, + IReadOnlyDictionary layerIndices) + { + var surfaceHeight = layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight]; + var patchHeight = layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight]; + var patch = layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogPatch]; + var tint = layerIndices[AetheriaRuntimeRenderSplatLayerKeys.FogTint]; + + AddFossilSimplexBrush(splats, surfaceHeight, "environment.fog_surface.wave_large", 2, -2.46, 0.02, 0.1); + AddFossilSimplexBrush(splats, surfaceHeight, "environment.fog_surface.wave_medium", 7.58, 1, 0.01, 0.0375); + AddFossilSimplexBrush(splats, surfaceHeight, "environment.fog_surface.wave_small", -16.75, 0, 0.005, 0.075); + AddFossilSimplexBrush(splats, patchHeight, "environment.fog_patch_height.wave_large", 2, -2.46, 0.02, 0.1); + AddFossilSimplexBrush(splats, patchHeight, "environment.fog_patch_height.wave_medium", 7.58, 1, 0.01, 0.0375); + AddFossilSimplexBrush(splats, patchHeight, "environment.fog_patch_height.wave_small", -16.75, 0, 0.005, 0.075); + AddFossilSimplexBrush(splats, patchHeight, "environment.fog_patch_height.displacement", 10, -1, 0.0002, 0.01); + AddFossilSimplexBrush(splats, patch, "environment.fog_patch.depth_micro", 10, 0, 0.005, 0.025); + AddFossilCellBrush(splats, patch, "environment.fog_patch.depth_macro", 20, 0.001, 0.025); + + splats.Add( + tint, + 0, + 0, + FossilZoneBrushHalfExtent, + FossilZoneBrushHalfExtent, + AetheriaRuntimeRenderSplatChannels.Tint, + EveFieldsSplatFalloffs.PowerPulse, + 0.025 * 0.496, + 0.025 * 0.5813884, + 0.025, + 1, + "environment.fog_tint.ambient", + falloffScale: 1, + falloffExponent: 2); + } + + private static void AddFossilSimplexBrush( + RenderSplatBuilder splats, + int layerIndex, + string sourceKey, + double depth, + double constant, + double frequency, + double speed) + { + if (constant != 0) + { + splats.Add( + layerIndex, + 0, + 0, + FossilZoneBrushHalfExtent, + FossilZoneBrushHalfExtent, + AetheriaRuntimeRenderSplatChannels.Tint, + EveFieldsSplatFalloffs.PowerPulse, + constant, + 0, + 0, + 1, + sourceKey + ":constant", + falloffScale: 1, + falloffExponent: FossilGlobalBrushExponent); + } + + splats.Add( + layerIndex, + 0, + 0, + FossilZoneBrushHalfExtent, + FossilZoneBrushHalfExtent, + AetheriaRuntimeRenderSplatChannels.Tint, + EveFieldsSplatFalloffs.PowerPulse, + depth, + 0, + 0, + 1, + sourceKey, + sourceKind: EveFieldsSplatSourceKinds.AnimatedSimplexNoise, + frequencyX: frequency, + frequencyY: frequency, + animationSpeed: speed, + sourceFlags: EveFieldsSplatSourceFlags.AbsoluteValue, + falloffScale: 1, + falloffExponent: FossilGlobalBrushExponent); + } + + private static void AddFossilCellBrush( + RenderSplatBuilder splats, + int layerIndex, + string sourceKey, + double depth, + double frequency, + double speed) + { + splats.Add( + layerIndex, + 0, + 0, + FossilZoneBrushHalfExtent, + FossilZoneBrushHalfExtent, + AetheriaRuntimeRenderSplatChannels.Tint, + EveFieldsSplatFalloffs.PowerPulse, + depth, + 0, + 0, + 1, + sourceKey, + sourceKind: EveFieldsSplatSourceKinds.AnimatedCellNoiseB, + frequencyX: frequency, + frequencyY: frequency, + animationSpeed: speed, + falloffScale: 1, + falloffExponent: FossilGlobalBrushExponent); + } + + private static void AddFossilRadialWaveBrush( + RenderSplatBuilder splats, + int layerIndex, + AetheriaRuntimeBodySnapshotCommit body, + int channel, + string sourceKey) + { + // The fossil scales a one-unit Quad by GravityWaveRadius. Local + // distance therefore reaches one at physical radius / 2. + var halfExtent = body.GravityWaveRadius * 0.5; + splats.Add( + layerIndex, + body.GravityInfluenceCenterX, + body.GravityInfluenceCenterZ, + halfExtent, + halfExtent, + channel, + EveFieldsSplatFalloffs.PowerPulse, + body.GravityWaveDepth, + 0, + 0, + 1, + sourceKey, + sourceKind: EveFieldsSplatSourceKinds.AnimatedRadialCosine, + frequencyX: Math.Max(0.0001, body.GravityWaveFrequency), + frequencyY: 1.25, + animationSpeed: body.GravityWaveSpeed, + falloffScale: 1, + falloffExponent: 8); + } + public static AetheriaRuntimeCurrentZoneDocument CurrentZone( AetheriaRuntimeDaemonFrameDocument frame) { diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs index 7fea19681..c5831cfd0 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs @@ -804,6 +804,9 @@ namespace GameCult.Aetheria.State.Verse [Key(22)] public double IconSize { get; set; } + + [Key(23)] + public double GravityWaveFrequency { get; set; } = 1.0; } [MessagePackObject] diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index e1bc51613..ad605590c 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -906,6 +906,23 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. `fog.surface_height` is the positive visual displacement the fossil `PowerBrush` camera supplied to `_NebulaSurfaceHeight`. Both derive from the same daemon-owned radial brushes, but neither may impersonate the other. +- Fossil producer map: layer 9 coarse displacement and layer 10 fine waves + compose `fog.surface_height`; those same producers plus layer 12 patch + displacement compose `fog.patch_height`; layer 11 simplex/cellular density + brushes compose `fog.patch`; layer 13 ambient and body light brushes compose + `fog.tint`. These are additive field producers, not Unity camera objects. +- Procedural-source ownership: Eve Fields defines world-anchored Ashima simplex, + the fossil moving cellular-B function, published simulation-time animation, + splat-local animated radial cosine, absolute-value folding, and `PowerPulse`. + Aetheria owns each brush's extent, target layer, depth, constant offset, + frequency, speed, exponent, and color. The fossil brush objects use Unity's + one-unit built-in Quad (`fileID 10210`), so authored scale converts to half + extent by multiplying by `0.5`, not `5`. Canonical `ARPG.unity` scales the + gameplay `Zone Brushes` parent to `3000`, making its global half extent + `1500`; `TestScene`'s smaller `1024` value is not gameplay authority. + Constant offsets are separate constant splats. No provider texture reference + is introduced: the fossil materials carry a stale `_MainTex`, but their + shaders sample procedural functions and never read it. - Entity altitude: after every Ymir XZ step, the daemon derives root entity Y as fossil `gravity.height + HullGridOffset`; children inherit their parent's full altitude. Eve clients consume the resulting 3D position and never sample or @@ -921,7 +938,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. without all required history ports, or a viewport scale relation with an unresolved logical port or texture, fails closed. - Cut line: the daemon publishes the fossil zone and body `PowerPulse` brushes - directly; the former full-viewport solid gravity writer is deleted. The + directly; the former full-viewport solid gravity writer is deleted. The two + invented viewport-local patch noises are also deleted before the fossil + producer stack is restored. Procedural samples are anchored to field-world + coordinates, so viewport motion cannot move the fog through the world. The portable surface contains logical field and parameter names only. `_Nebula*`, `_PrevVP`, `_ResetHistory`, shader keywords, and pass indices live exclusively in the provider's Unity asset-variant metadata. diff --git a/docs/original-game-migration-matrix.md b/docs/original-game-migration-matrix.md index 605621595..1def926f7 100644 --- a/docs/original-game-migration-matrix.md +++ b/docs/original-game-migration-matrix.md @@ -34,7 +34,7 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity. | Pilot camera | ARPG follow/dock cameras | Eve view semantics + runtime | camera rig and semantic view id | EveUnity camera rig | daemon dock state selects distinct contracts instead of collapsing both fossil cameras: undocked flight advertises the entity-forward `Third Person Rig` framing (distance 30, screen 0.64/0.81, FOV 60, lens 1-4096) and the shared `aim.presentation` convergence point as its look-at; EveUnity solves native pose while preserving both optical aim and advertised entity framing, without provider types; the retained dock projection remains separate | reproduce the dock camera's separate follow/look-at subjects, blend timeline, noise, and reconnect state | | Render channels | Unity layers/culling masks | provider surface policy + runtime asset variant | semantic channel exclusions | EveUnity native layer mapping | wired, exclusive, and lowered: the pilot surface excludes the semantic `map` channel; the Unity asset variant maps `map` to its authored native layer; EveUnity subtracts that layer from the configured camera mask; portable state contains no Unity layer or mask; package tests and the clean-client dependency verifier pass | live pilot capture proving no map glyphs while the map camera retains them | | Minimap/tactical map | `ZoneRenderer`, map cameras | daemon world facts | explicit map projections | Unity and Electron | facts fragmented; generic lowerers absent | same state in both runtimes | -| Fields/fog/gravity | zone renderer and shaders | daemon/plugin semantics | Fields plugin documents | EveUnity fields lowerer | daemon publishes canonical gravity/fog splat layers through `field.volume3d`; Eve Fields `0.2.1` owns portable `PowerPulse` scale/exponent semantics, so Aetheria emits the fossil radial zone/body wells instead of a viewport-sized solid slab; negative gameplay `gravity.height` and positive visual `fog.surface_height` are distinct projections of the same brushes; daemon entities again follow fossil terrain height plus hull grid offset after Ymir XZ motion; released EveUnity `0.3.42` lowers the radial field, temporal history, viewport-derived dither scale, and finite aim convergence without provider code; the warm raymarch now has real density variation and both camera-channel captures pass, but the field remains dark brown and low contrast rather than the fossil blue fog sea | restore gravity-wave, patch, tint/color, and lighting parity from fossil settings and measured native outputs; keep visual parity explicitly open | +| Fields/fog/gravity | zone renderer and shaders | daemon/plugin semantics | Fields plugin documents | EveUnity fields lowerer | daemon publishes canonical gravity/fog splat layers through `field.volume3d`; Eve Fields `0.2.3` owns portable `PowerPulse` and `AnimatedRadialCosine` semantics; Aetheria emits the fossil global animated simplex/cellular/ambient stack, body-owned radial waves, and separate negative gameplay `gravity.height` and positive visual `fog.surface_height`; daemon entities follow fossil terrain height plus hull grid offset after Ymir XZ motion; released EveUnity `0.3.44` lowers the field sources, temporal history, viewport-derived dither scale, and finite aim convergence without provider code; both camera-channel assertions pass and all field/HDR targets are populated, but the pilot capture is nearly black with sparse clipped geometry rather than the fossil blue fog sea | diagnose native composition and geometry visibility at the pilot camera, then restore tint/color, lighting, patch structure, and gravity-wave visual parity from fossil settings and measured native outputs; keep visual parity explicitly open | | HUD/schematic | ARPG HUD and `SchematicDisplay` | Eve composition from daemon facts | semantic status/components | EveUnity UI | provider-authored transparent cockpit is wired, projected, and lowered through generic UI Toolkit panes, metrics, and progress components for hull, shield, capacitor, temperature, cargo, weapon cooldown/groups, selected target, target lock, target shield, and target hull; the original armor/equipment schematic and transient warning choreography remain reference-only | schematic topology, warning timelines, dynamic action state, and composited live capture | | Hit/lock/warnings | `ActionGameManager` feedback | daemon event facts | transient feedback stream | VFX/UI/audio | bounded deduplicated game-event ledger and Eve `feedback.stream` wired for pickup outcomes, projectile launch/impact, entity damage, alive-to-dead destruction, authoritative weapon reload chronology, and episode-deduplicated `weapon.fire.refused` facts for insufficient energy or missing ammunition; refusal identity, authored weapon, target, reason, and remaining magazine state are runtime-independent cockpit inputs | add lock transitions, thermal and docking event families, negative transaction fixtures, and runtime effect consumption | | Assets | `Assets/Resources` | Aetheria provider | asset catalog + CultMesh CDN | runtime cache/load | provider manifests name deterministic bundle-internal paths; the authoring build derives presentation-only prefabs, strips missing and non-Eve `MonoBehaviour` components, verifies both saved prefabs and the loaded bundle, and publishes the immutable bundle through CultMesh; released-client warm-cache loading, content verification, and rendered provider identity are proven, while cold transfer still times out because the 46.1 MB body travels through batched snapshot records | mapped/network body transport followed by empty-cache download, atomic promotion, and cache reuse proof |