diff --git a/Assets/Materials/Brushes/Sector Tint.mat b/Assets/Materials/Brushes/Sector Tint.mat
index 3e5e00f24..84e252ad3 100644
--- a/Assets/Materials/Brushes/Sector Tint.mat
+++ b/Assets/Materials/Brushes/Sector Tint.mat
@@ -8,7 +8,7 @@ Material:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Sector Tint
- m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, type: 3}
+ m_Shader: {fileID: 4800000, guid: 782740c1a3d7455ea75dc87fafdc8432, type: 3}
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
@@ -59,8 +59,8 @@ Material:
m_Ints: []
m_Floats:
- _BumpScale: 1
- - _Cutoff: 1
- _Depth: 0.025
+ - _EdgeBrightness: 0.12
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _Frequency: 6.2832
@@ -72,7 +72,6 @@ Material:
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Phase: 0
- - _Power: 2
- _SinePower: 1.25
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
diff --git a/Assets/Scenes/ARPG.unity b/Assets/Scenes/ARPG.unity
index 9c650e2f0..3ce35fe93 100644
--- a/Assets/Scenes/ARPG.unity
+++ b/Assets/Scenes/ARPG.unity
@@ -409,13 +409,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 23420356}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
+ m_LocalPosition: {x: 0, y: -5, 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}
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!23 &23420359
MeshRenderer:
m_ObjectHideFlags: 0
@@ -16322,6 +16322,7 @@ MonoBehaviour:
Settings: {fileID: 11400000, guid: cd2344e2001a37544aab0960c164d4ff, type: 2}
ZoneRoot: {fileID: 2095699057}
SectorBrushes: {fileID: 1037490739}
+ AmbientLight: {fileID: 23420357}
SceneCamera: {fileID: 887677622}
FogCameras:
- {fileID: 559840917}
@@ -16864,7 +16865,6 @@ Transform:
m_LocalScale: {x: 3000, y: 3000, z: 3000}
m_ConstrainProportionsScale: 0
m_Children:
- - {fileID: 23420357}
- {fileID: 1108843909}
- {fileID: 1647230682}
- {fileID: 1254851255}
@@ -38395,6 +38395,7 @@ SceneRoots:
- {fileID: 906767442}
- {fileID: 1009503730}
- {fileID: 1037490739}
+ - {fileID: 23420357}
- {fileID: 2095699057}
- {fileID: 134113271}
- {fileID: 666227432}
diff --git a/Assets/Scripts/Editor/AmbientEdgeVerify.cs b/Assets/Scripts/Editor/AmbientEdgeVerify.cs
new file mode 100644
index 000000000..fd0482bd9
--- /dev/null
+++ b/Assets/Scripts/Editor/AmbientEdgeVerify.cs
@@ -0,0 +1,221 @@
+using System;
+using System.IO;
+using GameCult.Caching;
+using UnityEditor;
+using UnityEditor.SceneManagement;
+using UnityEngine;
+using UnityEngine.Rendering;
+
+namespace Aetheria.EditorTools
+{
+ ///
+ /// Batchmode check for campaign aetheria-gravity, cut ambient-follows-populated-area: the ambient tint falls softly to
+ /// dark and never hits an edge (ruling dark-beyond-populated-area). It opens ARPG.unity, takes the Ambient Fog Light the
+ /// scene wires into ZoneRenderer, sizes it for a generated zone with ZoneRenderer.FitAmbientLight (the call LoadZone
+ /// makes, not a copy), renders it through an orthographic camera that culls its layer into a float target, and checks the
+ /// radial profile against exp(-(r/P)^2 ln(1/_EdgeBrightness)) times the centre out to 2 P (P is Zone.PopulatedRadius),
+ /// that it is lit at Pack.Radius, that the quad's rim is at most 2e-5 of the centre, and that no texel is more than half
+ /// a decade from its neighbour.
+ ///
+ /// Renders, so it needs a graphics device: run WITHOUT -nographics.
+ /// Unity -batchmode -projectPath <repo> -executeMethod Aetheria.EditorTools.AmbientEdgeVerify.Run -logFile <path>
+ /// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
+ ///
+ public static class AmbientEdgeVerify
+ {
+ const string Tag = "[AmbientEdgeVerify]";
+ const int Size = 512;
+ const int AmbientLayer = 13;
+ static bool _ok = true;
+
+ static void OnLog(string message, string stack, LogType type)
+ {
+ if ((type == LogType.Error || type == LogType.Exception || type == LogType.Assert) && !message.StartsWith(Tag))
+ Fail("logged-error", message.Split('\n')[0]);
+ }
+
+ static void Fail(string check, string detail)
+ {
+ Debug.LogError($"{Tag} FAIL {check}: {detail}");
+ _ok = false;
+ }
+
+ public static void Run()
+ {
+ if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null)
+ {
+ Debug.LogError($"{Tag} FAIL: no graphics device in this session; rerun batchmode without -nographics.");
+ EditorApplication.Exit(1);
+ return;
+ }
+ Application.logMessageReceived += OnLog;
+ var work = Path.Combine(Path.GetTempPath(), "ambient-edge-" + Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(Path.Combine(work, "probe"));
+ try
+ {
+ var settings = AssetDatabase.LoadAssetAtPath("Assets/Resources/Settings.asset")
+ ?? throw new InvalidOperationException("No Settings.asset.");
+ // Generated zones have the populated radius at about .78 of the pack radius (p50 over 1500 zones): this one's
+ // pack radius is set so, which puts Pack.Radius where it sits in play, inside the quad's reach.
+ var probe = GravityFieldParityVerify.BuildZone(settings, Path.Combine(work, "probe"), out _);
+ var zone = GravityFieldParityVerify.BuildZone(settings, work, out _, probe.PopulatedRadius / .78f);
+ EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Single);
+ try
+ {
+ var renderer = UnityEngine.Object.FindFirstObjectByType(FindObjectsInactive.Include);
+ if (renderer == null || renderer.AmbientLight == null) throw new InvalidOperationException("ARPG.unity wires no ZoneRenderer.AmbientLight.");
+ Check(renderer, zone);
+ }
+ finally
+ {
+ EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
+ }
+ Debug.Log(_ok ? $"{Tag} PASS" : $"{Tag} FAIL: see errors above");
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"{Tag} FAIL exception: {e}");
+ _ok = false;
+ }
+ finally
+ {
+ Application.logMessageReceived -= OnLog;
+ try { Directory.Delete(work, true); } catch { }
+ }
+ EditorApplication.Exit(_ok ? 0 : 1);
+ }
+
+ // A bare ambient quad for the verifies that run LoadZone on a renderer built in code: the scene's material, no scene.
+ public static Transform NewAmbientLight()
+ {
+ var quad = new GameObject("Ambient Fog Light") { layer = AmbientLayer };
+ quad.AddComponent().sharedMesh = Resources.GetBuiltinResource("Quad.fbx");
+ quad.AddComponent().sharedMaterial = AssetDatabase.LoadAssetAtPath("Assets/Materials/Brushes/Sector Tint.mat");
+ return quad.transform;
+ }
+
+ static void Check(ZoneRenderer renderer, Zone zone)
+ {
+ var light = renderer.AmbientLight;
+ if (light.parent != null) Fail("wiring", $"the ambient quad sits under {light.parent.name}, whose scale is the pack radius's");
+ if (light.gameObject.layer != AmbientLayer) Fail("wiring", $"the ambient quad is on layer {light.gameObject.layer}, not {AmbientLayer}");
+
+ ZoneRenderer.FitAmbientLight(light, zone);
+ var meshRenderer = light.GetComponent();
+ var material = meshRenderer.sharedMaterial;
+ var edge = material.GetFloat("_EdgeBrightness");
+ var centre = material.GetFloat("_Depth") * material.GetColor("_Color").linear.r;
+ var populated = zone.PopulatedRadius;
+ var half = light.localScale.x / 2f;
+ Debug.Log($"{Tag} populated radius {populated:F1} u, pack radius {zone.Pack.Radius:F1} u, quad half-extent {half:F1} u, edge brightness {edge:G3}, centre {centre:G4}");
+ if (!(populated > 0f) || !(half > populated)) { Fail("sizing", $"the quad half-extent {half:F1} u does not reach beyond the populated radius {populated:F1} u"); return; }
+
+ Render(light, half * 1.05f, out var value, out var world);
+ var logFall = Mathf.Log(1f / edge);
+ foreach (var k in new[] { 0f, .5f, 1f, 1.5f, 2f })
+ {
+ var drawn = Sample(value, world, new Vector2(k * populated, 0f));
+ Debug.Log($"{Tag} r = {k} P: drawn {drawn:G5}, expected {centre * Mathf.Exp(-k * k * logFall):G5}");
+ }
+ var worstProfile = 0f;
+ var compared = 0;
+ var rim = 0f;
+ for (var i = 0; i < value.Length; i++)
+ {
+ if (float.IsNaN(value[i])) { Fail("texels", $"texel {i}: NaN"); return; }
+ var r = world[i].magnitude;
+ if (r <= 2f * populated)
+ {
+ var expected = centre * Mathf.Exp(-(r / populated) * (r / populated) * logFall);
+ var error = Mathf.Abs(value[i] - expected) / expected;
+ worstProfile = Mathf.Max(worstProfile, error);
+ compared++;
+ if (error > .01f) { Fail("profile", $"at r = {r / populated:F3} P: expected {expected:G5}, drawn {value[i]:G5}, off by {error * 100f:F2}%"); return; }
+ }
+ // The rim: the texels in the quad's outermost ring (within two texels of its edge).
+ var edgeDistance = half - Mathf.Max(Mathf.Abs(world[i].x), Mathf.Abs(world[i].y));
+ if (edgeDistance >= 0f && edgeDistance < 2f * (half * 1.05f * 2f / Size)) rim = Mathf.Max(rim, value[i]);
+ }
+ Debug.Log($"{Tag} profile: {compared} texels within 2 P, worst error {worstProfile * 100f:F3}%; rim {rim / centre:E2} of the centre");
+ if (compared < Size * Size / 8) Fail("profile", $"only {compared} texels were within 2 P; the check is vacuous");
+ if (rim > 2e-5f * centre) Fail("rim", $"the quad's rim is {rim / centre:E2} of the centre, above 2e-5");
+
+ var atPack = Sample(value, world, new Vector2(zone.Pack.Radius, 0f));
+ Debug.Log($"{Tag} at Pack.Radius: {atPack / centre:E2} of the centre");
+ if (!(atPack > 0f)) Fail("pack-radius", "the ambient is zero at Pack.Radius");
+
+ var worstJump = 0f;
+ for (var y = 0; y < Size; y++)
+ for (var x = 0; x < Size; x++)
+ {
+ var here = value[y * Size + x];
+ if (here <= 0f) continue;
+ foreach (var next in new[] { x + 1 < Size ? value[y * Size + x + 1] : 0f, y + 1 < Size ? value[(y + 1) * Size + x] : 0f })
+ {
+ if (next <= 0f) continue;
+ worstJump = Mathf.Max(worstJump, Mathf.Abs(Mathf.Log10(here / next)));
+ }
+ }
+ Debug.Log($"{Tag} largest jump between neighbouring lit texels: {worstJump:F3} decades (limit 0.5)");
+ if (worstJump > .5f) Fail("jump", $"neighbouring texels differ by {worstJump:F2} decades");
+ var lit = 0;
+ foreach (var v in value) if (v > 0f) lit++;
+ if (lit < Size * Size / 2) Fail("jump", $"only {lit} texels are lit; the check is vacuous");
+ }
+
+ static float Sample(float[] value, Vector2[] world, Vector2 at)
+ {
+ var best = 0;
+ for (var i = 1; i < world.Length; i++)
+ if ((world[i] - at).sqrMagnitude < (world[best] - at).sqrMagnitude) best = i;
+ return value[best];
+ }
+
+ // Renders the ambient layer through a downward orthographic camera centred on the origin, as the Tint cameras see it,
+ // and returns the value of every texel with the world xz of its centre, which is asked of the camera.
+ static void Render(Transform light, float half, out float[] value, out Vector2[] world)
+ {
+ var rt = new RenderTexture(Size, Size, 0, RenderTextureFormat.RFloat) { filterMode = FilterMode.Point };
+ var cameraObject = new GameObject("Ambient Edge Camera");
+ var read = new Texture2D(Size, Size, TextureFormat.RFloat, false);
+ var previous = RenderTexture.active;
+ try
+ {
+ var camera = cameraObject.AddComponent();
+ camera.orthographic = true;
+ camera.orthographicSize = half;
+ camera.aspect = 1f;
+ camera.nearClipPlane = .3f;
+ camera.farClipPlane = 1000f;
+ camera.cullingMask = 1 << light.gameObject.layer;
+ camera.clearFlags = CameraClearFlags.SolidColor;
+ camera.backgroundColor = Color.black;
+ camera.allowHDR = false;
+ camera.allowMSAA = false;
+ camera.targetTexture = rt;
+ cameraObject.transform.SetPositionAndRotation(new Vector3(0f, 100f, 0f), Quaternion.Euler(90f, 0f, 0f));
+ camera.Render();
+
+ RenderTexture.active = rt;
+ read.ReadPixels(new Rect(0, 0, Size, Size), 0, 0);
+ read.Apply();
+ value = read.GetPixelData(0).ToArray();
+ world = new Vector2[Size * Size];
+ for (var y = 0; y < Size; y++)
+ for (var x = 0; x < Size; x++)
+ {
+ var p = camera.ViewportToWorldPoint(new Vector3((x + .5f) / Size, (y + .5f) / Size, 100f));
+ world[y * Size + x] = new Vector2(p.x, p.z);
+ }
+ }
+ finally
+ {
+ RenderTexture.active = previous;
+ UnityEngine.Object.DestroyImmediate(cameraObject);
+ UnityEngine.Object.DestroyImmediate(read);
+ rt.Release();
+ UnityEngine.Object.DestroyImmediate(rt);
+ }
+ }
+ }
+}
diff --git a/Assets/Scripts/Editor/AmbientEdgeVerify.cs.meta b/Assets/Scripts/Editor/AmbientEdgeVerify.cs.meta
new file mode 100644
index 000000000..0962854de
--- /dev/null
+++ b/Assets/Scripts/Editor/AmbientEdgeVerify.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 63b03af05dfdae44c89e21ca7ebec892
\ No newline at end of file
diff --git a/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs b/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs
index 3815ab27d..7e2701897 100644
--- a/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs
+++ b/Assets/Scripts/Editor/DrawnPoseWiringVerify.cs
@@ -126,6 +126,7 @@ namespace Aetheria.EditorTools
renderer.MinePrefab = minePrefab;
renderer.ItemManager = items;
renderer.SectorBrushes = new GameObject("Wiring Brushes").transform;
+ renderer.AmbientLight = AmbientEdgeVerify.NewAmbientLight();
renderer.SlimeGravityCamera = new GameObject("Wiring Gravity Camera").AddComponent();
var slimeObject = new GameObject("Wiring Slime");
slimeObject.SetActive(false);
diff --git a/Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs b/Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs
index 54e2b39fb..1d8a986af 100644
--- a/Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs
+++ b/Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs
@@ -147,6 +147,7 @@ public static class FloatingItemsDrawnSmoke
typeof(ZoneRenderer).GetField(name).SetValue(renderer, pickup);
renderer.ItemManager = items;
renderer.SectorBrushes = new GameObject("Smoke Brushes").transform;
+ renderer.AmbientLight = Aetheria.EditorTools.AmbientEdgeVerify.NewAmbientLight();
renderer.SlimeGravityCamera = new GameObject("Smoke Gravity Camera").AddComponent();
var slimeObject = new GameObject("Smoke Slime");
slimeObject.SetActive(false); // the real Slime needs its compute shader; LoadZone only sets its radius
diff --git a/Assets/Scripts/Editor/GravityFieldParityVerify.cs b/Assets/Scripts/Editor/GravityFieldParityVerify.cs
index bc413bfa0..1753b95ab 100644
--- a/Assets/Scripts/Editor/GravityFieldParityVerify.cs
+++ b/Assets/Scripts/Editor/GravityFieldParityVerify.cs
@@ -124,7 +124,7 @@ namespace Aetheria.EditorTools
// mass 0, whose well has no strength (a legal body; its curves give c = 0 and R = 0), at (1700, 300): parentless,
// fixed, off both axes and asymmetric, so the zone's wells sit where the verify says before any Update and a
// mirrored or swapped axis cannot agree with the sim.
- static Zone BuildZone(GameSettings settings, string work, out Orbit[] orbits)
+ internal static Zone BuildZone(GameSettings settings, string work, out Orbit[] orbits, float radius = 4182f)
{
var cache = AetheriaStores.Open(Path.Combine(work, "catalog.cc"), Path.Combine(work, "run.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
@@ -133,7 +133,7 @@ namespace Aetheria.EditorTools
var positions = new[] { new Vector2(-130f, 85f), new Vector2(610f, -240f), new Vector2(670f, -270f), new Vector2(1700f, 300f) };
var masses = new[] { 14152f, 273.9f, 29.2f, 0f };
var keys = new CultRecordKey[positions.Length];
- var pack = new ZonePack { Radius = 4182f };
+ var pack = new ZonePack { Radius = radius };
for (var i = 0; i < positions.Length; i++)
{
var orbit = keys[i] = cache.Upsert(new OrbitData { FixedPosition = new float2(positions[i].x, positions[i].y) }).Key;
diff --git a/Assets/Scripts/Zone Display/ZoneRenderer.cs b/Assets/Scripts/Zone Display/ZoneRenderer.cs
index e92c78c27..31f9aab3f 100644
--- a/Assets/Scripts/Zone Display/ZoneRenderer.cs
+++ b/Assets/Scripts/Zone Display/ZoneRenderer.cs
@@ -27,6 +27,8 @@ public class ZoneRenderer : MonoBehaviour
public GameSettings Settings;
public Transform ZoneRoot;
public Transform SectorBrushes;
+ // The ambient tint quad, sized from the zone's populated radius, never under SectorBrushes: FitAmbientLight.
+ public Transform AmbientLight;
public CinemachineVirtualCamera SceneCamera;
public Camera[] FogCameras;
public Camera[] MinimapCameras;
@@ -191,11 +193,28 @@ public class ZoneRenderer : MonoBehaviour
_transposer = SceneCamera.GetCinemachineComponent();
}
+ // The ambient is a gaussian: its brightness at the populated edge is the material's _EdgeBrightness (a shape, authored), and
+ // the quad reaches out to where it falls to AmbientFloor of the centre, so no edge is ever drawn. The one writer of the
+ // quad's size and _Reach, and the one place the verify reads the sizing from.
+ const float AmbientFloor = 1e-5f;
+
+ public static void FitAmbientLight(Transform light, Zone zone)
+ {
+ var renderer = light.GetComponent();
+ var reach = Mathf.Sqrt(Mathf.Log(1f / AmbientFloor) / Mathf.Log(1f / renderer.sharedMaterial.GetFloat("_EdgeBrightness")));
+ light.localScale = 2f * reach * zone.PopulatedRadius * Vector3.one;
+ var block = new MaterialPropertyBlock();
+ renderer.GetPropertyBlock(block);
+ block.SetFloat("_Reach", reach);
+ renderer.SetPropertyBlock(block);
+ }
+
public void LoadZone(Zone zone)
{
ClearZone();
Zone = zone;
SectorBrushes.localScale = zone.Pack.Radius * 2 * Vector3.one;
+ FitAmbientLight(AmbientLight, zone);
SlimeGravityCamera.orthographicSize = zone.Pack.Radius;
SlimeRenderer.ZoneRadius = zone.Pack.Radius;
if (_gravityField == null) _gravityField = GravityLowering.CreateField(GravityField);
diff --git a/Assets/Shaders/Brushes/AmbientBrush.shader b/Assets/Shaders/Brushes/AmbientBrush.shader
new file mode 100644
index 000000000..6c6202d0b
--- /dev/null
+++ b/Assets/Shaders/Brushes/AmbientBrush.shader
@@ -0,0 +1,61 @@
+Shader "Brushes/Ambient Brush" {
+Properties {
+ _Color ("Color", Color) = (1,1,1,1)
+ _Depth ("Depth", Float) = 0.5
+ // The brightness at the populated edge relative to the centre: the shape of the fall, never an extent.
+ _EdgeBrightness ("Edge Brightness", Range(0.0001, 0.9999)) = .12
+ // The quad's radius in units of the populated radius. Written by ZoneRenderer.FitAmbientLight, never authored.
+ _Reach ("Reach", Float) = 1
+}
+
+// A gaussian: log-brightness is quadratic in distance from the centre, so the fall is smooth everywhere and
+// never reaches an edge or a cliff. x is the distance in units of the zone's populated radius.
+Category {
+ Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
+ Blend One One
+ ColorMask RGB
+ Cull Off Lighting Off ZWrite Off
+
+ SubShader {
+ Pass {
+
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ #pragma target 2.0
+
+ #include "UnityCG.cginc"
+
+ float4 _Color;
+ float _Depth;
+ float _EdgeBrightness;
+ float _Reach;
+
+ struct appdata_t {
+ float4 vertex : POSITION;
+ float2 texcoord : TEXCOORD0;
+ };
+
+ struct v2f {
+ float4 vertex : SV_POSITION;
+ float2 texcoord : TEXCOORD0;
+ };
+
+ v2f vert (appdata_t v)
+ {
+ v2f o;
+ o.vertex = UnityObjectToClipPos(v.vertex);
+ o.texcoord = v.texcoord;
+ return o;
+ }
+
+ float4 frag (v2f i) : SV_Target
+ {
+ float x = length(i.texcoord - float2(.5, .5)) * 2 * _Reach;
+ return _Depth * _Color * exp(-x * x * log(1 / _EdgeBrightness));
+ }
+ ENDCG
+ }
+ }
+}
+}
diff --git a/Assets/Shaders/Brushes/AmbientBrush.shader.meta b/Assets/Shaders/Brushes/AmbientBrush.shader.meta
new file mode 100644
index 000000000..8f80f596b
--- /dev/null
+++ b/Assets/Shaders/Brushes/AmbientBrush.shader.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 782740c1a3d7455ea75dc87fafdc8432
+ShaderImporter:
+ externalObjects: {}
+ defaultTextures: []
+ nonModifiableTextures: []
+ userData:
+ assetBundleName:
+ assetBundleVariant: