Compare commits

..
814 changed files with 17678 additions and 51825 deletions

2
.gitignore vendored
View file

@ -94,7 +94,7 @@ crashlytics-build.properties
Packages/packages-lock.json
GameData/run.cc
GameData/player.cc
GameData/RemovedMods/
Vault/Aetheria/.obsidian/plugins/copilot/data.json
# Agent scratch, sandboxed toolchain caches, and throwaway checkouts. These had
# grown to 47 untracked entries; the worktree must stay legible, so they never

View file

@ -1,2 +0,0 @@
[lfs]
url = https://eureka.gamecult.org/forge/GameCult/Aetheria.git/info/lfs

View file

@ -146,5 +146,6 @@ MonoBehaviour:
ActiveEmission: 100
ArmedEmission: 1000
RotationSpeed: 1
GridOffset: 0
HitEffect: {fileID: 1121192042590650, guid: e1b08032bec16ba4eb1ae23cbd572664, type: 3}
HitEffectScale: 10

View file

@ -81,6 +81,87 @@ MeshRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &7783244517717541194
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7783244517717541195}
- component: {fileID: 7783244517717541175}
- component: {fileID: 7783244517717541174}
m_Layer: 9
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7783244517717541195
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7783244517717541194}
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_Children: []
m_Father: {fileID: 7783244518521719148}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &7783244517717541175
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7783244517717541194}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &7783244517717541174
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7783244517717541194}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
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: 44a8bbb89acb4e34c9f41a9da5842bb4, 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 &7783244518317531027
GameObject:
m_ObjectHideFlags: 0
@ -208,6 +289,7 @@ Transform:
m_Children:
- {fileID: 589192530128514016}
- {fileID: 7783244518317531036}
- {fileID: 7783244517717541195}
- {fileID: 7783244516809707565}
m_Father: {fileID: 0}
m_RootOrder: 0
@ -225,6 +307,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
Body: {fileID: 7783244518317531038}
GravityWell: {fileID: 7783244517717541174}
Icon: {fileID: 7783244516809707566}
Generator: {fileID: 741236912059895623}
--- !u!114 &741236912059895623

View file

@ -8,12 +8,12 @@ using UnityEditor.AddressableAssets.Settings;
using UnityEditor.Build.Reporting;
using Debug = UnityEngine.Debug;
// The one way to make a player directory, for Windows or Linux. Owns what the directory contains: the player and its data,
// The one way to make a Win64 player directory. Owns what the directory contains: Aetheria.exe and its data,
// GameData/Aetheria.cc, GameData/Narrative/**, GameData/Mods/** and ModTools/AetherDb. Manual builds, smokes and
// release cuts all call this and stage nothing themselves.
//
// Unity -batchmode -quit -projectPath <repo> -executeMethod AetheriaBuild.Build
// -aetheriaVersion <semver> -aetheriaOut <dir> [-aetheriaTarget win64|linux64] -logFile <log>
// -aetheriaVersion <semver> -aetheriaOut <dir> -logFile <log>
//
// AetherDb is published from tools/AetherDb, so the headless build's CULTLIB_ROOT / CULTMATH_ROOT (Directory.Build.props)
// must be set for dotnet when the defaults do not hold the pinned revisions.
@ -22,14 +22,6 @@ public static class AetheriaBuild
{
private static readonly Regex SemVer = new Regex(@"^\d+\.\d+\.\d+([-+][0-9A-Za-z.-]+)?$");
// A target: the player Unity builds, its executable, and the runtime AetherDb is published for.
private static (BuildTarget target, string player, string runtime, string aetherDb) Target(string name) => name switch
{
"win64" => (BuildTarget.StandaloneWindows64, "Aetheria.exe", "win-x64", "AetherDb.exe"),
"linux64" => (BuildTarget.StandaloneLinux64, "Aetheria.x86_64", "linux-x64", "AetherDb"),
_ => throw new InvalidOperationException($"-aetheriaTarget {name}: expected win64 or linux64"),
};
public static void Build()
{
var step = "arguments";
@ -38,7 +30,6 @@ public static class AetheriaBuild
var version = Argument("-aetheriaVersion");
if (!SemVer.IsMatch(version)) throw new InvalidOperationException("-aetheriaVersion is not a semver");
var output = Path.GetFullPath(Argument("-aetheriaOut"));
var target = Target(Argument("-aetheriaTarget", "win64"));
var repoRoot = Directory.GetParent(UnityEngine.Application.dataPath).FullName;
step = "version";
@ -55,8 +46,8 @@ public static class AetheriaBuild
var report = BuildPipeline.BuildPlayer(new BuildPlayerOptions
{
scenes = scenes,
locationPathName = Path.Combine(output, target.player),
target = target.target,
locationPathName = Path.Combine(output, "Aetheria.exe"),
target = BuildTarget.StandaloneWindows64,
options = BuildOptions.None,
});
if (report.summary.result != BuildResult.Succeeded || report.summary.totalErrors != 0)
@ -66,7 +57,7 @@ public static class AetheriaBuild
PlayerStaging.Stage(repoRoot, output);
step = "aetherdb";
Publish(repoRoot, Path.Combine(output, "ModTools"), target.runtime, target.aetherDb);
Publish(repoRoot, Path.Combine(output, "ModTools"));
step = "burst";
var burst = Path.Combine(output, "Aetheria_BurstDebugInformation_DoNotShip");
@ -83,11 +74,11 @@ public static class AetheriaBuild
EditorApplication.Exit(0);
}
private static void Publish(string repoRoot, string modTools, string runtime, string aetherDb)
private static void Publish(string repoRoot, string modTools)
{
var info = new ProcessStartInfo("dotnet")
{
Arguments = $"publish tools/AetherDb -c Release -r {runtime} --self-contained -p:PublishSingleFile=true -o \"{modTools}\"",
Arguments = $"publish tools/AetherDb -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -o \"{modTools}\"",
WorkingDirectory = repoRoot,
UseShellExecute = false,
RedirectStandardOutput = true,
@ -101,16 +92,15 @@ public static class AetheriaBuild
if (process.ExitCode != 0)
throw new InvalidOperationException($"dotnet publish exited {process.ExitCode}:\n{text}\n{errors.Result}");
}
if (!File.Exists(Path.Combine(modTools, aetherDb)))
throw new InvalidOperationException($"dotnet publish produced no ModTools/{aetherDb}");
if (!File.Exists(Path.Combine(modTools, "AetherDb.exe")))
throw new InvalidOperationException("dotnet publish produced no ModTools/AetherDb.exe");
}
private static string Argument(string name, string fallback = null)
private static string Argument(string name)
{
var args = Environment.GetCommandLineArgs();
var at = Array.IndexOf(args, name);
if (at < 0 || at + 1 >= args.Length)
return fallback ?? throw new InvalidOperationException(name + " is required");
if (at < 0 || at + 1 >= args.Length) throw new InvalidOperationException(name + " is required");
return args[at + 1];
}
}

View file

@ -2,8 +2,9 @@ using System;
using System.IO;
// What a player directory holds beside the executable's data: GameData/Aetheria.cc, GameData/Narrative/** and, when
// the repo has one, GameData/Mods/**. Nothing else under GameData ships (run.cc and player.cc are the player's own).
// Unity-free so the headless suite can prove the rules; AetheriaBuild is the only caller.
// the repo has one, GameData/Mods/**. Nothing else under GameData ships (run.cc and player.cc are the player's own;
// SoundbanksInfo.json is read by no code). Unity-free so the headless suite can prove the rules; AetheriaBuild is the
// only caller.
internal static class PlayerStaging
{
public const string Catalog = "Aetheria.cc";

View file

@ -0,0 +1,83 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Gravity Waves
m_Shader: {fileID: 4800000, guid: 8a8e593b17063b54ea02f7ea5363b59d, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.5
- _Depth: 3.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _Frequency: 27.43
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Phase: 0
- _Power: 8
- _SinePower: 1.25
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c1012d5466454651b7279be93ef9af88
guid: 11fbaa87dcc7a3743a6bd3aa5c86e5ae
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000

View file

@ -7,8 +7,8 @@ Material:
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Gravity Field
m_Shader: {fileID: 4800000, guid: 08d8efa80af84065b54c9bfed784e435, type: 3}
m_Name: Gravity Well
m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
@ -57,6 +57,8 @@ Material:
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 1
- _Depth: 80
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
@ -66,6 +68,7 @@ Material:
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Power: 16
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1

View file

@ -8,7 +8,7 @@ Material:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Sector Tint
m_Shader: {fileID: 4800000, guid: 782740c1a3d7455ea75dc87fafdc8432, type: 3}
m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, 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,6 +72,7 @@ Material:
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Phase: 0
- _Power: 2
- _SinePower: 1.25
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1

View file

@ -0,0 +1,80 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Wormhole Gravity 1
m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 1
- _Depth: 54
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Power: 4
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 244f3e4c0c030a24b8d7aa944895bdcf
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,80 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Wormhole Gravity 1
m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 1
- _Depth: 54
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Power: 4
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0ec0689c58b99c64297b3e429e1d9112
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,82 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Wormhole Gravity
m_Shader: {fileID: 4800000, guid: 268da230635cfb34ab4ed6ad39503fa0, type: 3}
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.15
- _Depth: 2000
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Power: 64
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a9ef17c902709ce41a837c54b809184b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,84 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Wormhole Waves
m_Shader: {fileID: 4800000, guid: 8681ac064d59d15458d16cc9b884262c, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.5
- _Depth: 33.9
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _Frequency: 2
- _FrequencyDist: 60
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _Power: 2
- _SinePower: 1.25
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _Speed: 15
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ef2f6090d5cc14a4e9ce38def3bed9ac
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,73 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Fog Marching 1
m_Shader: {fileID: 4800000, guid: 7a890b6ceccf53c44bfc54b18b5daa58, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _Displacement:
m_Texture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Patch:
m_Texture: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Surface:
m_Texture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _TintTexture:
m_Texture: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _AlphaFloor: 0.01
- _DepthBlend: 512
- _DepthCeiling: 4096
- _FlowSpeed: 2
- _Gamma: 0.5
- _GridCeilingBlend: -300
- _GridCeilingDensity: 0.01
- _GridCeilingFollow: 0
- _GridCeilingOffset: 0
- _GridFillDensity: 0.001
- _GridFloorBlend: 100
- _GridFloorDensity: 2
- _GridFloorOffset: 20
- _GridPatchBlend: 50
- _GridPatchDensity: 2
- _NoiseExponent: 2.25
- _NoiseFrequency: 0.015
- _NoiseSpeed: 0
- _NoiseStrength: 500
- _SafetyDistance: 20
- _Scattering: 0
- _ScatteringDensityExponent: 0.5
- _ScatteringDistExponent: 0.5
- _ScatteringMinDist: 500
- _StepExponent: 2
- _TintExponent: 0.1
- _TurbulenceAmplitude: 100
- _TurbulenceScale: 100
m_Colors:
- _Tint: {r: 0.0627451, g: 0.0627451, b: 0.0627451, a: 1}
m_BuildTextureStacks: []

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a72896cb7097bfa46a1a8f4092e47c72
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,99 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Fog Marching Main Menu
m_Shader: {fileID: 4800000, guid: 7a890b6ceccf53c44bfc54b18b5daa58, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _Displacement:
m_Texture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaPatch:
m_Texture: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaPatchHeight:
m_Texture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaSurfaceHeight:
m_Texture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaTint:
m_Texture: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Patch:
m_Texture: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Surface:
m_Texture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _TintTexture:
m_Texture: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _AlphaFloor: 0
- _DepthBlend: 512
- _DepthCeiling: 1024
- _Exposure: 100
- _FlowAmplitude: 1
- _FlowScale: 1
- _FlowScroll: 1
- _FlowSpeed: 5
- _Gamma: 0.3
- _GridCeilingBlend: -300
- _GridCeilingDensity: 0.01
- _GridCeilingFollow: 0
- _GridCeilingOffset: 0
- _GridFillDensity: 0.001
- _GridFloorBlend: 50
- _GridFloorDensity: 1
- _GridFloorOffset: 100
- _GridPatchBlend: 50
- _GridPatchDensity: 1
- _NebulaFillDensity: 0.00075
- _NebulaFloorBlend: 50
- _NebulaFloorDensity: 1
- _NebulaFloorOffset: 80
- _NebulaPatchBlend: 25
- _NebulaPatchDensity: 0.1
- _NoiseExponent: 2
- _NoiseFrequency: 0.02
- _NoiseSpeed: 0.00001
- _NoiseStrength: 200
- _SafetyDistance: 50
- _Scattering: 0
- _ScatteringDensityExponent: 8
- _ScatteringDistExponent: 0.5
- _ScatteringMinDist: 1
- _StepExponent: 2
- _TintExponent: 1.2
- _TurbulenceAmplitude: 20
- _TurbulenceScale: 30
m_Colors:
- _Tint: {r: 0.09411765, g: 0.09411765, b: 0.09411765, a: 1}
m_BuildTextureStacks: []

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e21702a5e6e98b7409d6913663ceab7e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,65 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Fog Marching Test
m_Shader: {fileID: 4800000, guid: 7a890b6ceccf53c44bfc54b18b5daa58, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _Displacement:
m_Texture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Patch:
m_Texture: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Surface:
m_Texture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _TintTexture:
m_Texture: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _AlphaFloor: 0
- _DepthBlend: 512
- _DepthCeiling: 4096
- _Gamma: 0.66
- _GridCeilingBlend: -300
- _GridCeilingDensity: 0.01
- _GridCeilingFollow: 0
- _GridCeilingOffset: 0
- _GridFillDensity: 0.000025
- _GridFloorBlend: 100
- _GridFloorDensity: 4
- _GridFloorOffset: 0
- _GridPatchBlend: 5
- _GridPatchDensity: 0.1
- _NoiseFrequency: 0.01
- _NoiseSpeed: 0.02
- _NoiseStrength: 50
- _SafetyDistance: 0
- _StepExponent: 2
- _TintExponent: 8
m_Colors:
- _Tint: {r: 2.0078435, g: 2.0078435, b: 2.0078435, a: 1}
m_BuildTextureStacks: []

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ad63650ae6d6cc542a5aad16694b3c46
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,99 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Fog Marching
m_Shader: {fileID: 4800000, guid: 7a890b6ceccf53c44bfc54b18b5daa58, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _Displacement:
m_Texture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaPatch:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaPatchHeight:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaSurfaceHeight:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NebulaTint:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Patch:
m_Texture: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _Surface:
m_Texture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _TintTexture:
m_Texture: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _AlphaFloor: 0.01
- _DepthBlend: 512
- _DepthCeiling: 4096
- _Exposure: 10
- _FlowAmplitude: 512
- _FlowScale: 1024
- _FlowScroll: 0
- _FlowSpeed: 1
- _Gamma: 0.3
- _GridCeilingBlend: -300
- _GridCeilingDensity: 0.01
- _GridCeilingFollow: 0
- _GridCeilingOffset: 0
- _GridFillDensity: 0.0005
- _GridFloorBlend: 50
- _GridFloorDensity: 1
- _GridFloorOffset: 10
- _GridPatchBlend: 150
- _GridPatchDensity: 1
- _NebulaFillDensity: 0.00075
- _NebulaFloorBlend: 150
- _NebulaFloorDensity: 1
- _NebulaFloorOffset: 0
- _NebulaPatchBlend: 51.47
- _NebulaPatchDensity: 0.25
- _NoiseExponent: 3
- _NoiseFrequency: 0.005
- _NoiseSpeed: 0
- _NoiseStrength: -400
- _SafetyDistance: 200
- _Scattering: 50
- _ScatteringDensityExponent: 1
- _ScatteringDistExponent: 1.5
- _ScatteringMinDist: 10
- _StepExponent: 2
- _TintExponent: 2
- _TurbulenceAmplitude: 20
- _TurbulenceScale: 60
m_Colors:
- _Tint: {r: 0.01, g: 0.01, b: 0.01, a: 1}
m_BuildTextureStacks: []

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2ff39f5d101241043ba1dcde19d92f77
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,31 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: MapAsteroid
m_Shader: {fileID: 4800000, guid: 22e2fc798f044a3aa2f6cd71f1df09a7, type: 3}
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 0
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: 3000
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 2800000, guid: 060aefd5a3d5ced4dabd9190a140e76e, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats: []
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []

View file

@ -189,6 +189,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
RotationSpeed: 1
GridOffset: 0
LabelFadeDuration: 0.5
LabelPersistDuration: 3
LabelDisplayAngle: 15

View file

@ -162,6 +162,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
RotationSpeed: 1
GridOffset: 0
LabelFadeDuration: 0.5
LabelPersistDuration: 3
LabelDisplayAngle: 15

View file

@ -162,6 +162,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
RotationSpeed: 1
GridOffset: 0
LabelFadeDuration: 0.5
LabelPersistDuration: 3
LabelDisplayAngle: 15

View file

@ -162,6 +162,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
RotationSpeed: 1
GridOffset: 0
LabelFadeDuration: 0.5
LabelPersistDuration: 3
LabelDisplayAngle: 15

View file

@ -162,6 +162,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
RotationSpeed: 1
GridOffset: 0
LabelFadeDuration: 0.5
LabelPersistDuration: 3
LabelDisplayAngle: 15

View file

@ -30,6 +30,8 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3973935122748960680}
- {fileID: 3973935121058605868}
- {fileID: 3973935122114073483}
- {fileID: 3973935121358855036}
m_Father: {fileID: 0}
m_RootOrder: 0
@ -47,9 +49,94 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
Body: {fileID: 3973935122748960683}
GravityWell: {fileID: 3973935121058605871}
Icon: {fileID: 3973935121358855037}
Generator: {fileID: 0}
SunMaterial: {fileID: 3973935122748960685}
GravityWaves: {fileID: 3973935122114073485}
--- !u!1 &3973935121058605867
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3973935121058605868}
- component: {fileID: 3973935121058605856}
- component: {fileID: 3973935121058605871}
m_Layer: 9
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3973935121058605868
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3973935121058605867}
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: 1
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &3973935121058605856
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3973935121058605867}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3973935121058605871
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3973935121058605867}
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: 44a8bbb89acb4e34c9f41a9da5842bb4, 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 &3973935121358855035
GameObject:
m_ObjectHideFlags: 0
@ -133,6 +220,89 @@ 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

View file

@ -83,6 +83,89 @@ MeshRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &8859079004671924238
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8859079004671924239}
- component: {fileID: 8859079004671924339}
- component: {fileID: 8859079004671924338}
m_Layer: 9
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8859079004671924239
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8859079004671924238}
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: 8859079004937162792}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &8859079004671924339
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8859079004671924238}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &8859079004671924338
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8859079004671924238}
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: 44a8bbb89acb4e34c9f41a9da5842bb4, 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 &8859079004937162791
GameObject:
m_ObjectHideFlags: 0
@ -114,6 +197,7 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8859079005270853336}
- {fileID: 8859079004671924239}
- {fileID: 8859079003226229097}
m_Father: {fileID: 0}
m_RootOrder: 0
@ -131,6 +215,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
Body: {fileID: 8859079005270853338}
GravityWell: {fileID: 8859079004671924338}
Icon: {fileID: 8859079003226229098}
Generator: {fileID: -7153121627769399805}
--- !u!114 &-7153121627769399805

View file

@ -30,6 +30,8 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7852221720971737233}
- {fileID: 7852221720969441490}
- {fileID: 7852221720741322892}
- {fileID: 7852221720816817928}
- {fileID: 7852221720964551245}
m_Father: {fileID: 0}
@ -48,11 +50,96 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
Body: {fileID: 7852221720971737229}
GravityWell: {fileID: 7852221720969441484}
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
@ -219,6 +306,89 @@ MeshRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &7852221720969441489
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7852221720969441490}
- component: {fileID: 7852221720969441485}
- component: {fileID: 7852221720969441484}
m_Layer: 9
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7852221720969441490
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7852221720969441489}
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: 1
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &7852221720969441485
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7852221720969441489}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &7852221720969441484
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7852221720969441489}
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: 44a8bbb89acb4e34c9f41a9da5842bb4, 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 &7852221720971737232
GameObject:
m_ObjectHideFlags: 0

View file

@ -1,5 +1,167 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1826894967093516808
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1030590306481442051}
- component: {fileID: 3216580199816124320}
- component: {fileID: 3211969211988968959}
m_Layer: 9
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1030590306481442051
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1826894967093516808}
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1024, y: 1024, z: 1024}
m_Children: []
m_Father: {fileID: 463084220523011477}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &3216580199816124320
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1826894967093516808}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3211969211988968959
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1826894967093516808}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
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: 244f3e4c0c030a24b8d7aa944895bdcf, 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 &3908760799087214860
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 581261949390603689}
- component: {fileID: 987447324816385543}
- component: {fileID: 3959510878930752720}
m_Layer: 9
m_Name: Gravity Waves
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &581261949390603689
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3908760799087214860}
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 512, y: 512, z: 512}
m_Children: []
m_Father: {fileID: 463084220523011477}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &987447324816385543
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3908760799087214860}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3959510878930752720
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3908760799087214860}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
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: ef2f6090d5cc14a4e9ce38def3bed9ac, 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 &4837153162502999620
GameObject:
m_ObjectHideFlags: 0
@ -27,6 +189,9 @@ Transform:
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 8222188360457572195}
- {fileID: 1030590306481442051}
- {fileID: 581261949390603689}
- {fileID: 8249320701965103923}
m_Father: {fileID: 0}
m_RootOrder: 0
@ -112,3 +277,84 @@ MeshRenderer:
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &7647029665738834910
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8222188360457572195}
- component: {fileID: 2468954688819198096}
- component: {fileID: 8290522694236574882}
m_Layer: 9
m_Name: Gravity Pit
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8222188360457572195
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7647029665738834910}
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 512, y: 512, z: 512}
m_Children: []
m_Father: {fileID: 463084220523011477}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!33 &2468954688819198096
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7647029665738834910}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &8290522694236574882
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7647029665738834910}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
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: a9ef17c902709ce41a837c54b809184b, 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}

View file

@ -0,0 +1,111 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &992254348637625916
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1779911639310264026}
- component: {fileID: 349597964696402918}
- component: {fileID: 2844878085845264008}
- component: {fileID: 917803079556930035}
- component: {fileID: 4397878771830533054}
m_Layer: 14
m_Name: Asteroid Belt
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1779911639310264026
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 992254348637625916}
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_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &349597964696402918
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 992254348637625916}
m_Mesh: {fileID: 0}
--- !u!23 &2844878085845264008
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 992254348637625916}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
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: c018d99199afeca448c64b9022ac7270, 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: 0
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!64 &917803079556930035
MeshCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 992254348637625916}
m_Material: {fileID: 0}
m_IsTrigger: 0
m_Enabled: 1
serializedVersion: 4
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 0}
--- !u!114 &4397878771830533054
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 992254348637625916}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 52538a3309c36fc4583c964a1c0b705f, type: 3}
m_Name:
m_EditorClassIdentifier:

View file

@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: fd5f95c7d7c63b243aadd16f6e9dc702
ComputeShaderImporter:
guid: 1f1bfa2175a13b647888e2949e0cb138
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:

View file

@ -223,6 +223,7 @@ Transform:
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 5321404120083801960}
- {fileID: 5321404118931821940}
- {fileID: 5321404119494867799}
- {fileID: 1093865191529778971}
@ -244,7 +245,7 @@ MonoBehaviour:
Label: {fileID: 5321404119494867796}
Message: {fileID: 6196070888394201391}
Children: []
GravityMesh: {fileID: 0}
GravityMesh: {fileID: 5321404120083801962}
--- !u!1 &5321404118931821943
GameObject:
m_ObjectHideFlags: 0
@ -563,3 +564,93 @@ MonoBehaviour:
m_hasFontAssetChanged: 0
m_renderer: {fileID: 5321404119494867795}
m_maskType: 0
--- !u!1 &5321404120083801964
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5321404120083801960}
- component: {fileID: 5321404120083801963}
- component: {fileID: 5321404120083801962}
m_Layer: 8
m_Name: Gravity Well
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &5321404120083801960
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5321404120083801964}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 10, y: 10, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5321404118521994135}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &5321404120083801963
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5321404120083801964}
m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &5321404120083801962
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5321404120083801964}
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_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: 44a8bbb89acb4e34c9f41a9da5842bb4, 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: 0
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}

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0f3e518a4c9bcc2469abe6cebdcb3691
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Australia.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:01bb478f4433dc42d5533e5030ef103ad163deca6bc40fb412025acd12e35c73
3 size 442795

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8ba25d5d0ecee31408c7ce7460b4e015
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Congo.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:c84ff44083e4f0d9d9314e7b7095ed4ce2955905f26e7926faa09ed222655729
3 size 506722

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1e43b383bf860524986a6947496776d0
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Germany.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:73e4e7231d448879d0a6d8524b3200be75fc8f5011a70cfd670b0897d07eca10
3 size 1284208

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ed470b8eb6cc68c4485ddc05d5a17ca1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/India.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:96c66414822c43a5451ce1910c1e545dedb224b0f3e79bc54f945dd484afc6a2
3 size 3229324

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ca62f6d649360184aaa975c2716dfb27
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Iran.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:b70fbbe6b4832eb1a3f35844bd87c0eb479fc77b6e59d6600b64cbbe7cc1bf16
3 size 1073119

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e2d60c7158f6a01418d78e46c5845729
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Japan.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:d58dc0227cc6e3b7622726c155031467b6d7314623c7e3a33aad8ce1462b2c5b
3 size 1031132

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5dd450a6f5ec92d49871674b33509135
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Mexico.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:9a654b8451a08a0c769712f4fb1d96c3c661457a88bb57ec4288eab36eab7ec1
3 size 633432

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b75e17ea31f6a62488628bf9605bd48c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Russia.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:f4ed581eef681e5357a14efe4e6a1fc52370def7ada6565f4b953e410fa7f39e
3 size 2375319

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5c1aee30ccf3a1e44bddeb4d550edbf0
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Swahili.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:fb0f44e7948c637a2b0f9e0ca098b2f4689d065fe6484723acf0bfa34a26d0a5
3 size 501002

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 63e21276398c8b0449e01d97a088add1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/Thailand.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:888c929f0e29728369448e6981bb8a043c49610f4c4390a2cb549bf6b494d3dc
3 size 143084

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2923f8bd3a2f0fb4084f64138e6d793d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/USA.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:314a30df2033795f04738589c660991e4310f1bfe0578c71bda2a9a72d8a617f
3 size 954928

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a06c0e26f73d51f489303deadad77030
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 790ef350f8658b04480e0645919a21fa
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Geonames/pleiades.csv (Stored with Git LFS) Normal file

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:45b0dbc07d492958b26de3695427076d270bdd0525ab45c415be72560ebccb5f
3 size 400944

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b532694a5ec3cc6458d42aa2835537b5
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/Settings.asset (Stored with Git LFS)

Binary file not shown.

View file

@ -409,13 +409,13 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 23420356}
serializedVersion: 2
m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0, y: -5, z: 0}
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: 0}
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
m_Father: {fileID: 1037490739}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!23 &23420359
MeshRenderer:
m_ObjectHideFlags: 0
@ -8037,7 +8037,7 @@ Camera:
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 5120
m_Bits: 5632
m_RenderingPath: 1
m_TargetTexture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_TargetDisplay: 0
@ -16322,7 +16322,7 @@ MonoBehaviour:
Settings: {fileID: 11400000, guid: cd2344e2001a37544aab0960c164d4ff, type: 2}
ZoneRoot: {fileID: 2095699057}
SectorBrushes: {fileID: 1037490739}
AmbientLight: {fileID: 23420357}
SectorBoundaryBrush: {fileID: 1476765550}
SceneCamera: {fileID: 887677622}
FogCameras:
- {fileID: 559840917}
@ -16333,98 +16333,70 @@ MonoBehaviour:
- {fileID: 488792362}
- {fileID: 970624071}
- {fileID: 2095741464}
FogMaterial: {fileID: 2100000, guid: 2ff39f5d101241043ba1dcde19d92f77, type: 2}
FogFarFadeFraction: 0.125
FarPlaneDistanceMultiplier: 1
AsteroidMeshes:
- Mesh: {fileID: 4300004, guid: bdb4c719a85f3f2419f6c573dd1daf15, type: 3}
Material: {fileID: 2100000, guid: ce975d2d2ccd0914b9260b7d66319aaa, type: 2}
Radius: 1.5022775
- Mesh: {fileID: 4300004, guid: 19fa9aa3b5fee244fb007d58c351be01, type: 3}
Material: {fileID: 2100000, guid: 443981befe15f604aba2b04eef2f0de2, type: 2}
Radius: 1.0907339
- Mesh: {fileID: 4300004, guid: 693c11c21d8f22240ae2b6c576bf923a, type: 3}
Material: {fileID: 2100000, guid: 818a00140c1abb047be1d65d1c20b52a, type: 2}
Radius: 1.345119
- Mesh: {fileID: 4300004, guid: 73f7fb37d1925f246919650feda3df71, type: 3}
Material: {fileID: 2100000, guid: 60bb3870b22a23b458c2bc94e4403bda, type: 2}
Radius: 0.9581426
- Mesh: {fileID: 4300004, guid: 13503d5cc77421a4384053d90e12610f, type: 3}
Material: {fileID: 2100000, guid: 3175592343a31774b872b32ae89a7ee8, type: 2}
Radius: 1.3620772
- Mesh: {fileID: 4300004, guid: 7c93e75f8f4cde14d82932c65389f566, type: 3}
Material: {fileID: 2100000, guid: edee875c32d24154688acf645e314864, type: 2}
Radius: 1.1282945
- Mesh: {fileID: 4300004, guid: b2e60056527750e4c87bd4f6de85ec32, type: 3}
Material: {fileID: 2100000, guid: c3d0e5db17690724b89cff9a71876aa6, type: 2}
Radius: 1.5490558
- Mesh: {fileID: 4300004, guid: 87ef785989ec82b498488e651b61b2c6, type: 3}
Material: {fileID: 2100000, guid: 5686a9131ca8350448768971c9e21da0, type: 2}
Radius: 1.4786077
- Mesh: {fileID: 4300004, guid: a0f93e74d454f5046908a26ecbbc31cf, type: 3}
Material: {fileID: 2100000, guid: 85e9ac4c62af0fb4a94bc35ac5e24254, type: 2}
Radius: 1.1443666
- Mesh: {fileID: 4300004, guid: 4a9e0d7ffd8f8e94abf7a800653c0b4e, type: 3}
Material: {fileID: 2100000, guid: 27b1a299ede684d499133b9a0dc85bda, type: 2}
Radius: 1.1114912
- Mesh: {fileID: 4300004, guid: 232b4a71860923049890d661dee4aff7, type: 3}
Material: {fileID: 2100000, guid: d9e9ebabc89054f4495754372a7b4d8f, type: 2}
Radius: 1.5410137
- Mesh: {fileID: 4300004, guid: 335c54d8095aa3f44bab46ca5fa5bd4c, type: 3}
Material: {fileID: 2100000, guid: 71cd479bb26cd7140ba9d3753f49ffbc, type: 2}
Radius: 1.3792597
- Mesh: {fileID: 4300004, guid: 8942a135422bd014bb3451fe02d656f2, type: 3}
Material: {fileID: 2100000, guid: 0f7035043b158484995ff86e9371b141, type: 2}
Radius: 1.2433527
- Mesh: {fileID: 4300004, guid: 47e8c832aa7d46e4ba06961fddf3cc06, type: 3}
Material: {fileID: 2100000, guid: d5788bb4b0d2d93429d9c6462034e470, type: 2}
Radius: 1.3664026
- Mesh: {fileID: 4300004, guid: 7c1227bb235e0f74aa9a6cdb9e7ccac5, type: 3}
Material: {fileID: 2100000, guid: 060b5e360701d5146adc3d1c65c7a3df, type: 2}
Radius: 1.2458941
- Mesh: {fileID: 4300004, guid: 77a272b796d88344eacf56a79372b45e, type: 3}
Material: {fileID: 2100000, guid: 3cd15eb9c358aa547bcd223ee3c1edde, type: 2}
Radius: 2.157539
- Mesh: {fileID: 4300004, guid: bafc829a0712b174d9485c5bcbc1b529, type: 3}
Material: {fileID: 2100000, guid: b58fd84189173114d8708fb50b43f2d7, type: 2}
Radius: 1.5399581
- Mesh: {fileID: 4300004, guid: 47fa308b6b417034ea47809cb08bccfb, type: 3}
Material: {fileID: 2100000, guid: f56b0896907ac9e41b6fd9dca05f1fa2, type: 2}
Radius: 1.8855069
- Mesh: {fileID: 4300004, guid: fb03a3e40b409db44a810641e0a6c493, type: 3}
Material: {fileID: 2100000, guid: 78859c2284122944493cc283967036db, type: 2}
Radius: 1.2214345
- Mesh: {fileID: 4300004, guid: de7739fee205f4a4f9ae290126a36b61, type: 3}
Material: {fileID: 2100000, guid: 89b312038af5203469d447f7ff01105f, type: 2}
Radius: 1.8183402
- Mesh: {fileID: 4300004, guid: a966f9aba651bf34da1bb6e3e7f26301, type: 3}
Material: {fileID: 2100000, guid: 3326bc3e2b8ada34c9bfd44cf8cf1a1c, type: 2}
Radius: 1.5686527
- Mesh: {fileID: 4300004, guid: 0d55ec2b68453b7418abcd63378eed69, type: 3}
Material: {fileID: 2100000, guid: 12b1f515279281a42b2bd53cdb174488, type: 2}
Radius: 1.9112675
- Mesh: {fileID: 4300004, guid: e5dc38d7ffa6ed242831255d93657aaa, type: 3}
Material: {fileID: 2100000, guid: 5a06a4f6d5df32b4dae76c13939d3ce9, type: 2}
Radius: 1.9507759
- Mesh: {fileID: 4300004, guid: 9f36793bd68d3d84d8116485bf933992, type: 3}
Material: {fileID: 2100000, guid: decc37f956dfc244b956de2d6f5375c2, type: 2}
Radius: 1.5778105
- Mesh: {fileID: 4300004, guid: 214c1534cdd5b3a4a80927e9d879290f, type: 3}
Material: {fileID: 2100000, guid: a20b424628f81de4e8cf080a503b66af, type: 2}
Radius: 1.4580269
- Mesh: {fileID: 4300004, guid: 7db51bba443e10f4f9a597a296ceb0c6, type: 3}
Material: {fileID: 2100000, guid: f96d19f3fe978b044972d1360d2e7707, type: 2}
Radius: 2.430305
- Mesh: {fileID: 4300004, guid: 6116ec4d9786d04408c3b26d0c525407, type: 3}
Material: {fileID: 2100000, guid: 1788de6db766a4444a09dc0c80e1bcb2, type: 2}
Radius: 1.7872484
- Mesh: {fileID: 4300004, guid: 3d6e611c9ebead04b9d6d7d490977ddc, type: 3}
Material: {fileID: 2100000, guid: 0fab294fd86e3b84394a967642904d55, type: 2}
Radius: 1.9273407
- Mesh: {fileID: 4300004, guid: 76b1f709ca820234bbf6c03507333b7e, type: 3}
Material: {fileID: 2100000, guid: 63a8a57976371814689d7fb222e635e9, type: 2}
Radius: 1.70707
- Mesh: {fileID: 4300004, guid: d3c9f05eabff9e24ca9521e0b8dffd50, type: 3}
Material: {fileID: 2100000, guid: 9be65d82397829b46894243c2d91a06d, type: 2}
Radius: 1.6336656
AsteroidSpritesheetWidth: 4
AsteroidSpritesheetHeight: 4
LODHandler: {fileID: 1009503736}
@ -16445,13 +16417,13 @@ MonoBehaviour:
MapGravityMaterials:
- {fileID: 2100000, guid: 841e4091d19548347bd59115047b4e49, type: 2}
- {fileID: 2100000, guid: a92fc6d958edb9f46b6e58134f609cc0, type: 2}
GravityField: {fileID: 2100000, guid: 44a8bbb89acb4e34c9f41a9da5842bb4, type: 2}
Tour: 0
TourSwitchTime: 10
TourFollowDistance: 100
TourHeightOffset: 50
TourFollowOffsetDegrees: 55
MapAsteroidMaterial: {fileID: 2100000, guid: c1012d5466454651b7279be93ef9af88, type: 2}
AsteroidBeltUI: {fileID: 349597964696402918, guid: 1f1bfa2175a13b647888e2949e0cb138,
type: 3}
Planet: {fileID: 8287217960984982033, guid: 88ace6073e3db1a46bd3aaf4f971acb9, type: 3}
GasGiant: {fileID: 3250623180916197132, guid: 85753b4a7c762274385af384abed7a04,
type: 3}
@ -16491,6 +16463,7 @@ MonoBehaviour:
NebulaTint: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
Heightmap: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
ParticleColors: {fileID: 2800000, guid: 56db63da398b2d04394ddbccedce943c, type: 3}
GravityCamera: {fileID: 1537342003}
--- !u!114 &1009503736
MonoBehaviour:
m_ObjectHideFlags: 0
@ -16891,6 +16864,8 @@ Transform:
m_LocalScale: {x: 3000, y: 3000, z: 3000}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1476765548}
- {fileID: 23420357}
- {fileID: 1108843909}
- {fileID: 1647230682}
- {fileID: 1254851255}
@ -22664,6 +22639,96 @@ 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
@ -23500,7 +23565,7 @@ Camera:
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 512
m_Bits: 1536
m_RenderingPath: 1
m_TargetTexture: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
m_TargetDisplay: 0
@ -38421,7 +38486,6 @@ SceneRoots:
- {fileID: 906767442}
- {fileID: 1009503730}
- {fileID: 1037490739}
- {fileID: 23420357}
- {fileID: 2095699057}
- {fileID: 134113271}
- {fileID: 666227432}

View file

@ -892,6 +892,7 @@ MonoBehaviour:
NebulaTint: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2}
Heightmap: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2}
ParticleColors: {fileID: 2800000, guid: 56db63da398b2d04394ddbccedce943c, type: 3}
GravityCamera: {fileID: 2103364771}
--- !u!114 &750375536
MonoBehaviour:
m_ObjectHideFlags: 0
@ -981,7 +982,7 @@ Camera:
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 5120
m_Bits: 5632
m_RenderingPath: 1
m_TargetTexture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_TargetDisplay: 0
@ -1060,6 +1061,16 @@ 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
@ -1075,6 +1086,16 @@ PrefabInstance:
propertyPath: m_Materials.Array.data[0]
value:
objectReference: {fileID: 2100000, guid: b9f52544f98f47146b486fb6e27179bb, type: 2}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x
value: 512
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 512
objectReference: {fileID: 0}
- target: {fileID: 7852221720971737233, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.x

View file

@ -911,6 +911,26 @@ 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
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720971737233, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalPosition.y
@ -1758,6 +1778,26 @@ 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
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720971737233, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalPosition.y
@ -3769,7 +3809,7 @@ Camera:
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 5120
m_Bits: 5632
m_RenderingPath: 1
m_TargetTexture: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2}
m_TargetDisplay: 0
@ -5257,6 +5297,26 @@ 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
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalScale.y
value: 20
objectReference: {fileID: 0}
- target: {fileID: 7852221720971737233, guid: 046dde0f4600bce41ad33c476f99b309,
type: 3}
propertyPath: m_LocalPosition.y

View file

@ -1,244 +0,0 @@
using System;
using System.IO;
using GameCult.Caching;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
namespace Aetheria.EditorTools
{
/// <summary>
/// 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 target built from the descriptor of the production
/// Fog Tint render texture (B10G11R11_UFloatPack32, the format the Tint cameras write), and checks the
/// radial profile against exp(-(r/P)^2 ln(1/_EdgeBrightness)) times the centre, to within what that format stores (a
/// 5-bit mantissa) wherever the gaussian is at least a few ZoneRenderer.AmbientFloor (P is Zone.PopulatedRadius), that it
/// is lit at Pack.Radius, that the quad's rim and every lit texel beside a dark one are
/// at most two floors, and that no two lit texels are more than half a decade apart.
///
/// Renders, so it needs a graphics device: run WITHOUT -nographics.
/// Unity -batchmode -projectPath &lt;repo&gt; -executeMethod Aetheria.EditorTools.AmbientEdgeVerify.Run -logFile &lt;path&gt;
/// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
/// </summary>
public static class AmbientEdgeVerify
{
const string Tag = "[AmbientEdgeVerify]";
const int Size = 512;
const int AmbientLayer = 13;
const string FogTintPath = "Assets/Resources/Fog Tint.renderTexture";
// A float with a 5-bit mantissa (Fog Tint's blue) steps by 2^-5 of its value, and the target truncates to a step.
const float FormatRelativeError = 1f / 32f;
// The profile is held to the gaussian where it is at least this many floors; below that the format's tail is measured, not held.
const float FaithfulFloors = 8f;
// The rim and every lit texel beside a dark one hold at most this many floors, 2^-14 (the smallest normal of the format's
// floats): the one step to dark is the format's last value. Measured on the Fog Tint format it is 1.5 floors at most.
const float StepToDarkFloors = 2f;
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<GameSettings>("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<ZoneRenderer>(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<MeshFilter>().sharedMesh = Resources.GetBuiltinResource<Mesh>("Quad.fbx");
quad.AddComponent<MeshRenderer>().sharedMaterial = AssetDatabase.LoadAssetAtPath<Material>("Assets/Materials/Brushes/Sector Tint.mat");
return quad.transform;
}
static void Check(ZoneRenderer renderer, Zone zone)
{
var production = AssetDatabase.LoadAssetAtPath<RenderTexture>(FogTintPath);
if (production == null || production.graphicsFormat != GraphicsFormat.B10G11R11_UFloatPack32)
Fail("format", $"Fog Tint is {(production == null ? "missing" : production.graphicsFormat.ToString())}, not the B10G11R11_UFloatPack32 that AmbientFloor and FormatRelativeError describe");
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<MeshRenderer>();
var material = meshRenderer.sharedMaterial;
var edge = material.GetFloat("_EdgeBrightness");
var tint = material.GetColor("_Color").linear;
var centre = material.GetFloat("_Depth") * Mathf.Max(tint.r, Mathf.Max(tint.g, tint.b));
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 floor = ZoneRenderer.AmbientFloor;
Debug.Log($"{Tag} format floor {floor:E2} = {floor / centre:E2} of the centre; tail (drawn / floor): "
+ string.Join(" ", System.Linq.Enumerable.Select(new[] { 1.5f, 1.75f, 1.9f, 2f, 2.05f, 2.1f, 2.15f, 2.2f },
k => $"{k}P:{Sample(value, world, new Vector2(k * populated, 0f)) / floor:G3}")));
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;
var expected = centre * Mathf.Exp(-(r / populated) * (r / populated) * logFall);
if (r <= half && expected >= FaithfulFloors * floor)
{
var error = Mathf.Abs(value[i] - expected) / (FormatRelativeError * expected);
worstProfile = Mathf.Max(worstProfile, error);
compared++;
if (error > 1f) { Fail("profile", $"at r = {r / populated:F3} P: expected {expected:G5}, drawn {value[i]:G5}, off by {error:F2} times the format's step"); 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 held to the gaussian, worst error {worstProfile:F3} of the format's step; rim {rim / centre:E2} of the centre");
if (compared < Size * Size / 16) Fail("profile", $"only {compared} texels were held to the gaussian; the check is vacuous");
if (rim > floor * StepToDarkFloors) Fail("rim", $"the quad's rim is {rim / floor:G3} times the format floor");
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) { if (here > floor * StepToDarkFloors) Fail("last-step", $"a lit texel of {here / floor:G3} floors sits beside a dark one"); 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 brightest channel 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 production = AssetDatabase.LoadAssetAtPath<RenderTexture>(FogTintPath) ?? throw new InvalidOperationException("No Fog Tint render texture.");
var rt = new RenderTexture(production.descriptor) { filterMode = FilterMode.Point };
var cameraObject = new GameObject("Ambient Edge Camera");
var read = new Texture2D(Size, Size, TextureFormat.RGBAFloat, false);
var previous = RenderTexture.active;
try
{
var camera = cameraObject.AddComponent<Camera>();
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();
var pixels = read.GetPixelData<float>(0);
value = new float[Size * Size];
for (var i = 0; i < value.Length; i++)
value[i] = Mathf.Max(pixels[4 * i], Mathf.Max(pixels[4 * i + 1], pixels[4 * i + 2]));
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);
}
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 63b03af05dfdae44c89e21ca7ebec892

View file

@ -1,265 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using GameCult.Caching;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using float2 = CultMath.float2;
using float4 = CultMath.float4;
// End-to-end proof of the belt draw (rulings asteroid-tops-at-layer, rock-meshes-by-size), measured where it fails: two belts are loaded
// into a ZoneRenderer carrying ARPG.unity's real AsteroidMeshes (meshes, stored radii) and Settings.asset, over a zone whose wells make the
// field differ rock to rock, and ZoneRenderer.DrawBelts runs as Update runs it. Every instanced matrix it hands to the GPU is read back with the
// mesh it is drawn with (ZoneRenderer.DrawInstanced), and for each drawn rock the verify measures, from the mesh's real vertices:
// - the farthest vertex from the rock's centre after the matrix (the reach, a bound on the rock's top under any rotation) is the rock's
// gameplay radius, and centre y + reach is the floater layer over the field at the rock's xz and the drawn time;
// - the rock is drawn exactly once, on exactly one matrix of a real pose (no spare instance in a batch);
// - within a belt a bigger rock never takes a smaller mesh (by the mesh's own farthest vertex), and the belt keeps min(AsteroidMeshCount, meshes) meshes.
// The arguments the renderer hands the draw list (radius field, belt, batch size, kept count) are not inspected: their consequence is.
//
// Reached: ZoneRenderer.LoadPlanet for each belt, DrawBelts. Not reached: LoadZone's other planets (their prefabs), the GPU draw itself.
// Needs a GPU device for MapPresenter's belt buffer: run without -nographics.
//
// Unity -batchmode -projectPath <project> -executeMethod Aetheria.EditorTools.BeltFloaterLayerVerify.Run -logFile <log>
//
// Do not pass -quit: Run enters play mode (a domain reload), the check runs there, and the Editor exits itself, 0 on success and 1 on any failure.
namespace Aetheria.EditorTools
{
[InitializeOnLoad]
public static class BeltFloaterLayerVerify
{
private const string Tag = "[BeltFloaterLayerVerify]";
private const string Flag = "BeltFloaterLayerVerify.running";
private const string Wiring = "BeltFloaterLayerVerify.wiring";
private const double Budget = 300; // real seconds
private const float Lead = .008f, Layer = -1.5f, Tolerance = .02f;
private static IEnumerator _check;
private static double _started;
private static string _work;
static BeltFloaterLayerVerify()
{
if (SessionState.GetBool(Flag, false)) EditorApplication.playModeStateChanged += OnPlayMode;
}
public static void Run()
{
try
{
SessionState.SetString(Wiring, ReadWiring());
SessionState.SetBool(Flag, true);
EditorApplication.EnterPlaymode();
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void OnPlayMode(PlayModeStateChange change)
{
if (change != PlayModeStateChange.EnteredPlayMode) return;
_work = Path.Combine(Path.GetTempPath(), $"belt-floater-{Guid.NewGuid():N}");
Directory.CreateDirectory(_work);
_started = Time.realtimeSinceStartupAsDouble;
_check = Check();
EditorApplication.update += Step;
}
private static void Step()
{
try
{
if (Time.realtimeSinceStartupAsDouble - _started > Budget) throw new InvalidOperationException($"The check did not finish in {Budget} seconds.");
if (_check.MoveNext()) return;
Finish(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
Finish(1);
}
}
private static void Finish(int code)
{
EditorApplication.update -= Step;
SessionState.SetBool(Flag, false);
try { if (Directory.Exists(_work)) Directory.Delete(_work, true); } catch (Exception) { /* a temp directory */ }
EditorApplication.Exit(code);
}
private static void Require(bool condition, string what)
{
if (!condition) throw new InvalidOperationException(what);
}
// A belt of rocks of every size, its sizes in the given order, round an orbit about the planet.
private static Asteroid[] Rocks(int count, Func<int, float> size, float start) => Enumerable.Range(0, count).Select(i => new Asteroid
{
Distance = start + i * 9.7f, Phase = Mathf.Repeat(i * .6180339887f, 1f), Size = size(i), RotationSpeed = .1f + i * .013f
}).ToArray();
private static IEnumerator Check()
{
var game = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset") ?? throw new InvalidOperationException("No Settings.asset.");
var settings = game.GameplaySettings;
settings.FloaterLayerHeight = Layer; // in memory, never saved back to the asset: a draw that drops the layer fails
var meshes = SessionState.GetString(Wiring, "").Split('|').Select(entry => entry.Split('~')).Select(p => new InstancedMesh
{
Mesh = AssetDatabase.LoadAllAssetsAtPath(p[0]).OfType<Mesh>().First(m => m.name == p[1]),
Material = p[2] == "" ? null : AssetDatabase.LoadAssetAtPath<Material>(p[2]),
Radius = float.Parse(p[3], System.Globalization.CultureInfo.InvariantCulture)
}).ToArray();
Require(meshes.Length > game.AsteroidMeshCount, $"ARPG.unity has {meshes.Length} rock meshes for a kept count of {game.AsteroidMeshCount}: nothing to prune.");
var cache = AetheriaStores.Open(Path.Combine(_work, "belts.cc"), Path.Combine(_work, "belts-run.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
cache.FlushAsync().Wait();
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
CultRecordKey Orbit(CultRecordKey parent, float distance, float phase, float2 fixedPosition = default)
{
var data = new OrbitData { Distance = distance, Phase = phase, FixedPosition = fixedPosition };
if (parent.IsSet()) data.Parent = new CultRecordRef<OrbitData>(parent);
return cache.Upsert(data).Key;
}
// A heavy sun and a planet on a moving rail, the belts round the planet: the field under a rock differs from rock to rock.
var sunOrbit = Orbit(default, 0f, 0f, new float2(700f, -300f));
var planetOrbit = Orbit(sunOrbit, 600f, .3f);
var beltOrbitA = Orbit(planetOrbit, 150f, .1f);
var beltOrbitB = Orbit(planetOrbit, 300f, .6f);
var beltA = cache.Upsert(new AsteroidBeltData { Orbit = new CultRecordRef<OrbitData>(beltOrbitA), Asteroids = Rocks(24, i => (i * 7 % 24) / 23f, 40f) }).Key;
var beltB = cache.Upsert(new AsteroidBeltData { Orbit = new CultRecordRef<OrbitData>(beltOrbitB), Asteroids = Rocks(24, i => (i * 5 % 24) / 23f, 60f) }).Key;
var pack = new ZonePack { Radius = 5000f, Mass = 10000f };
foreach (var orbit in new[] { sunOrbit, planetOrbit, beltOrbitA, beltOrbitB }) pack.Orbits.Add(new CultRecordRef<OrbitData>(orbit));
pack.Planets.Add(new CultRecordRef<BodyData>(cache.Upsert(new PlanetData { Name = "Belt Sun", Orbit = new CultRecordRef<OrbitData>(sunOrbit), Mass = 400f }).Key));
pack.Planets.Add(new CultRecordRef<BodyData>(cache.Upsert(new PlanetData { Name = "Belt Planet", Orbit = new CultRecordRef<OrbitData>(planetOrbit), Mass = 400f }).Key));
pack.Planets.Add(new CultRecordRef<BodyData>(beltA));
pack.Planets.Add(new CultRecordRef<BodyData>(beltB));
var curves = new PlanetSettings
{
BodyRadius = new ExponentialCurve(),
WellStrength = new ExponentialCurve { Multiplier = 1f, Exponent = 1f },
RocheLimit = new ExponentialCurve { Constant = 10f },
AsteroidSize = new ExponentialLerp { Minimum = 2f, Maximum = 9f, Exponent = 1.7f },
AsteroidHitpoints = new ExponentialLerp { Minimum = 10f, Maximum = 300f, Exponent = 2f },
AsteroidRespawnTime = new ExponentialLerp { Minimum = 5f, Maximum = 60f, Exponent = 1f }
};
var zone = new Zone(items, curves, pack, null, null);
zone.Update(.5f);
// The renderer, configured as the scene configures it for belts: Awake builds the map, LoadPlanet reads Zone, Settings and AsteroidMeshes.
var rendererObject = new GameObject("Belt Zone Renderer");
rendererObject.SetActive(false);
var renderer = rendererObject.AddComponent<ZoneRenderer>();
renderer.Settings = game;
renderer.AsteroidMeshes = meshes;
renderer.ItemManager = items;
renderer.LODHandler = new GameObject("Belt LOD").AddComponent<LODHandler>();
const BindingFlags Any = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
typeof(ZoneRenderer).GetProperty("Zone").GetSetMethod(true).Invoke(renderer, new object[] { zone });
typeof(ZoneRenderer).GetMethod("Awake", Any).Invoke(renderer, null);
var load = typeof(ZoneRenderer).GetMethod("LoadPlanet", Any);
foreach (var belt in new[] { beltA, beltB }) load.Invoke(renderer, new object[] { belt, zone.Planets[belt] });
var planetAt = DrawAhead.Orbit(zone, planetOrbit, Lead);
var camera = new GameObject("Belt Camera").AddComponent<Camera>();
camera.transform.position = new Vector3(planetAt.x, 4000f, planetAt.y);
camera.transform.rotation = Quaternion.Euler(90, 0, 0);
camera.farClipPlane = 20000f;
renderer.MainCamera = camera;
yield return null;
ActionGameManager.Clock.Reset();
ActionGameManager.Clock.Advance(Lead, _ => throw new InvalidOperationException("a step ran"));
var lead = ActionGameManager.Clock.Lead;
Require(Mathf.Abs(lead - Lead) < 1e-6f, $"The clock's lead is {lead}, not {Lead}.");
var drawn = new List<(Mesh mesh, Matrix4x4[] matrices)>();
renderer.DrawInstanced = (mesh, _, matrices) => drawn.Add((mesh, (Matrix4x4[]) matrices.Clone()));
renderer.DrawBelts(lead);
// The poses the sim defines at the drawn time: every rock of both belts.
var rocks = new List<(CultRecordKey belt, int index, float4 pose)>();
foreach (var belt in new[] { beltA, beltB })
{
var poses = new float4[((AsteroidBeltData) zone.Planets[belt]).Asteroids.Length];
zone.EvaluateBelt(belt, poses, DrawAhead.Time(zone, lead));
for (var i = 0; i < poses.Length; i++) rocks.Add((belt, i, poses[i]));
}
Require(drawn.Count > 0, "DrawBelts drew nothing: the belts are out of the camera or have no rocks.");
// The field differs between rocks, so a draw that drops it or samples it at the wrong place fails.
var layers = rocks.Select(r => DrawAhead.FloaterHeight(zone, settings, r.pose.xy, lead)).ToArray();
Require(layers.Max() - layers.Min() > 1f, $"The fixture's field is flat under the rocks: layer heights span {layers.Max() - layers.Min()}.");
var seen = new Dictionary<(CultRecordKey, int), float>(); // rock -> its mesh's own farthest vertex
var vertices = new Dictionary<Mesh, Vector3[]>();
foreach (var (mesh, matrices) in drawn)
{
if (!vertices.TryGetValue(mesh, out var v)) vertices[mesh] = v = mesh.vertices;
var own = Mathf.Sqrt(v.Max(x => x.sqrMagnitude));
foreach (var matrix in matrices)
{
var centre = (Vector3) matrix.GetColumn(3);
var at = rocks.Where(r => Mathf.Abs(r.pose.x - centre.x) < .01f && Mathf.Abs(r.pose.y - centre.z) < .01f).ToArray();
Require(at.Length == 1, $"An instance of {mesh.name} sits at {centre.x},{centre.z}, where {at.Length} rocks are (a spare or misplaced instance).");
var rock = at[0];
Require(seen.TryAdd((rock.belt, rock.index), own), $"Rock {rock.index} of {rock.belt} is drawn twice.");
var reach = v.Max(x => (matrix.MultiplyPoint3x4(x) - centre).magnitude);
Require(Mathf.Abs(reach - rock.pose.w) < Tolerance * Mathf.Max(1f, rock.pose.w),
$"Rock {rock.index} of {rock.belt} on {mesh.name}: its farthest vertex is {reach} from its centre, its gameplay radius {rock.pose.w}.");
var top = centre.y + reach;
var want = DrawAhead.FloaterHeight(zone, settings, rock.pose.xy, lead);
Require(Mathf.Abs(top - want) < Tolerance,
$"Rock {rock.index} of {rock.belt} on {mesh.name}: its top is {top}, the floater layer over the field there {want}.");
}
}
Require(seen.Count == rocks.Count, $"{seen.Count} of {rocks.Count} rocks were drawn.");
foreach (var belt in new[] { beltA, beltB })
{
var mine = rocks.Where(r => r.belt.Equals(belt)).ToArray();
var radii = mine.Select(r => seen[(belt, r.index)]).Distinct().OrderBy(x => x).ToArray();
Require(radii.Length >= 2, $"Belt {belt} is drawn on {radii.Length} distinct mesh radii: the size match is not exercised.");
var kept = drawn.Where(d => d.matrices.Any(m => mine.Any(r => Mathf.Abs(r.pose.x - m.m03) < .01f && Mathf.Abs(r.pose.y - m.m23) < .01f))).Select(d => d.mesh).Distinct().Count();
Require(kept == Mathf.Min(game.AsteroidMeshCount, meshes.Length), $"Belt {belt} is drawn on {kept} meshes, not the kept count {game.AsteroidMeshCount}.");
var size = mine.ToDictionary(r => r.index, r => zone.AsteroidBelts[belt].UndamagedSize(r.index, zone.Settings));
foreach (var a in mine)
foreach (var b in mine)
if (size[a.index] < size[b.index])
Require(seen[(belt, a.index)] <= seen[(belt, b.index)],
$"Belt {belt}: rock {a.index} (size {size[a.index]}) is smaller than rock {b.index} ({size[b.index]}) and takes a bigger mesh ({seen[(belt, a.index)]} against {seen[(belt, b.index)]}).");
var biggest = mine.OrderByDescending(r => size[r.index]).First();
Require(Mathf.Approximately(seen[(belt, biggest.index)], radii[^1]), $"Belt {belt}: its biggest rock is not on its biggest mesh.");
}
ActionGameManager.Clock.Reset();
Debug.Log($"{Tag} ok: {rocks.Count} rocks of 2 belts drawn at the floater layer, big rocks on big meshes ({drawn.Count} batches)");
}
// The AsteroidMeshes ARPG.unity's ZoneRenderer names, read before the play session (a scene cannot be opened inside one) and carried across the
// domain reload as asset paths: mesh path, mesh name, material path, stored radius.
private static string ReadWiring()
{
var scene = EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Additive);
try
{
var found = UnityEngine.Object.FindObjectsByType<ZoneRenderer>(FindObjectsInactive.Include, FindObjectsSortMode.None)
.Where(r => r.gameObject.scene == scene).ToArray();
Require(found.Length == 1, $"ARPG.unity has {found.Length} ZoneRenderers.");
var meshes = found[0].AsteroidMeshes ?? throw new InvalidOperationException("ARPG.unity's ZoneRenderer has no AsteroidMeshes.");
return string.Join("|", meshes.Select(m => string.Join("~", AssetDatabase.GetAssetPath(m.Mesh), m.Mesh.name,
m.Material == null ? "" : AssetDatabase.GetAssetPath(m.Material), m.Radius.ToString("R", System.Globalization.CultureInfo.InvariantCulture))));
}
finally { EditorSceneManager.CloseScene(scene, true); }
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 7d25a93db1972324fa5460b2415c1e4a

View file

@ -62,7 +62,7 @@ public static class CapabilityPresentationSmoke
cache.FlushAsync().Wait();
var ledger = new ProvenanceLedger();
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
var items = new ItemManager(cache, ledger, settings, _ => { });
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Smoke", Owner = null }, null);
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hull"));
var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
@ -85,11 +85,6 @@ public static class CapabilityPresentationSmoke
?? throw new InvalidOperationException("No Tractor Beam prefab.");
// An entity's instance as the game builds it: the beam is a child before SetEntity on both ship paths, so the
// binder finds it as the game does.
// The scene's map: an entity registers its icon with the ZoneRenderer's presenter, so each instance carries a quad.
var mapRenderer = new GameObject("Smoke Zone Renderer").AddComponent<ZoneRenderer>();
mapRenderer.Settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset");
mapRenderer.MapGravityMaterials = new Material[0];
Awaken(mapRenderer);
(GameObject Object, EntityInstance Instance, TractorBeam Beam) Bound(string name, Entity entity)
{
var go = new GameObject(name);
@ -103,11 +98,7 @@ public static class CapabilityPresentationSmoke
var tractor = ((GameObject)PrefabUtility.InstantiatePrefab(beamPrefab, go.transform)).GetComponent<TractorBeam>();
tractor.Power = .8f;
tractor.Direction = Vector3.forward;
var icon = GameObject.CreatePrimitive(PrimitiveType.Quad);
icon.transform.SetParent(go.transform);
bound.MapIcon = icon.GetComponent<MeshRenderer>();
bound.MapIcon.sharedMaterial = new Material(Shader.Find("Unlit/Color"));
bound.SetEntity(mapRenderer, entity);
bound.SetEntity(null, entity);
return (go, bound, tractor);
}
var (instanceObject, instance, beam) = Bound("Smoke Entity Instance", ship);

View file

@ -1,4 +1,3 @@
using System.Collections.Generic;
using System;
using System.Linq;
using System.Reflection;
@ -88,13 +87,14 @@ public sealed class InspectableTemperatureDrawer : ICultInspectorDrawer
}
}
// A size and a toggle per cell. The underlay (the item's schematic texture, or a hull's line art stretched to the Shape's
// Width x Height) is drawn by HullUnderlay; the height follows a texture's aspect. A hull's hardpoint cells are tinted.
// Both read the owning record through
// A size and a toggle per cell. When the owning item has a schematic texture, the texture lies under the grid and the
// height follows its aspect; a hull's hardpoint cells are tinted. Both read the owning record through
// inspector.Record, which is a read-only copy: nothing here writes to it.
[CultInspectorDrawer(typeof(InspectableSchematicShapeAttribute))]
public sealed class InspectableSchematicShapeDrawer : ICultInspectorDrawer
{
private static Material _schematicMaterial;
public object Draw(CultInspector inspector, string label, Type type, object value, MemberInfo member)
{
if (type != typeof(Shape)) return inspector.DrawDefault(label, type, value, member);
@ -122,13 +122,18 @@ public sealed class InspectableSchematicShapeDrawer : ICultInspectorDrawer
{
using (var grid = new EditorGUILayout.VerticalScope())
{
HullUnderlay.Draw(grid.rect, item, inspector.Records);
if (schematic != null && Event.current.type == EventType.Repaint)
{
_schematicMaterial ??= new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
EditorGUI.DrawPreviewTexture(grid.rect, schematic, _schematicMaterial);
}
for (var y = shape.Height - 1; y >= 0; y--)
using (new EditorGUILayout.HorizontalScope())
for (var x = 0; x < shape.Width; x++)
{
var hardpoint = hull?.Hardpoints.FirstOrDefault(hp => HullFootprint.Cells(hp).Contains(int2(x, y)));
var hardpoint = hull?.Hardpoints.FirstOrDefault(hp => hp.Shape.Coordinates
.Any(v => v.x + hp.Position.x == x && v.y + hp.Position.y == y));
var previous = GUI.backgroundColor;
if (hardpoint != null) GUI.backgroundColor = hardpoint.TintColor.ToColor();
shape[int2(x, y)] = GUILayout.Toggle(shape[int2(x, y)], GUIContent.none, GUILayout.Width(16));
@ -140,93 +145,3 @@ public sealed class InspectableSchematicShapeDrawer : ICultInspectorDrawer
return shape;
}
}
// The underlay under a hull's cell grids, drawn on Repaint only. A shipped hull's schematic texture is stretched over
// the grid rect; a mod hull's line art (its ShipAuthoring, found in the open cache at hull.Visual) is placed by
// SchematicUnderlay.Fit in cell units and mapped to the rect here, the one site that maps cells to a grid's screen
// rect. The hull Shape grid and the footprint painter both draw through it. No lookup found: no line underlay.
public static class HullUnderlay
{
private static Material _schematicMaterial;
public static void Draw(Rect grid, EquippableItemData item, IReadOnlyList<CultStoredDocument> records)
{
if (item == null || Event.current.type != EventType.Repaint) return;
var texture = string.IsNullOrEmpty(item.Schematic) ? null : AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(item.Schematic));
if (texture != null)
{
_schematicMaterial ??= new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
EditorGUI.DrawPreviewTexture(grid, texture, _schematicMaterial);
}
if (!(item is HullData hull) || hull.Shape == null || hull.Shape.Width == 0 || hull.Shape.Height == 0) return;
var key = CultInspectorModel.RecordKey(hull.Visual);
var authoring = records?.FirstOrDefault(record => record.Key.Value == key && record.Document is ShipAuthoring)?.Document as ShipAuthoring;
if (authoring == null) return;
var lines = authoring.SchematicLines;
var fit = SchematicUnderlay.Fit(lines, hull.Shape);
for (var i = 0; i < fit.Length; i++)
{
var rgba = lines[i].Color;
var color = rgba == null ? Color.white : new Color(rgba[0], rgba[1], rgba[2], rgba[3]);
var points = fit[i].Select(p => new Vector3(
grid.x + p.x / hull.Shape.Width * grid.width,
grid.yMax - p.y / hull.Shape.Height * grid.height)).ToList();
if (lines[i].Cyclic && points.Count > 2) points.Add(points[0]);
using (new Handles.DrawingScope(color)) Handles.DrawAAPolyLine(2f, points.ToArray());
}
}
}
// A hardpoint's schematic footprint over its hull's cells: the hull's occupied cells shaded, this hardpoint's cells lit
// in its tint, other hardpoints' cells tinted and disabled. A click edits Position and Shape together through
// HullFootprint.Toggle, which owns the rule so a Thing host reads the same one. The footprint is authored for gameplay,
// never derived from the model. Read-only on inspector.Record; only the HardpointData it is given changes.
[CultInspectorDrawer(typeof(HardpointData))]
public sealed class HardpointFootprintDrawer : ICultInspectorDrawer
{
public object Draw(CultInspector inspector, string label, Type type, object value, MemberInfo member)
{
var hardpoint = value as HardpointData;
if (type != typeof(HardpointData) || hardpoint == null) return inspector.DrawDefault(label, type, value, member);
var hull = inspector.Record as HullData;
var own = hull?.Hardpoints.FirstOrDefault(hp => hp.Type == hardpoint.Type && hp.Position.Equals(hardpoint.Position)
&& HullFootprint.Cells(hp).SequenceEqual(HullFootprint.Cells(hardpoint)));
var drawn = (HardpointData) inspector.DrawDefault(label, type, value, member);
if (hull?.Shape == null) return drawn;
var others = hull.Hardpoints.Where(hp => hp != own).ToArray();
EditorGUILayout.LabelField("Footprint");
using (new EditorGUILayout.HorizontalScope())
{
using (var grid = new EditorGUILayout.VerticalScope())
{
HullUnderlay.Draw(grid.rect, hull, inspector.Records);
for (var y = hull.Shape.Height - 1; y >= 0; y--)
using (new EditorGUILayout.HorizontalScope())
for (var x = 0; x < hull.Shape.Width; x++)
{
var cell = int2(x, y);
var other = others.FirstOrDefault(hp => HullFootprint.Cells(hp).Contains(cell));
var on = HullFootprint.Cells(drawn).Contains(cell);
var previous = GUI.backgroundColor;
GUI.backgroundColor = on ? drawn.TintColor.ToColor() : other != null ? other.TintColor.ToColor()
: hull.Shape[cell] ? Color.gray : previous;
EditorGUI.BeginChangeCheck();
using (new EditorGUI.DisabledScope(other != null && !on))
GUILayout.Toggle(on || other != null, GUIContent.none, GUILayout.Width(16));
if (EditorGUI.EndChangeCheck() && HullFootprint.Toggle(drawn.Shape, drawn.Position, cell, out var shape, out var position))
{
drawn.Shape = shape;
drawn.Position = position;
GUI.changed = true;
}
GUI.backgroundColor = previous;
}
}
GUILayout.FlexibleSpace();
}
return drawn;
}
}

View file

@ -1,232 +0,0 @@
using System;
using System.Collections;
using System.IO;
using System.Linq;
using GameCult.Caching;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using float2 = CultMath.float2;
using float3 = CultMath.float3;
// Batch proof that the presenters read the drawn pose through the clock: DrawnPoseTests pins the pose maths headlessly,
// and this is what pins that each presenter passes ActionGameManager.Clock.Lead into it. A zone with a sun and a planet on a moving rail
// is built and the pickup and mine prefabs ARPG.unity's ZoneRenderer names are bound to it as the renderer binds them; a loose Projectile is launched on the
// zone. The zone is stepped once, then the clock is set to leads 0, 0.008 and 0.015 s with no step, and each presenter's
// own Update places its transform. A body is drawn at its last step plus its step velocity times the lead, spelled here
// from the body's fields, so a presenter that reads lead 0 (or half the lead) sits on the step and fails.
//
// Reached: ItemPickup, MineInstance, Projectile.Draw. Not reached: ZoneRenderer.Update (Lower needs a ComputeBuffer, a
// GPU device) and the ActionGameManager markers (they need the ARPG scene running); a Lower split so its record fill is
// pure, or a run with a GPU and ARPG.unity in play mode, would reach them.
//
// Unity -batchmode -nographics -projectPath <project> -executeMethod Aetheria.EditorTools.DrawnPoseWiringVerify.Run -logFile <log>
//
// Do not pass -quit: Run enters play mode (a domain reload), the check runs there, and the Editor exits itself, 0 on
// success and 1 on any failure.
namespace Aetheria.EditorTools
{
[InitializeOnLoad]
public static class DrawnPoseWiringVerify
{
private const string Tag = "[DrawnPoseWiringVerify]";
private const string Flag = "DrawnPoseWiringVerify.running";
private const string Wiring = "DrawnPoseWiringVerify.wiring";
private const double Budget = 300; // real seconds
private const float RoundSpeed = 100f, Tolerance = 1e-3f;
private static IEnumerator _check;
private static double _started;
private static string _work;
static DrawnPoseWiringVerify()
{
if (SessionState.GetBool(Flag, false)) EditorApplication.playModeStateChanged += OnPlayMode;
}
public static void Run()
{
try
{
SessionState.SetString(Wiring, ReadWiring());
SessionState.SetBool(Flag, true);
EditorApplication.EnterPlaymode();
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void OnPlayMode(PlayModeStateChange change)
{
if (change != PlayModeStateChange.EnteredPlayMode) return;
_work = Path.Combine(Path.GetTempPath(), $"drawn-pose-wiring-{Guid.NewGuid():N}");
Directory.CreateDirectory(_work);
_started = Time.realtimeSinceStartupAsDouble;
_check = Check();
EditorApplication.update += Step;
}
private static void Step()
{
try
{
if (Time.realtimeSinceStartupAsDouble - _started > Budget) throw new InvalidOperationException($"The check did not finish in {Budget} seconds.");
if (_check.MoveNext()) return;
Finish(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
Finish(1);
}
}
private static void Finish(int code)
{
EditorApplication.update -= Step;
SessionState.SetBool(Flag, false);
try { if (Directory.Exists(_work)) Directory.Delete(_work, true); } catch (Exception) { /* a temp directory */ }
EditorApplication.Exit(code);
}
private static void Require(bool condition, string what)
{
if (!condition) throw new InvalidOperationException(what);
}
private static IEnumerator Check()
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")?.GameplaySettings
?? throw new InvalidOperationException("No Settings.asset.");
settings.FloaterLayerHeight = -1.5f; // nonzero, so a presenter that drops the floater layer fails
var wired = SessionState.GetString(Wiring, "").Split('|').Select(entry => entry.Split('=')).ToDictionary(pair => pair[0], pair => pair[1]);
var pickupPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(wired["SimpleCommodityPickup"])?.GetComponent<ItemPickup>();
var minePrefab = AssetDatabase.LoadAssetAtPath<GameObject>(wired["MinePrefab"])?.GetComponent<MineInstance>();
Require(pickupPrefab != null, "ZoneRenderer.SimpleCommodityPickup is not an ItemPickup in ARPG.unity.");
Require(minePrefab != null, "ZoneRenderer.MinePrefab is not a MineInstance in ARPG.unity.");
var oneCell = new Shape(1, 1);
oneCell[new CultMath.int2(0, 0)] = true;
var cache = AetheriaStores.Open(Path.Combine(_work, "wiring.cc"), Path.Combine(_work, "wiring-run.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
cache.Upsert(new SimpleCommodityData { Name = "Wiring Ore", Shape = oneCell, MaxStack = 100 });
cache.FlushAsync().Wait();
var ledger = new ProvenanceLedger();
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
// A sun and a planet on a moving rail round it, both near the bodies below: the field under a floater changes with the
// drawn time, so a presenter that samples it at the step's time, drops it, or reads lead 0 for the height draws a different y.
var sunOrbit = cache.Upsert(new OrbitData { FixedPosition = new float2(-55, -.5f) }).Key;
var planetOrbit = cache.Upsert(new OrbitData { Parent = new CultRecordRef<OrbitData>(sunOrbit), Distance = 40f, Phase = 0f }).Key;
var pack = new ZonePack { Radius = 5000f, Mass = 10000f };
pack.Orbits.Add(new CultRecordRef<OrbitData>(sunOrbit));
pack.Orbits.Add(new CultRecordRef<OrbitData>(planetOrbit));
pack.Planets.Add(new CultRecordRef<BodyData>(cache.Upsert(new PlanetData { Name = "Wiring Sun", Orbit = new CultRecordRef<OrbitData>(sunOrbit), Mass = 400f }).Key));
pack.Planets.Add(new CultRecordRef<BodyData>(cache.Upsert(new PlanetData { Name = "Wiring Planet", Orbit = new CultRecordRef<OrbitData>(planetOrbit), Mass = 400f }).Key));
var curves = new PlanetSettings
{
BodyRadius = new ExponentialCurve(),
WellStrength = new ExponentialCurve { Multiplier = 1f, Exponent = 1f },
RocheLimit = new ExponentialCurve { Constant = 10f }
};
var zone = new Zone(items, curves, pack, null, null);
var rootObject = new GameObject("Wiring Zone Root");
var ore = new SimpleCommodity { Data = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<SimpleCommodityData>("Wiring Ore")), Quantity = 7 };
var floating = zone.Release(ore, new float2(-17, 4), new float2(10, 0));
var mine = new Mine
{
Body = new KinematicBody { Position = new float2(-20, 8), Velocity = new float2(0, 14) },
ArmingDelay = 1e6f, FuseDelay = 1f, Lifetime = 1e6f, BlastRadius = 1f
};
zone.Lay(mine);
// Bound the way ZoneRenderer.LoadFloatingItem and LoadMine bind them: the prefab ARPG.unity names, Bind(zone, body, settings).
// (A ZoneRenderer would load this zone's sun and planet too, and that needs the planet prefab, the map and a GPU.)
var pickup = UnityEngine.Object.Instantiate(pickupPrefab, rootObject.transform);
pickup.Bind(zone, floating, settings);
var mineInstance = UnityEngine.Object.Instantiate(minePrefab, rootObject.transform);
mineInstance.Bind(zone, mine, settings);
// A round on its frozen line, no target and no outcome known: drawn at FireOrigin + direction * speed * (T - FireTime).
var shot = new PendingShot
{
ShotId = 1, FireOrigin = new float3(0, 0, 0), FireTime = 0f, TravelDirection = new float2(1, 0), Speed = RoundSpeed,
MaxRange = 1e6f, FireRange = 1e6f, CommitTime = 1e5f, ArrivalTime = 2e5f
};
var projectile = new GameObject("Wiring Round").AddComponent<Projectile>();
projectile.Launch(null, zone, shot, new Vector3(0, 0, 0));
yield return null;
zone.Update(.5f);
var pickupVelocity = floating.Body.TotalVelocity;
var mineVelocity = mine.Body.TotalVelocity;
Require(Mathf.Abs(pickupVelocity.x) > 1f && Mathf.Abs(mineVelocity.y) > 1f, "The fixture's bodies stopped: a lead would not move them.");
// The field under each floater really is a function of the drawn time here, by more than the tolerance at the largest lead.
const float LargestLead = .015f;
foreach (var at in new[] { floating.Body.Position + pickupVelocity * LargestLead, mine.Body.Position + mineVelocity * LargestLead })
Require(Mathf.Abs(DrawAhead.Height(zone, at, LargestLead) - zone.GetHeight(at, zone.ExactTime)) > 5 * Tolerance,
$"The fixture's field is flat under a floater: height {DrawAhead.Height(zone, at, LargestLead)} at the drawn time, {zone.GetHeight(at, zone.ExactTime)} at the step, planet at {zone.OrbitPositionAt(planetOrbit, zone.ExactTime)}, floater at {at}.");
Vector2 baseline = default;
foreach (var leadSeconds in new[] { 0f, .008f, .015f })
{
ActionGameManager.Clock.Reset();
if (leadSeconds > 0) ActionGameManager.Clock.Advance(leadSeconds, _ => throw new InvalidOperationException("a step ran"));
var lead = ActionGameManager.Clock.Lead;
Require(Mathf.Abs(lead - leadSeconds) < 1e-6f, $"The clock's lead is {lead}, not {leadSeconds}.");
yield return null;
var seen = pickup.transform.position;
var want = floating.Body.Position + pickupVelocity * lead;
Require(Mathf.Abs(seen.x - want.x) < Tolerance && Mathf.Abs(seen.z - want.y) < Tolerance,
$"ItemPickup at lead {lead}: drawn x/z {seen.x},{seen.z}, the step plus velocity times lead {want.x},{want.y}.");
Require(Mathf.Abs(seen.y - DrawAhead.FloaterHeight(zone, settings, new float2(want.x, want.y), lead)) < Tolerance,
$"ItemPickup y {seen.y} is not on the floater layer.");
seen = mineInstance.transform.position;
want = mine.Body.Position + mineVelocity * lead;
Require(Mathf.Abs(seen.x - want.x) < Tolerance && Mathf.Abs(seen.z - want.y) < Tolerance,
$"MineInstance at lead {lead}: drawn x/z {seen.x},{seen.z}, the step plus velocity times lead {want.x},{want.y}.");
Require(Mathf.Abs(seen.y - DrawAhead.FloaterHeight(zone, settings, new float2(want.x, want.y), lead)) < Tolerance,
$"MineInstance y {seen.y} is not on the floater layer.");
// The round's line is a function of the drawn time: it moves RoundSpeed * lead along its direction between leads.
seen = projectile.transform.position;
if (leadSeconds == 0f) baseline = new Vector2(seen.x, seen.z);
var moved = new Vector2(seen.x, seen.z) - baseline;
Require(Mathf.Abs(moved.x - RoundSpeed * lead) < Tolerance && Mathf.Abs(moved.y) < Tolerance,
$"Projectile at lead {lead}: drawn {moved.x},{moved.y} past its lead-0 pose, expected {RoundSpeed * lead},0.");
}
ActionGameManager.Clock.Reset();
Debug.Log($"{Tag} ok: ItemPickup, MineInstance and Projectile draw at the clock's lead (0, 0.008, 0.015 s)");
}
// What ARPG.unity's ZoneRenderer names in the pickup and mine fields, read from the saved scene before the play session
// (a scene cannot be opened inside one) and carried across the domain reload as asset paths.
private static string ReadWiring()
{
var scene = EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Additive);
try
{
var found = UnityEngine.Object.FindObjectsByType<ZoneRenderer>(FindObjectsInactive.Include, FindObjectsSortMode.None)
.Where(r => r.gameObject.scene == scene).ToArray();
Require(found.Length == 1, $"ARPG.unity has {found.Length} ZoneRenderers.");
var wired = found[0];
return string.Join("|", new (string, UnityEngine.Object)[]
{
("SimpleCommodityPickup", wired.SimpleCommodityPickup), ("MinePrefab", wired.MinePrefab)
}.Select(f => f.Item1 + "=" + (f.Item2 == null ? "" : AssetDatabase.GetAssetPath(f.Item2))));
}
finally { EditorSceneManager.CloseScene(scene, true); }
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: c2270cf68a4af384eb715c8b5a55f5e8

View file

@ -24,8 +24,7 @@ public static class FloatingItemsDrawnSmoke
private const string Flag = "FloatingItemsDrawnSmoke.running";
private const string Wiring = "FloatingItemsDrawnSmoke.wiring";
private static IEnumerator _check;
private const double Budget = 300; // real seconds: batch frames can be far shorter than a game second
private static double _started;
private static int _frames;
private static string _work;
static FloatingItemsDrawnSmoke()
@ -53,7 +52,6 @@ public static class FloatingItemsDrawnSmoke
if (change != PlayModeStateChange.EnteredPlayMode) return;
_work = Path.Combine(Path.GetTempPath(), $"floating-items-smoke-{Guid.NewGuid():N}");
Directory.CreateDirectory(_work);
_started = Time.realtimeSinceStartupAsDouble;
_check = Check();
EditorApplication.update += Step;
}
@ -62,7 +60,7 @@ public static class FloatingItemsDrawnSmoke
{
try
{
if (Time.realtimeSinceStartupAsDouble - _started > Budget) throw new InvalidOperationException($"The check did not finish in {Budget} seconds.");
if (++_frames > 3000) throw new InvalidOperationException("The check did not finish in 3000 frames.");
if (_check.MoveNext()) return;
Finish(0);
}
@ -90,8 +88,6 @@ public static class FloatingItemsDrawnSmoke
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")?.GameplaySettings
?? throw new InvalidOperationException("No Settings.asset.");
// A floater rides its own layer: a nonzero one (never saved back to the asset), so a presenter that drops it fails.
settings.FloaterLayerHeight = -1.5f;
// The wiring: the scene's ZoneRenderer names an ItemPickup prefab in each of the four fields, and none of those
// prefabs keeps a script that no longer exists.
@ -124,7 +120,7 @@ public static class FloatingItemsDrawnSmoke
cache.FlushAsync().Wait();
var ledger = new ProvenanceLedger();
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
var items = new ItemManager(cache, ledger, settings, _ => { });
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), null, null);
EquippableItem Make(string name, int lot) => new EquippableItem
{
@ -149,7 +145,6 @@ 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<Camera>();
var slimeObject = new GameObject("Smoke Slime");
slimeObject.SetActive(false); // the real Slime needs its compute shader; LoadZone only sets its radius
@ -160,9 +155,8 @@ public static class FloatingItemsDrawnSmoke
ItemPickup[] Pickups() => rootObject.GetComponentsInChildren<ItemPickup>(true).Where(p => p != null).ToArray();
void At(ItemPickup pickup, FloatingItem floating, string what)
{
var lead = ActionGameManager.Clock.Lead;
var at = DrawAhead.Position(floating.Body, lead);
var want = new Vector3(at.x, DrawAhead.FloaterHeight(zone, settings, at, lead), at.y);
var at = floating.Body.Position;
var want = new Vector3(at.x, zone.GetHeight(at), at.y);
Require(Vector3.Distance(pickup.transform.position, want) < 1e-3f,
$"{what}: the pickup is at {pickup.transform.position}, the body at {want}.");
}
@ -191,13 +185,10 @@ public static class FloatingItemsDrawnSmoke
var earlyPickup = Pickups().Single(p => p.Item == early.Item);
earlyPickup.LabelPersistDuration = 1;
earlyPickup.LabelFadeDuration = .2f;
// Game time, not frames: batch frames can be far shorter than a game second, so a frame count can run out before the fade.
var labelUntil = Time.time + earlyPickup.LabelPersistDuration + earlyPickup.LabelFadeDuration + 1f;
while (Time.time < labelUntil && earlyPickup.ScanLabel.color.a > .05f) yield return null;
for (var i = 0; i < 1200 && earlyPickup.ScanLabel.color.a > .05f; i++) yield return null;
Require(earlyPickup.ScanLabel.color.a <= .05f, "label: an unviewed, unheld label did not go dark.");
Require(zone.TryHold(taker, early.Id), "label: the zone refused to hold the item.");
labelUntil = Time.time + earlyPickup.LabelPersistDuration + earlyPickup.LabelFadeDuration + 1f;
while (Time.time < labelUntil && earlyPickup.ScanLabel.color.a < .95f) yield return null;
for (var i = 0; i < 1200 && earlyPickup.ScanLabel.color.a < .95f; i++) yield return null;
Require(earlyPickup.ScanLabel.color.a >= .95f, "label: a held item's label is not lit.");
// The test scene's stand-in sim and tendril ride the zone's own held-pose curve: its pose at each point of a grab

View file

@ -1,302 +0,0 @@
using System;
using System.IO;
using GameCult.Caching;
using UnityEditor;
using UnityEngine;
namespace Aetheria.EditorTools
{
/// <summary>
/// Batchmode check for flow-shader-parity (campaign aetheria-fauna, cut fauna-flow-field): the HLSL globalFlow and
/// slopeFlow of Assets/Shaders/Volumetric.cginc give the values of FlowField (ServerShared) for the same position,
/// scroll, settings and gradient. It drives the real lowering: a Zone is stepped, VolumeSampling.LowerFlow sets the
/// shader globals from the zone's Settings.Flow and FlowScroll, FlowParity.compute reads those globals, and the
/// results are compared with FlowField. It also checks that Settings.asset carries the authored flow under
/// PlanetSettings and that the shader got the zone's scroll (speed times zone time).
///
/// Dispatches a compute kernel, so it needs a graphics device: run WITHOUT -nographics (as ShieldPanelCut3Verify).
/// Unity -batchmode -projectPath &lt;repo&gt; -executeMethod Aetheria.EditorTools.FlowFieldParityVerify.Run -logFile &lt;path&gt;
/// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
/// </summary>
public static class FlowFieldParityVerify
{
const string Tag = "[FlowFieldParityVerify]";
// Measured disagreement between the HLSL and the C# on this tree (float32 on both sides, positions to 10000,
// scrolls to 100, through the keyword-gated flow()): max 2.9e-5 of amplitude (the "max error" line this run
// prints). The tolerance is 5x that noise, so constant errors of 0.05% (y factor .501, swirl or amplitude
// x1.0005: 4e-4 to 8e-4) fail; a 0.01% error is at the noise floor and unresolvable.
const float RelativeTolerance = 1.5e-4f;
static float _maxError;
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 == UnityEngine.Rendering.GraphicsDeviceType.Null)
{
Debug.LogError($"{Tag} FAIL: no graphics device in this session; rerun batchmode without -nographics.");
EditorApplication.Exit(1);
return;
}
// The flow's error lines (a shader keyword that does not exist, a missing property) fail the check.
Application.logMessageReceived += OnLog;
var work = Path.Combine(Path.GetTempPath(), "flow-parity-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(work);
try
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")
?? throw new InvalidOperationException("No Settings.asset.");
var compute = AssetDatabase.LoadAssetAtPath<ComputeShader>("Assets/Shaders/Compute/FlowParity.compute")
?? throw new InvalidOperationException("No FlowParity.compute.");
CheckAuthoredFlow(settings.PlanetSettings.Flow);
// The authored flow (global term only: its slope amplitudes are authored zero), then a flow with every
// term live and a scroll speed that reaches scrolls up to 100.
var authored = settings.PlanetSettings.Flow;
CheckParity("authored", settings, work, compute, authored, new[] { 0.0, 3.1, 40.0, 100.0 });
var wide = new FlowSettings
{
GlobalScale = authored.GlobalScale, GlobalAmplitude = authored.GlobalAmplitude, Period = authored.Period,
GlobalScrollSpeed = 1f, SlopeAmplitude = 3f, SwirlAmplitude = 5f
};
CheckParity("wide", settings, work, compute, wide, new[] { 0.0, 1.0, 17.0, 60.0, 100.0 });
CheckNoiseClock(compute);
Debug.Log($"{Tag} max error {_maxError:G4} of amplitude (tolerance {RelativeTolerance:G4})");
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);
}
// Settings.asset carries the authored flow under PlanetSettings (it moved from DefaultEnvironment unchanged).
static void CheckAuthoredFlow(FlowSettings flow)
{
if (flow == null) { Fail("authored-flow", "PlanetSettings.Flow is null"); return; }
if (flow.GlobalScale != 512f || flow.GlobalAmplitude != 15f || flow.GlobalScrollSpeed != .025f ||
flow.Period != 8f || flow.SlopeAmplitude != 0f || flow.SwirlAmplitude != 0f)
Fail("authored-flow", $"PlanetSettings.Flow is {flow.GlobalScale}/{flow.GlobalAmplitude}/{flow.GlobalScrollSpeed}/" +
$"{flow.Period}/{flow.SlopeAmplitude}/{flow.SwirlAmplitude}, authored 512/15/0.025/8/0/0");
}
internal static Zone BuildZone(GameSettings settings, string work, FlowSettings flow, double time = 0)
{
var cache = AetheriaStores.Open(Path.Combine(work, Guid.NewGuid().ToString("N") + ".cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
cache.FlushAsync().Wait();
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
return new Zone(items, new PlanetSettings { Flow = flow }, new ZonePack { Time = time }, new GalaxyZone { Name = "Parity", Owner = null }, null);
}
static void CheckParity(string label, GameSettings settings, string work, ComputeShader compute, FlowSettings flow,
double[] zoneTimes)
{
var zone = BuildZone(settings, work, flow);
var kernel = compute.FindKernel("Evaluate");
var rng = new System.Random(label.Length * 7919);
const int positionCount = 256, gradientCount = 64;
const int count = positionCount;
var positions = new Vector3[count];
for (var i = 0; i < positionCount; i++)
positions[i] = new Vector3((float) (rng.NextDouble() * 20000 - 10000), (float) (rng.NextDouble() * 200 - 100),
(float) (rng.NextDouble() * 20000 - 10000));
// Gradients of any direction and magnitude, plus one zero gradient.
var gradients = new Vector2[count];
for (var i = 1; i < gradientCount; i++)
{
var angle = rng.NextDouble() * Math.PI * 2;
var magnitude = (float) Math.Exp(rng.NextDouble() * 8 - 4);
gradients[i] = new Vector2((float) Math.Cos(angle) * magnitude, (float) Math.Sin(angle) * magnitude);
}
gradients[0] = Vector2.zero;
var positionBuffer = new ComputeBuffer(count, 12);
var gradientBuffer = new ComputeBuffer(count, 8);
var globalBuffer = new ComputeBuffer(count, 12);
var slopeBuffer = new ComputeBuffer(count, 12);
var gatedBuffer = new ComputeBuffer(count, 12);
ComputeBuffer noiseBuffer = null;
// flow() reads the nebula's gravity height under FLOW_SLOPE. A ramp rising along the texture's x gives every
// position inside the grid the gradient direction (1, 0), so the slope term is live where a flat height
// would zero it (and the FLOW_SLOPE keyword and its pragma would not matter).
const int rampSize = 64;
var height = new Texture2D(rampSize, rampSize, TextureFormat.RFloat, false)
{ filterMode = FilterMode.Point, wrapMode = TextureWrapMode.Clamp };
var ramp = new float[rampSize * rampSize];
for (var i = 0; i < ramp.Length; i++) ramp[i] = (i % rampSize) / (float) (rampSize - 1);
height.SetPixelData(ramp, 0);
height.Apply();
var rampGradient = new CultMath.float2(1f, 0f);
try
{
positionBuffer.SetData(positions);
gradientBuffer.SetData(gradients);
compute.SetBuffer(kernel, "positions", positionBuffer);
compute.SetBuffer(kernel, "gradients", gradientBuffer);
compute.SetBuffer(kernel, "globalOut", globalBuffer);
compute.SetBuffer(kernel, "slopeOut", slopeBuffer);
compute.SetBuffer(kernel, "gatedOut", gatedBuffer);
compute.SetTexture(kernel, "_NebulaSurfaceHeight", height);
compute.SetVector("_NebulaSurfaceHeight_TexelSize", new Vector4(1f / rampSize, 1f / rampSize, rampSize, rampSize));
// The grid spans 40000: every sampled position (|xz| to 10000) is inside the ramp's uv range.
Shader.SetGlobalVector("_GridTransform", new Vector4(0f, 0f, 40000f, 0f));
noiseBuffer = new ComputeBuffer(count, 4);
compute.SetBuffer(kernel, "noiseOut", noiseBuffer);
compute.SetInt("count", count);
var elapsed = 0.0;
foreach (var target in zoneTimes)
{
// Step the zone to the target by an awkward step; the sum is the zone's time.
while (elapsed < target - 1e-9)
{
var dt = (float) Math.Min(0.37, target - elapsed);
zone.Update(dt);
elapsed += dt;
}
VolumeSampling.LowerFlow(zone.Settings.Flow, zone.ExactTime);
// The shader got the zone's scroll and cycle: the same zone time Zone.Flow evaluates at.
var zoneScroll = zone.FlowScroll;
var lowered = Shader.GetGlobalVector("_FlowScroll");
if (Math.Abs(lowered.x - zoneScroll.x) > 1e-6 || Math.Abs(lowered.y - zoneScroll.y) > 1e-6)
Fail($"{label}-lowered-scroll", $"at zone time {elapsed:F3} the shader got _FlowScroll ({lowered.x}, {lowered.y}), the zone's is ({zoneScroll.x}, {zoneScroll.y})");
if (Math.Abs(Shader.GetGlobalFloat("_FlowCycle") - zone.CyclePhase(flow.Period)) > 1e-6)
Fail($"{label}-lowered-cycle", $"at zone time {elapsed:F3} the shader got _FlowCycle {Shader.GetGlobalFloat("_FlowCycle")}, the zone's phase is {zone.CyclePhase(flow.Period)}");
if (Shader.GetGlobalFloat("_FlowScale") != flow.GlobalScale || Shader.GetGlobalFloat("_FlowAmplitude") != flow.GlobalAmplitude ||
Shader.GetGlobalFloat("_FlowSlopeAmplitude") != flow.SlopeAmplitude ||
Shader.GetGlobalFloat("_FlowSwirlAmplitude") != flow.SwirlAmplitude || Shader.GetGlobalFloat("_FlowPeriod") != flow.Period)
Fail($"{label}-lowered-settings", "a flow uniform does not equal the zone's FlowSettings");
compute.Dispatch(kernel, (count + 63) / 64, 1, 1);
var globalOut = new Vector3[count];
var slopeOut = new Vector3[count];
var gatedOut = new Vector3[count];
globalBuffer.GetData(globalOut);
slopeBuffer.GetData(slopeOut);
gatedBuffer.GetData(gatedOut);
var at = $"{label} zone time {elapsed:F2} scroll {zoneScroll.x:F4}";
for (var i = 0; i < positionCount; i++)
{
var p = positions[i];
var expected = FlowField.Global(flow, new CultMath.float3(p.x, p.y, p.z), zoneScroll);
Compare($"{label}-global", at, i, new Vector3(expected.x, expected.y, expected.z), globalOut[i], flow.GlobalAmplitude);
}
// flow() is the keyword-gated function the nebula and stardust call: the global term plus, under
// FLOW_SLOPE, the slope term of the ramp's gradient. A flow that is live must not read zero.
var live = 0;
var gatedScale = Math.Max(Math.Abs(flow.GlobalAmplitude), Math.Max(Math.Abs(flow.SlopeAmplitude), Math.Abs(flow.SwirlAmplitude)));
for (var i = 0; i < positionCount; i++)
{
var p = positions[i];
var expected = FlowField.At(flow, new CultMath.float3(p.x, p.y, p.z), zoneScroll, rampGradient);
var e = new Vector3(expected.x, expected.y, expected.z);
if (e.magnitude > .1f * gatedScale) live++;
Compare($"{label}-flow-keywords", at, i, e, gatedOut[i], gatedScale);
}
if (gatedScale != 0f && live < positionCount / 4)
Fail($"{label}-flow-keywords", $"{at}: only {live} of {positionCount} zone flows are live; the check is vacuous");
var slopeScale = Math.Max(Math.Abs(flow.SlopeAmplitude), Math.Abs(flow.SwirlAmplitude));
for (var i = 0; i < gradientCount; i++)
{
var g = gradients[i];
var expected = FlowField.Slope(flow, new CultMath.float2(g.x, g.y));
Compare($"{label}-slope", at, i, new Vector3(expected.x, expected.y, expected.z), slopeOut[i], slopeScale);
}
}
}
finally
{
positionBuffer.Release(); gradientBuffer.Release(); globalBuffer.Release(); slopeBuffer.Release(); gatedBuffer.Release(); noiseBuffer?.Release(); UnityEngine.Object.DestroyImmediate(height);
}
}
// The nebula noise the flow advects evolves on the flow clock VolumeSampling lowers as _NebulaNoiseTime, never on
// _Time: with the speed non-zero, the noise changes when _NebulaNoiseTime does and not otherwise (a frame clock
// would not follow the global set here).
static void CheckNoiseClock(ComputeShader compute)
{
const int count = 64;
var kernel = compute.FindKernel("Evaluate");
var rng = new System.Random(4217);
var positions = new Vector3[count];
for (var i = 0; i < count; i++)
positions[i] = new Vector3((float) (rng.NextDouble() * 4000 - 2000), (float) (rng.NextDouble() * 100 - 50), (float) (rng.NextDouble() * 4000 - 2000));
var buffers = new[] { new ComputeBuffer(count, 12), new ComputeBuffer(count, 8), new ComputeBuffer(count, 12), new ComputeBuffer(count, 12), new ComputeBuffer(count, 12), new ComputeBuffer(count, 4) };
var height = new Texture2D(1, 1, TextureFormat.RFloat, false);
try
{
buffers[0].SetData(positions);
buffers[1].SetData(new Vector2[count]);
compute.SetBuffer(kernel, "positions", buffers[0]);
compute.SetBuffer(kernel, "gradients", buffers[1]);
compute.SetBuffer(kernel, "globalOut", buffers[2]);
compute.SetBuffer(kernel, "slopeOut", buffers[3]);
compute.SetBuffer(kernel, "gatedOut", buffers[4]);
compute.SetBuffer(kernel, "noiseOut", buffers[5]);
compute.SetTexture(kernel, "_NebulaSurfaceHeight", height);
compute.SetInt("count", count);
Shader.SetGlobalFloat("_NebulaNoiseSpeed", 1f);
float[] Noise(float noiseTime)
{
Shader.SetGlobalFloat("_NebulaNoiseTime", noiseTime);
compute.Dispatch(kernel, 1, 1, 1);
var result = new float[count];
buffers[5].GetData(result);
return result;
}
var a = Noise(0f);
var b = Noise(3f);
var again = Noise(3f);
int moved = 0, repeated = 0;
for (var i = 0; i < count; i++)
{
if (Math.Abs(a[i] - b[i]) > 1e-4f) moved++;
if (b[i] == again[i]) repeated++;
}
if (moved < count / 2) Fail("nebula-noise-clock", $"only {moved} of {count} noise samples follow _NebulaNoiseTime");
if (repeated != count) Fail("nebula-noise-clock", $"{count - repeated} of {count} noise samples changed with _NebulaNoiseTime held");
}
finally
{
foreach (var buffer in buffers) buffer.Release();
UnityEngine.Object.DestroyImmediate(height);
}
}
// Error is measured against the term's amplitude; a term with no amplitude must be exactly zero and never NaN.
static void Compare(string check, string at, int index, Vector3 expected, Vector3 actual, float amplitude)
{
var error = (expected - actual).magnitude;
if (float.IsNaN(error)) { Fail(check, $"{at} sample {index}: NaN (expected {expected}, shader {actual})"); return; }
if (amplitude != 0f) _maxError = Math.Max(_maxError, error / Math.Abs(amplitude));
var tolerance = RelativeTolerance * Math.Abs(amplitude);
if (error > tolerance)
Fail(check, $"{at} sample {index}: expected {expected}, shader {actual}, error {error:G4} > {tolerance:G4}");
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 8fae18f5f32f10f43ba892a097f18caa

View file

@ -1,392 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering;
namespace Aetheria.EditorTools
{
/// <summary>
/// Batchmode check for the flow as the scene runs it (campaign aetheria-fauna, cut fauna-flow-field; target
/// invariants flow-one-owner and flow-shader-parity). FlowFieldParityVerify proves the HLSL against FlowField with
/// no scene loaded; this opens ARPG.unity, lets VolumeSampling and Stardust initialise as the game does, drives
/// their own Start and Update with a zone attached, and asserts what the presenters did:
/// - nothing threw and nothing logged an error (a type that cannot initialise in a scene, as VolumeSampling once
/// could when it created its keywords in a static field initializer, throws on its first use);
/// - FLOW_GLOBAL and FLOW_SLOPE are enabled, _FlowAmplitude is the zone's, and the nebula noise clock is the zone's;
/// - the stardust particle buffer changes with the flow and freezes when the zone pauses;
/// - CloudShader pass 0 carries the FLOW_* keywords.
/// The scene is opened before any Aetheria type is touched: run this method alone in its own Unity session, never
/// after FlowFieldParityVerify (which would initialise VolumeSampling outside a MonoBehaviour constructor).
/// Dispatches compute and renders, so it needs a graphics device: run WITHOUT -nographics.
/// Unity -batchmode -projectPath &lt;repo&gt; -executeMethod Aetheria.EditorTools.FlowFieldSceneVerify.Run -logFile &lt;path&gt;
/// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
/// </summary>
public static class FlowFieldSceneVerify
{
const string Tag = "[FlowFieldSceneVerify]";
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;
try
{
// Before anything else touches the project's types: the scene constructs its MonoBehaviours here.
EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Single);
Check();
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;
}
EditorApplication.Exit(_ok ? 0 : 1);
}
static void Check()
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")
?? throw new InvalidOperationException("No Settings.asset.");
var agm = UnityEngine.Object.FindObjectsByType<ActionGameManager>(FindObjectsInactive.Include, FindObjectsSortMode.None).FirstOrDefault();
var volume = UnityEngine.Object.FindObjectsByType<VolumeSampling>(FindObjectsInactive.Include, FindObjectsSortMode.None).FirstOrDefault();
var dust = UnityEngine.Object.FindObjectsByType<Stardust>(FindObjectsInactive.Include, FindObjectsSortMode.None).FirstOrDefault();
if (agm == null || volume == null || dust == null)
{
Fail("scene-objects", $"ARPG.unity has ActionGameManager={agm != null}, VolumeSampling={volume != null}, Stardust={dust != null}");
return;
}
// Every flow term live, so FLOW_SLOPE is on; and the same flow with every amplitude zero.
var on = new FlowSettings
{
GlobalScale = 512f, GlobalAmplitude = 15f, GlobalScrollSpeed = .025f, Period = 8f, SlopeAmplitude = 3f, SwirlAmplitude = 5f
};
var off = new FlowSettings { GlobalScale = on.GlobalScale, GlobalScrollSpeed = on.GlobalScrollSpeed, Period = on.Period };
var work = Path.Combine(Path.GetTempPath(), "flow-scene-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(work);
try
{
var zone = FlowFieldParityVerify.BuildZone(settings, work, on, 86400.0 * 3 + 12.3);
typeof(ActionGameManager).GetProperty("Instance").GetSetMethod(true).Invoke(null, new object[] { agm });
typeof(ActionGameManager).GetProperty("Zone").GetSetMethod(true).Invoke(agm, new object[] { zone });
if (agm.Settings == null) agm.Settings = settings;
Call(volume, "Start");
Call(volume, "Update");
if (!Shader.IsKeywordEnabled("FLOW_GLOBAL")) Fail("scene-keywords", "FLOW_GLOBAL is not enabled after VolumeSampling.Update");
if (!Shader.IsKeywordEnabled("FLOW_SLOPE")) Fail("scene-keywords", "FLOW_SLOPE is not enabled after VolumeSampling.Update");
if (Shader.GetGlobalFloat("_FlowAmplitude") != on.GlobalAmplitude)
Fail("scene-amplitude", $"_FlowAmplitude is {Shader.GetGlobalFloat("_FlowAmplitude")}, the zone's flow has {on.GlobalAmplitude}");
var scroll = Shader.GetGlobalVector("_FlowScroll");
if (scroll.x != zone.FlowScroll.x || scroll.y != zone.FlowScroll.y)
Fail("scene-scroll", $"_FlowScroll ({scroll.x}, {scroll.y}) is not the zone's ({zone.FlowScroll.x}, {zone.FlowScroll.y})");
var noiseTime = Shader.GetGlobalFloat("_NebulaNoiseTime");
if (noiseTime != (float) zone.ExactTime)
Fail("scene-noise-clock", $"_NebulaNoiseTime is {noiseTime}, the zone's time is {(float) zone.ExactTime}");
CheckCloudKeywords();
// Stardust follows the flow through its own dispatch: the particle buffer read back each frame.
Call(dust, "Start");
var bufferField = typeof(Stardust).GetField("_particlesBuffer", BindingFlags.NonPublic | BindingFlags.Instance);
float[] Frame()
{
Call(dust, "Update");
var buffer = (ComputeBuffer) bufferField.GetValue(dust);
var data = new float[buffer.count * 7];
buffer.GetData(data);
return data;
}
var flowing = Frame();
VolumeSampling.LowerFlow(off, VolumeSampling.FlowClock);
var still = Frame();
if (Differing(flowing, still) == 0) Fail("scene-stardust", "the particle buffer is the same with the zone's flow and with no flow");
// A paused zone freezes the particles and the nebula noise clock; a running one moves both.
Call(volume, "Update");
zone.Update(0f);
Call(volume, "Update");
if (Differing(flowing, Frame()) != 0) Fail("scene-stardust-pause", "the particle buffer moved while the zone was paused");
if (Shader.GetGlobalFloat("_NebulaNoiseTime") != noiseTime) Fail("scene-noise-clock", "_NebulaNoiseTime moved while the zone was paused");
zone.Update(.05f);
Call(volume, "Update");
if (Differing(flowing, Frame()) == 0) Fail("scene-stardust-run", "the particle buffer did not move while the zone ran");
if (Shader.GetGlobalFloat("_NebulaNoiseTime") == noiseTime) Fail("scene-noise-clock", "_NebulaNoiseTime did not move while the zone ran");
CheckStardustUncullable(dust, bufferField);
CheckStardustUnderFog(dust, bufferField);
}
finally
{
try { Directory.Delete(work, true); } catch { }
}
}
// The event Stardust itself attaches its draw at, read from the class so the verifier cannot agree with a copy of it.
static CameraEvent DrawEvent() =>
(CameraEvent) typeof(Stardust).GetField("DrawEvent", BindingFlags.NonPublic | BindingFlags.Static).GetRawConstantValue();
// The dust is a command buffer on the target camera, which no culling sees: with every particle 10000 below the
// camera (far past any box a hand could inflate round it) a render still shows dust, and shows none when the
// same particles have no size.
static void CheckStardustUncullable(Stardust dust, FieldInfo bufferField)
{
var camera = dust.TargetCamera;
if (camera == null) { Fail("scene-stardust-uncullable", "Stardust has no TargetCamera"); return; }
var drawEvent = DrawEvent();
var attached = camera.GetCommandBuffers(drawEvent).Count(buffer => buffer.name == "Stardust");
if (attached != 1) Fail("scene-stardust-uncullable", $"{attached} Stardust command buffers at {drawEvent} on the target camera, expected 1");
foreach (var other in Enum.GetValues(typeof(CameraEvent)).Cast<CameraEvent>().Where(other => other != drawEvent))
{
var stray = camera.GetCommandBuffers(other).Count(buffer => buffer.name == "Stardust");
if (stray != 0) Fail("scene-stardust-uncullable", $"{stray} Stardust command buffers at {other}, expected 0");
}
var particles = (ComputeBuffer) bufferField.GetValue(dust);
float[] Layout(float size, Vector3 centre, float below) => DustLayout(particles.count, size, centre, below);
var transform = camera.transform;
var (position, rotation) = (transform.position, transform.rotation);
var (far, flags, background, mask, target) = (camera.farClipPlane, camera.clearFlags, camera.backgroundColor, camera.cullingMask, camera.targetTexture);
var texture = RenderTexture.GetTemporary(64, 64, 24, RenderTextureFormat.ARGB32);
var readback = new Texture2D(64, 64, TextureFormat.RGBA32, false);
var previous = RenderTexture.active;
// Image effects on the camera would write their own frame over the dust; only the dust draw is under test.
var effects = camera.GetComponents<MonoBehaviour>().Where(behaviour => behaviour.enabled).ToArray();
Color32[] Render(float size, Vector3 centre, float below)
{
Call(dust, "Update"); // on a bounded draw this queues the draw for the camera; here it dispatches the compute
particles.SetData(Layout(size, centre, below));
camera.Render();
RenderTexture.active = texture;
readback.ReadPixels(new Rect(0, 0, 64, 64), 0, 0);
readback.Apply();
return readback.GetPixels32();
}
try
{
foreach (var effect in effects) effect.enabled = false;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = Color.black;
camera.cullingMask = 1;
camera.targetTexture = texture;
// control: the camera where the scene put it, dust 100 below, inside any box a hand-set extent would give
transform.SetPositionAndRotation(position, Quaternion.Euler(90f, 0f, 0f));
camera.farClipPlane = 1000f;
var nearWith = Render(5f, position, 100f); var nearWithout = Render(0f, position, 100f);
var nearDiff = 0; for (var i = 0; i < nearWith.Length; i++) if (!nearWith[i].Equals(nearWithout[i])) nearDiff++;
Debug.Log($"{Tag} stardust near-control differing pixels={nearDiff}");
if (nearDiff == 0) Fail("scene-stardust-uncullable", "control: dust 100 below the camera drew no pixels");
transform.SetPositionAndRotation(new Vector3(0f, -40000f, 0f), Quaternion.Euler(90f, 0f, 0f));
camera.farClipPlane = 30000f;
var withDust = Render(400f, transform.position, 10000f); // the material scales a quad by distance^.33, so at this range it needs this size to cover pixels
var without = Render(0f, transform.position, 10000f);
var differing = 0;
for (var i = 0; i < withDust.Length; i++)
if (!withDust[i].Equals(without[i])) differing++;
if (differing == 0) Fail("scene-stardust-uncullable", "the dust 10000 below the camera drew no pixels");
}
finally
{
RenderTexture.active = previous;
foreach (var effect in effects) effect.enabled = true;
camera.targetTexture = target;
camera.cullingMask = mask;
camera.backgroundColor = background;
camera.clearFlags = flags;
camera.farClipPlane = far;
transform.SetPositionAndRotation(position, rotation);
RenderTexture.ReleaseTemporary(texture);
UnityEngine.Object.DestroyImmediate(readback);
}
}
// The nebula is an [ImageEffectOpaque] that fogs the whole ray to a depth the dust never writes (B49): dust drawn
// before it is attenuated by the fog, dust drawn after it sits on top at full strength. The dust's contribution to a
// pixel (render with dust minus render without) is read with the cloud renderer off and on, in an HDR target.
static void CheckStardustUnderFog(Stardust dust, FieldInfo bufferField)
{
const int Size = 128;
var camera = dust.TargetCamera;
var cloud = camera == null ? null : camera.GetComponent<VolumeCloudRenderer>();
if (cloud == null) { Fail("scene-stardust-under-fog", "the target camera has no VolumeCloudRenderer"); return; }
var particles = (ComputeBuffer) bufferField.GetValue(dust);
var transform = camera.transform;
var (position, rotation) = (transform.position, transform.rotation);
var (far, flags, background, mask, target, hdr) = (camera.farClipPlane, camera.clearFlags, camera.backgroundColor, camera.cullingMask, camera.targetTexture, camera.allowHDR);
var texture = RenderTexture.GetTemporary(Size, Size, 24, RenderTextureFormat.ARGBHalf);
var readback = new Texture2D(Size, Size, TextureFormat.RGBAHalf, false);
var previous = RenderTexture.active;
var effects = camera.GetComponents<MonoBehaviour>().Where(behaviour => behaviour.enabled).ToArray();
var fields = new System.Collections.Generic.List<Texture2D>();
GameObject floor = null;
float[] Render(bool fog, float size)
{
cloud.enabled = fog;
for (var frame = 0; frame < 2; frame++) // the cloud's first frame forces ULTRA and fills its history; the second is the settled one
{
Call(dust, "Update");
particles.SetData(DustLayout(particles.count, size, position, 100f));
camera.Render();
}
RenderTexture.active = texture;
readback.ReadPixels(new Rect(0, 0, Size, Size), 0, 0);
readback.Apply();
return readback.GetPixels().Select(pixel => pixel.r * .2126f + pixel.g * .7152f + pixel.b * .0722f).ToArray();
}
try
{
foreach (var effect in effects) effect.enabled = false;
camera.allowHDR = true;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = Color.black;
camera.cullingMask = 1;
camera.targetTexture = texture;
camera.farClipPlane = 1000f;
transform.SetPositionAndRotation(position, Quaternion.Euler(90f, 0f, 0f));
// The fog only measures a partial density where depth is written: a pixel with no depth is fogged whole (CloudShader sets
// density 1 past the far plane). A floor under the dust gives the march a finite ray.
floor = GameObject.CreatePrimitive(PrimitiveType.Plane);
floor.transform.position = position + Vector3.down * 200f;
floor.transform.localScale = Vector3.one * 100f;
UnityEngine.Object.DestroyImmediate(floor.GetComponent<Collider>());
// The scene's own nebula fields are textures the game renders; here a uniform fill of white fog stands in for them.
foreach (var (name, format) in new[] { ("_NebulaSurfaceHeight", TextureFormat.RFloat), ("_NebulaPatchHeight", TextureFormat.RHalf), ("_NebulaPatch", TextureFormat.RHalf) })
Shader.SetGlobalTexture(name, Uniform(format, Color.black, fields));
Shader.SetGlobalTexture("_NebulaTint", Uniform(TextureFormat.RGBA32, Color.white, fields));
Shader.DisableKeyword("NOISE_SLOPE");
Shader.SetGlobalFloat("_NebulaFillDensity", .01f);
Shader.SetGlobalFloat("_NebulaFillDistance", 120f);
Shader.SetGlobalFloat("_NebulaFillExponent", 0f);
Shader.SetGlobalFloat("_NebulaFillOffset", 0f);
Shader.SetGlobalFloat("_NebulaPatchDensity", 0f);
Shader.SetGlobalFloat("_NebulaLuminance", .01f); // a fog dimmer than the dust: dust drawn over it would otherwise read as a negative contribution
Shader.SetGlobalFloat("_ExtinctionCoefficient", .25f); // with the fill density, about 40% of the light to the floor is fogged away
Shader.SetGlobalFloat("_SafetyDistance", -1e6f); // below every sample: only the uniform fill, none of the zone's noise
Shader.SetGlobalFloat("_TintLodExponent", 0f);
var clearBare = Render(false, 0f);
var clearDust = Render(false, 5f);
var fogBare = Render(true, 0f);
var fogDust = Render(true, 5f);
var fogShown = 0f;
var (clearSum, fogSum, drawn) = (0f, 0f, 0);
for (var i = 0; i < clearBare.Length; i++)
{
fogShown += Mathf.Abs(fogBare[i] - clearBare[i]) / clearBare.Length;
if (clearDust[i] == clearBare[i]) continue;
drawn++; clearSum += clearDust[i] - clearBare[i]; fogSum += fogDust[i] - fogBare[i];
}
var (clearMean, fogMean) = (drawn == 0 ? 0f : clearSum / drawn, drawn == 0 ? 0f : fogSum / drawn);
Debug.Log($"{Tag} stardust under fog: fog control mean |difference|={fogShown}, dust pixels={drawn}, dust contribution cloud off={clearMean}, cloud on={fogMean}, ratio={(clearMean == 0f ? 0f : fogMean / clearMean)}");
if (drawn == 0 || clearMean <= 0f) Fail("scene-stardust-under-fog", "control: the dust drew no pixels with the cloud renderer off");
else if (fogShown < 1e-3f) Fail("scene-stardust-under-fog", "control: the cloud renderer changed no pixels, so no fog is in view");
else if (fogMean > .9f * clearMean) Fail("scene-stardust-under-fog", "the dust is not attenuated by the fog in front of it: it was drawn after the nebula composites");
else if (fogMean < .1f * clearMean) Fail("scene-stardust-under-fog", "the dust is erased, not dimmed, by a partial fog");
}
finally
{
RenderTexture.active = previous;
foreach (var effect in effects) effect.enabled = true;
camera.allowHDR = hdr;
camera.targetTexture = target;
camera.cullingMask = mask;
camera.backgroundColor = background;
camera.clearFlags = flags;
camera.farClipPlane = far;
transform.SetPositionAndRotation(position, rotation);
RenderTexture.ReleaseTemporary(texture);
UnityEngine.Object.DestroyImmediate(readback);
foreach (var field in fields) UnityEngine.Object.DestroyImmediate(field);
if (floor != null) UnityEngine.Object.DestroyImmediate(floor);
}
}
static Texture2D Uniform(TextureFormat format, Color colour, System.Collections.Generic.List<Texture2D> owner)
{
var texture = new Texture2D(1, 1, format, false) { wrapMode = TextureWrapMode.Clamp };
texture.SetPixel(0, 0, colour);
texture.Apply();
owner.Add(texture);
return texture;
}
// A square patch of particles 4 apart, `below` under `centre`.
static float[] DustLayout(int count, float size, Vector3 centre, float below)
{
var side = Mathf.RoundToInt(Mathf.Sqrt(count));
var data = new float[count * 7];
for (var i = 0; i < count; i++)
{
data[i * 7 + 0] = centre.x + ((i % side) - side / 2f) * 4f;
data[i * 7 + 1] = centre.y - below;
data[i * 7 + 2] = centre.z + ((i / side) - side / 2f) * 4f;
data[i * 7 + 3] = data[i * 7 + 4] = data[i * 7 + 5] = 1f;
data[i * 7 + 6] = size;
}
return data;
}
// CloudShader's pass 0 compiles with the flow keywords (it must #include_with_pragmas the shared include).
static void CheckCloudKeywords()
{
var shader = AssetDatabase.LoadAssetAtPath<Shader>("Assets/Shaders/Raymarching/CloudShader.shader");
if (shader == null) { Fail("scene-cloud-keywords", "No CloudShader.shader."); return; }
var keywords = ShaderUtil.GetPassKeywords(shader, new PassIdentifier(0, 0), UnityEditor.Rendering.ShaderType.Fragment)
.Select(keyword => keyword.name).ToArray();
foreach (var name in new[] { "FLOW_GLOBAL", "FLOW_SLOPE" })
if (!keywords.Contains(name)) Fail("scene-cloud-keywords", $"CloudShader pass 0 does not carry {name}");
}
static int Differing(float[] a, float[] b)
{
var count = 0;
for (var i = 0; i < a.Length; i++)
if (Math.Abs(a[i] - b[i]) > 1e-4f) count++;
return count;
}
// The presenters' own Start and Update, as the game calls them; an exception is a failure that names the type.
static void Call(object target, string method)
{
var info = target.GetType().GetMethod(method, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (info == null) { Fail("scene-presenter-exception", $"{target.GetType().Name}.{method} not found"); return; }
try { info.Invoke(target, null); }
catch (TargetInvocationException e)
{
Fail("scene-presenter-exception", $"{target.GetType().Name}.{method} threw {e.InnerException?.GetType().Name}");
}
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 25fbd79fca1de49498c3bebf9f63243b

View file

@ -1,428 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using GameCult.Caching;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering;
using CultMath;
namespace Aetheria.EditorTools
{
/// <summary>
/// Batchmode check for campaign aetheria-gravity, cut grid-lowering: what the Gravity Field pass draws is the sim's
/// height. It builds a zone from Settings.asset's PlanetSettings with three bodies, runs the real lowering
/// (GravityLowering.Lower and CreateField), renders the field through an orthographic camera that culls layer 9 into
/// a float target, and compares every texel with -Zone.GetHeight at the texel centre's world position. It then checks
/// that the texture's gradient points where Zone.GetForce does, and that ARPG.unity and the body prefabs are wired
/// the way the cut leaves them (the gravity cameras cull layer 9 alone, nothing but the run-time field sits on it).
///
/// Renders, so it needs a graphics device: run WITHOUT -nographics (as FlowFieldParityVerify).
/// Unity -batchmode -projectPath &lt;repo&gt; -executeMethod Aetheria.EditorTools.GravityFieldParityVerify.Run -logFile &lt;path&gt;
/// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
/// </summary>
public static class GravityFieldParityVerify
{
const string Tag = "[GravityFieldParityVerify]";
const int Size = 256;
// Measured on this tree (the "max error" line this run prints): the largest |texel + GetHeight| over the windows of the three
// frames is 0.0103 u, in the moved-again frame's sun window (float32 on both sides, the GPU's log and the closed form's
// single-precision floor of 1.5e-5 per unit strength against the CPU's, the quad's interpolated world position). The
// tolerance is 2.9x that. A band whose terms were drawn from the wrong edge (reference, inner or outer) moves the
// drawn height by whole units.
const float Tolerance = 0.03f;
// The worst angle between the texture's central difference and GetForce (B28: 1.0 degrees median zone, 2.9 worst).
const float GradientDegrees = 3f;
// Below this force (u/s^2) the drawn height differs between neighbouring texels by less than float32 resolves.
const float FlatForce = 1e-3f;
static float _maxError, _maxAngle, _maxTau;
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(), "gravity-parity-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(work);
ComputeBuffer wells = null;
GameObject field = null;
try
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")
?? throw new InvalidOperationException("No Settings.asset.");
var material = AssetDatabase.LoadAssetAtPath<Material>("Assets/Materials/Brushes/Gravity Field.mat")
?? throw new InvalidOperationException("No Gravity Field.mat.");
var zone = BuildZone(settings, work, out var orbits, multiRoot: true);
GravityLowering.Lower(zone, 0f, ref wells);
field = GravityLowering.CreateField(material);
// Three frames through the one buffer, as ZoneRenderer.Update runs it: the bodies as built, then two moves (an
// orbit's Position is what Zone.GetHeight reads). A move keeps the tree's geometry, since a rail never leaves
// its parent's distance and the walk skips a subtree outside its reach: the sun is the root, the planet is
// its Distance from the sun, the planetoid its Distance from the planet. A lowering that mirrors an axis or
// stops refreshing the upload agrees with the sim in the first frame only where it is symmetric.
CheckFrame("built", zone, orbits);
orbits[1].Position = Around(orbits[0], orbits[1], .31f);
orbits[2].Position = Around(orbits[1], orbits[2], .77f);
orbits[5].Position = Around(orbits[1], orbits[5], .41f);
GravityLowering.Lower(zone, 0f, ref wells);
CheckFrame("moved", zone, orbits);
orbits[0].Position = new float2(210f, -95f);
orbits[1].Position = Around(orbits[0], orbits[1], .52f);
orbits[2].Position = Around(orbits[1], orbits[2], .05f);
orbits[5].Position = Around(orbits[1], orbits[5], .93f);
GravityLowering.Lower(zone, 0f, ref wells);
CheckFrame("moved-again", zone, orbits);
if (_maxTau <= 1f) Fail("series-branch", $"the largest tau over the drawn bounded terms is {_maxTau:G4}; no window draws the series branch");
Debug.Log($"{Tag} largest drawn tau {_maxTau:G4}");
Debug.Log($"{Tag} max error {_maxError:G4} u (tolerance {Tolerance:G4}); max gradient angle {_maxAngle:F2} degrees (limit {GradientDegrees:F0})");
CheckWiring(settings);
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;
if (field != null) UnityEngine.Object.DestroyImmediate(field);
wells?.Release();
try { Directory.Delete(work, true); } catch { }
}
EditorApplication.Exit(_ok ? 0 : 1);
}
static float Core(Zone zone, float mass) => zone.Settings.RocheLimit.Evaluate(mass);
// Where an orbit lies when it stands `turns` round its parent at its Distance.
static float2 Around(Orbit parent, Orbit child, float turns) => parent.Position + child.Data.Distance * OrbitData.Evaluate(turns);
static Vector2 At(Orbit orbit) => new Vector2(orbit.Position.x, orbit.Position.y);
// Orbits 0-2 are the sun, the planet and the planetoid, each window centred where the body is now; orbit 3 is the
// body of no strength, orbit 4 the second sun and orbit 5 the planet's moon inside its Roche limit (whose band has
// tau above 1: the series branch of the shader). The zone window holds all of them; the far window is centred on the
// zone node, so its band (nothing inside A, the zone's mass beyond Beta A) is drawn. A window draws a term when
// the term's disc reaches it; the largest tau over the drawn terms must exceed 1 or the series branch went unchecked.
static void CheckFrame(string frame, Zone zone, Orbit[] orbits)
{
CheckTexels(frame + "-planetoid", zone, At(orbits[2]), 4f * Core(zone, 29.2f));
CheckTexels(frame + "-planet", zone, At(orbits[1]), 4f * Core(zone, 273.9f));
CheckTexels(frame + "-sun", zone, At(orbits[0]), 4f * Core(zone, 14152f));
CheckTexels(frame + "-zone", zone, Vector2.zero, 1.05f * zone.Pack.Radius);
var centre = new Vector2(zone.Wells.Centre.x, zone.Wells.Centre.y);
var span = 0f;
foreach (var root in new[] { orbits[0], orbits[4] })
span = Math.Max(span, (At(root) - centre).magnitude + zone.Wells.Reach(zone.Orbits.First(o => o.Value == root).Key));
// The wells have their own windows: out here a texel is 40 u wide, so only the ring from 0.9 A outward, where the
// zone node's band rises and the zone becomes one body, is measured.
CheckTexels(frame + "-far", zone, centre, 1.6f * span, .9f * span);
CheckGradient(zone, orbits.Select(At).ToArray());
}
// A sun (14152 at (-130, 85)), its planet (273.9, placed at (610, -240) at the pack's time), the planet's planetoid
// (29.2, placed at (670, -270)) and a body of mass 0, whose well has no strength (a legal body; its curves give
// c = 0 and R = 0), at (1700, 300): a family, so the band terms are drawn (the planet's and the sun's cusps and
// fades), 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. The sun and the zero-mass body are fixed roots.
internal static Zone BuildZone(GameSettings settings, string work, out Orbit[] orbits, float radius = 4182f, bool multiRoot = false)
{
var cache = AetheriaStores.Open(Path.Combine(work, "catalog.cc"), Path.Combine(work, "run.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
cache.FlushAsync().Wait();
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
var positions = new List<Vector2> { new Vector2(-130f, 85f), new Vector2(610f, -240f), new Vector2(670f, -270f), new Vector2(1700f, 300f) };
var masses = new List<float> { 14152f, 273.9f, 29.2f, 0f };
var parents = new List<int> { -1, 0, 1, -1 };
if (multiRoot)
{
// A second sun (a second subzone, so the zone node exists) and a moon of the planet 0.6 of its Roche limit away.
positions.Add(new Vector2(-2400f, -1900f));
masses.Add(3000f);
parents.Add(-1);
positions.Add(positions[1] + new Vector2(-.6f * settings.PlanetSettings.RocheLimit.Evaluate(273.9f), 0f));
masses.Add(1f);
parents.Add(1);
}
var keys = new CultRecordKey[positions.Count];
var pack = new ZonePack { Radius = radius };
for (var i = 0; i < positions.Count; i++)
{
var data = new OrbitData { FixedPosition = new float2(positions[i].x, positions[i].y) };
if (parents[i] >= 0)
{
var offset = positions[i] - positions[parents[i]];
data.Parent = new CultRecordRef<OrbitData>(keys[parents[i]]);
data.Distance = offset.magnitude;
data.Phase = Mathf.Atan2(offset.y, offset.x) / (2f * Mathf.PI);
}
var orbit = keys[i] = cache.Upsert(data).Key;
var body = cache.Upsert(new PlanetData
{ Name = "Body " + i, Orbit = new CultRecordRef<OrbitData>(orbit), Mass = masses[i] }).Key;
pack.Orbits.Add(new CultRecordRef<OrbitData>(orbit));
pack.Planets.Add(new CultRecordRef<BodyData>(body));
}
var zone = new Zone(items, settings.PlanetSettings, pack, new GalaxyZone { Name = "Gravity Parity", Owner = null }, null);
orbits = keys.Select(k => zone.Orbits[k]).ToArray();
return zone;
}
// Renders the field through a downward orthographic camera that culls layer 9 only, centred on `centre` with
// half-size `half`, and returns the depth of every texel with the world xz of the texel's centre, which is asked
// of the camera rather than assumed.
static void Render(Vector2 centre, float half, out float[] depth, out Vector2[] world)
{
var rt = new RenderTexture(Size, Size, 0, RenderTextureFormat.RFloat) { filterMode = FilterMode.Point };
var cameraObject = new GameObject("Gravity Parity Camera");
var read = new Texture2D(Size, Size, TextureFormat.RFloat, false);
var previous = RenderTexture.active;
try
{
var camera = cameraObject.AddComponent<Camera>();
camera.orthographic = true;
camera.orthographicSize = half;
camera.aspect = 1f;
camera.nearClipPlane = .3f;
camera.farClipPlane = 1000f;
camera.cullingMask = 1 << GravityLowering.FieldLayer;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = Color.black;
camera.allowHDR = false;
camera.allowMSAA = false;
camera.targetTexture = rt;
cameraObject.transform.SetPositionAndRotation(new Vector3(centre.x, 100f, centre.y), Quaternion.Euler(90f, 0f, 0f));
camera.Render();
RenderTexture.active = rt;
read.ReadPixels(new Rect(0, 0, Size, Size), 0, 0);
read.Apply();
depth = read.GetPixelData<float>(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);
}
}
// The largest tau = (a^2 + eps^2) / (b^2 - a^2) over the bounded terms whose disc reaches the window's square.
static void NoteTau(Zone zone, Vector2 centre, float half)
{
foreach (var term in zone.Wells.Terms)
{
if (float.IsPositiveInfinity(term.Outer)) continue;
var at = term.Node.IsSet() ? zone.Orbits[term.Node].Position : zone.Wells.Centre;
if (Math.Abs(at.x - centre.x) > half + term.Outer || Math.Abs(at.y - centre.y) > half + term.Outer) continue;
_maxTau = Math.Max(_maxTau, (term.Inner * term.Inner + term.Softening * term.Softening) / (term.Outer * term.Outer - term.Inner * term.Inner));
}
}
static void CheckTexels(string window, Zone zone, Vector2 centre, float half, float ringFrom = 0f)
{
Render(centre, half, out var depth, out var world);
NoteTau(zone, centre, half);
var worst = 0f;
var live = 0;
var measured = 0;
for (var i = 0; i < depth.Length; i++)
{
if ((world[i] - centre).magnitude < ringFrom) continue;
measured++;
var expected = -zone.GetHeight(new float2(world[i].x, world[i].y));
if (float.IsNaN(depth[i])) { Fail($"{window}-texels", $"texel {i}: NaN"); return; }
var error = Math.Abs(depth[i] - expected);
worst = Math.Max(worst, error);
if (Math.Abs(expected) > 1f) live++;
if (error > Tolerance)
{
Fail($"{window}-texels", $"texel {i} at ({world[i].x:F2}, {world[i].y:F2}): expected {expected:G6}, drawn {depth[i]:G6}, error {error:G4} > {Tolerance:G4}");
return;
}
}
_maxError = Math.Max(_maxError, worst);
// A window that draws nothing agrees with a zone that is flat: the check must be live.
if (live < measured / 2) Fail($"{window}-texels", $"only {live} of {measured} texels are deeper than 1 u; the check is vacuous");
Debug.Log($"{Tag} {window}: {depth.Length} texels, max error {worst:G4} u");
}
// The texture's central difference (over two 4 u texels, mid-zone) points where GetForce does: depth = -h, so its
// gradient is the force over the exaggeration. This is what the slope readers (the shader's slopeFlow, the nebula
// surface normal, the drawn grid) take from the texture.
static void CheckGradient(Zone zone, Vector2[] bodies)
{
Render(new Vector2(1500f, 0f), 512f, out var depth, out var world);
var compared = 0;
for (var y = 1; y < Size - 1; y++)
for (var x = 1; x < Size - 1; x++)
{
var here = world[y * Size + x];
if (bodies.Any(b => (b - here).magnitude < 16f)) continue;
// The world step of each texel axis is read from the camera, so a flipped or rotated target is still measured.
var au = world[y * Size + x + 1] - world[y * Size + x - 1];
var av = world[(y + 1) * Size + x] - world[(y - 1) * Size + x];
var du = depth[y * Size + x + 1] - depth[y * Size + x - 1];
var dv = depth[(y + 1) * Size + x] - depth[(y - 1) * Size + x];
var det = au.x * av.y - au.y * av.x;
var g = new Vector2((du * av.y - dv * au.y) / det, (dv * au.x - du * av.x) / det);
var f = zone.GetForce(new float2(here.x, here.y));
if (CultMath.math.length(f) < FlatForce) continue; // between the subzones the ground is flat: no direction to compare
var angle = Vector2.Angle(g, new Vector2(f.x, f.y));
if (float.IsNaN(angle) || angle > GradientDegrees)
{
Fail("gradient", $"at ({here.x:F1}, {here.y:F1}) the drawn slope points {angle:F2} degrees from the force (limit {GradientDegrees:F0})");
return;
}
_maxAngle = Math.Max(_maxAngle, angle);
compared++;
}
if (compared < Size * Size / 2) Fail("gradient", $"only {compared} texels were compared");
}
// Main Menu.unity has no ZoneRenderer, so no Gravity Field and no lowered potential: its gravity cameras draw the
// menu's own brush objects (Boundary on layer 9, the Waves on layer 10) and are presentation by spec (follow-up
// menu-grid-brushes), not a reader of the potential. This pins that: a ZoneRenderer in the menu would make its
// cameras readers of the field, and they would have to cull layer 9 alone like ARPG's.
static void CheckTestSceneWiring()
{
EditorSceneManager.OpenScene("Assets/Scenes/TestScene.unity", OpenSceneMode.Single);
try
{
if (CheckPatchCameras("TestScene.unity") < 1) Fail("wiring", "no Fog Patch Displacement camera in TestScene.unity");
}
finally
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
}
static void CheckMenuWiring()
{
var scene = EditorSceneManager.OpenScene("Assets/Scenes/Main Menu.unity", OpenSceneMode.Single);
try
{
if (UnityEngine.Object.FindFirstObjectByType<ZoneRenderer>(FindObjectsInactive.Include) != null)
Fail("wiring", "Main Menu.unity has a ZoneRenderer: its gravity cameras now read the Gravity Field and must cull layer 9 alone");
var seen = 0;
foreach (var camera in UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Include, FindObjectsSortMode.None))
{
if (camera.targetTexture == null || camera.targetTexture.name != "Fog Surface Displacement") continue;
seen++;
const int menuBrushLayers = (1 << 9) | (1 << 10);
if (camera.cullingMask != menuBrushLayers)
Fail("wiring", $"Main Menu camera {camera.name} culls mask {camera.cullingMask}, not the menu brush layers 9 and 10");
}
if (seen != 1) Fail("wiring", $"{seen} Fog Surface Displacement cameras in Main Menu.unity, expected 1");
if (CheckPatchCameras("Main Menu.unity") < 1) Fail("wiring", "no Fog Patch Displacement camera in Main Menu.unity");
}
finally
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
}
// Every scene's Fog Patch Displacement camera: cloudDensity adds the R32 gravity height to the patch texture, so
// a patch camera that culls the gravity layer counts the field twice. Returns how many it found in the open scene.
static int CheckPatchCameras(string sceneName)
{
var found = 0;
foreach (var camera in UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Include, FindObjectsSortMode.None))
{
if (camera.targetTexture == null || camera.targetTexture.name != "Fog Patch Displacement") continue;
found++;
if ((camera.cullingMask & 1 << GravityLowering.FieldLayer) != 0)
Fail("wiring", $"camera {camera.name} (target Fog Patch Displacement) in {sceneName} culls layer {GravityLowering.FieldLayer}, the gravity layer");
}
return found;
}
static void CheckWiring(GameSettings settings)
{
var scene = EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Single);
try
{
var gravityTargets = new[] { "Fog Surface Displacement", "Slime Gravity", "MinimapGravity" };
var seen = 0;
foreach (var camera in UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Include, FindObjectsSortMode.None))
{
if (camera.targetTexture == null || !gravityTargets.Contains(camera.targetTexture.name)) continue;
seen++;
if (camera.cullingMask != 1 << GravityLowering.FieldLayer)
Fail("wiring", $"camera {camera.name} (target {camera.targetTexture.name}) culls mask {camera.cullingMask}, not layer {GravityLowering.FieldLayer} alone");
}
if (seen < gravityTargets.Length) Fail("wiring", $"only {seen} gravity cameras found in ARPG.unity");
// The patch texture is half-float: it holds the patch layers only, the nebula reads gravity from the R32 surface.
if (CheckPatchCameras("ARPG.unity") != 1) Fail("wiring", "ARPG.unity does not have exactly 1 Fog Patch Displacement camera");
// The map screen's gravity camera has no target until MapRenderer gives it one at run time.
var map = UnityEngine.Object.FindFirstObjectByType<MapRenderer>(FindObjectsInactive.Include);
if (map == null || map.GravityCamera == null) Fail("wiring", "no MapRenderer.GravityCamera in ARPG.unity");
else if (map.GravityCamera.cullingMask != 1 << GravityLowering.FieldLayer)
Fail("wiring", $"MapRenderer.GravityCamera culls mask {map.GravityCamera.cullingMask}, not layer {GravityLowering.FieldLayer} alone");
var renderer = UnityEngine.Object.FindFirstObjectByType<ZoneRenderer>(FindObjectsInactive.Include);
if (renderer == null) Fail("wiring", "no ZoneRenderer in ARPG.unity");
else if (renderer.GravityField == null || renderer.GravityField.shader.name != "Brushes/Gravity Field")
Fail("wiring", "ZoneRenderer.GravityField is not a Brushes/Gravity Field material");
foreach (var r in UnityEngine.Object.FindObjectsByType<Renderer>(FindObjectsInactive.Include, FindObjectsSortMode.None))
if (r.gameObject.layer == GravityLowering.FieldLayer)
Fail("wiring", $"renderer {r.name} in ARPG.unity sits on layer {GravityLowering.FieldLayer}");
}
finally
{
EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
}
CheckMenuWiring();
CheckTestSceneWiring();
foreach (var path in new[]
{
"Assets/Prefabs/RPG/Planets/Planet.prefab", "Assets/Prefabs/RPG/Planets/Gas Giant.prefab",
"Assets/Prefabs/RPG/Planets/Sun.prefab", "Assets/Prefabs/RPG/Wormhole 1.prefab"
})
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab == null) { Fail("wiring", $"no prefab at {path}"); continue; }
foreach (var r in prefab.GetComponentsInChildren<Renderer>(true))
if (r.gameObject.layer == GravityLowering.FieldLayer)
Fail("wiring", $"renderer {r.name} in {path} sits on layer {GravityLowering.FieldLayer}");
}
}
}
}

View file

@ -1,116 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using GameCult.Caching;
using UnityEditor;
using CultMath;
using UnityEngine;
using float4 = CultMath.float4;
using Debug = UnityEngine.Debug;
// The map's belt cost per frame, CPU milliseconds and managed allocation, for belts of 1000, 2250 (the generated
// maximum, Settings.asset) and 4000 asteroids: the base revision's AsteroidBeltUI.Update (MapBeltPerfBase, a verbatim copy)
// and MapPresenter's belt upload and draw, each as the median and interquartile range of 21 runs of 300 frames after
// a warm-up, in this one Unity job. CPU time only: GPU cost is not covered. Needs a GPU device.
//
// Unity -batchmode -projectPath <project> -executeMethod MapBeltPerf.Run -logFile <log>
public static class MapBeltPerf
{
private const int Warmup = 60, Frames = 300, RunCount = 21;
public static void Run()
{
var work = Path.Combine(Path.GetTempPath(), $"map-belt-perf-{Guid.NewGuid():N}");
try
{
Directory.CreateDirectory(work);
foreach (var count in new[] { 1000, 2250, 4000 }) Measure(work, count);
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void Measure(string work, int count)
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset");
var cache = AetheriaStores.Open(Path.Combine(work, $"c{count}.cc"), Path.Combine(work, $"r{count}.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
var sunOrbit = cache.Upsert(new OrbitData()).Key;
var sunKey = cache.Upsert(new PlanetData { Name = "Sun", Orbit = new CultRecordRef<OrbitData>(sunOrbit), Mass = 400f }).Key;
var beltOrbit = cache.Upsert(new OrbitData { Parent = new CultRecordRef<OrbitData>(sunOrbit), Distance = 900f, Phase = .42f }).Key;
var asteroids = Enumerable.Range(0, count).Select(i => new Asteroid
{ Distance = 120f + i * .01f, Phase = CultMath.math.frac(i * .618f), Size = CultMath.math.frac(i * .37f + .1f), RotationSpeed = .15f + i * 1e-4f }).ToArray();
var beltKey = cache.Upsert(new AsteroidBeltData { Orbit = new CultRecordRef<OrbitData>(beltOrbit), Asteroids = asteroids }).Key;
var pack = new ZonePack
{
Orbits = { new CultRecordRef<OrbitData>(sunOrbit), new CultRecordRef<OrbitData>(beltOrbit) },
Planets = { new CultRecordRef<BodyData>(sunKey), new CultRecordRef<BodyData>(beltKey) },
Radius = 5000f, Mass = 10000f, Time = 0
};
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
var zone = new Zone(items, new PlanetSettings
{
BodyRadius = new ExponentialCurve(), WellStrength = new ExponentialCurve { Multiplier = 1f, Exponent = 1f },
RocheLimit = new ExponentialCurve { Constant = 2f },
AsteroidSize = new ExponentialLerp { Minimum = 2f, Maximum = 9f, Exponent = 1.7f },
AsteroidHitpoints = new ExponentialLerp { Minimum = 10f, Maximum = 300f, Exponent = 2f },
AsteroidRespawnTime = new ExponentialLerp { Minimum = 5f, Maximum = 60f, Exponent = 1f }
}, pack, new GalaxyZone { Name = "Perf", Owner = null }, null);
var transforms = new float4[count];
var presenter = new MapPresenter(new Material[0], AssetDatabase.LoadAssetAtPath<Material>("Assets/Materials/UI/MapAsteroid.mat"), 4, 4, 3f);
presenter.RegisterBelt(beltKey, transforms);
var baseBelt = new MapBeltPerfBase(zone, zone.AsteroidBelts[beltKey], 4, 4, 3f);
Report("base", count, Runs(zone, beltKey, transforms, () => baseBelt.Update(transforms, 0f)));
Report("head", count, Runs(zone, beltKey, transforms, () => presenter.Draw(zone, 0)));
presenter.Clear();
}
// Each run is Frames frames after Warmup frames; the figure is one run's CPU ms per frame and managed bytes per frame.
private static (double ms, long bytes)[] Runs(Zone zone, CultRecordKey beltKey, float4[] transforms, Action frame)
{
var results = new (double, long)[RunCount];
var stopwatch = new Stopwatch();
var time = 0.0;
for (var run = -1; run < RunCount; run++)
{
stopwatch.Reset();
long allocated = 0;
for (var i = 0; i < Warmup + Frames; i++)
{
zone.EvaluateBelt(beltKey, transforms, time += 0.016);
var bytes = GC.GetAllocatedBytesForCurrentThread();
if (i >= Warmup) stopwatch.Start();
frame();
if (i >= Warmup) stopwatch.Stop();
if (i >= Warmup) allocated += GC.GetAllocatedBytesForCurrentThread() - bytes;
}
if (run >= 0) results[run] = (stopwatch.Elapsed.TotalMilliseconds / Frames, allocated / Frames);
}
return results;
}
private static double Quantile(double[] sorted, double q)
{
var at = q * (sorted.Length - 1);
var low = (int)Math.Floor(at);
var high = Math.Min(low + 1, sorted.Length - 1);
return sorted[low] + (sorted[high] - sorted[low]) * (at - low);
}
private static void Report(string revision, int count, (double ms, long bytes)[] runs)
{
var ms = runs.Select(r => r.ms).OrderBy(v => v).ToArray();
var bytes = runs.Select(r => (double)r.bytes).OrderBy(v => v).ToArray();
var units = Process.GetProcessesByName("Unity").Length;
Debug.Log($"[MapBeltPerf] revision={revision} asteroids={count} runs={runs.Length} frames_per_run={Frames} " +
$"cpu_ms_median={Quantile(ms, .5):F4} iqr={Quantile(ms, .25):F4}-{Quantile(ms, .75):F4} " +
$"gc_bytes_median={Quantile(bytes, .5):F0} unity_processes={units} cpus={Environment.ProcessorCount}");
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 2bf3d3dcd7e3dd940add2640db93e3fe

View file

@ -1,88 +0,0 @@
using GameCult.Caching;
using CultMath;
using UnityEngine;
using static CultMath.math;
using float2 = CultMath.float2;
using float4 = CultMath.float4;
using Random = UnityEngine.Random;
// The base revision's per-frame belt map path, copied from ZoneRenderer.cs at da6fe033 (class AsteroidBeltUI) so that
// MapBeltPerf can measure it beside MapPresenter in one job. Only the lead it read from ActionGameManager.Clock is
// fixed at zero. Not used by the game.
public class MapBeltPerfBase
{
private Vector3[] _vertices;
private Vector3[] _normals;
private Vector2[] _uvs;
private int[] _indices;
private CultRecordKey _orbitParent;
private Mesh _mesh;
private float _size;
private Zone _zone;
private AsteroidBelt _belt;
private float _scale;
public MapBeltPerfBase(Zone zone, AsteroidBelt belt, int spritesheetWidth, int spritesheetHeight, float scale)
{
_belt = belt;
_zone = zone;
var orbit = zone.Orbits[belt.Data.Orbit.Key];
_orbitParent = orbit.Data.Parent.Key;
_vertices = new Vector3[_belt.Data.Asteroids.Length * 4];
_normals = new Vector3[_belt.Data.Asteroids.Length * 4];
_uvs = new Vector2[_belt.Data.Asteroids.Length * 4];
_indices = new int[_belt.Data.Asteroids.Length * 6];
_scale = scale;
var maxDist = 0f;
var spriteSize = float2(1f / spritesheetWidth, 1f / spritesheetHeight);
// vertex order: bottom left, top left, top right, bottom right
for (var i = 0; i < belt.Data.Asteroids.Length; i++)
{
if (belt.Data.Asteroids[i].Distance > maxDist)
maxDist = belt.Data.Asteroids[i].Distance;
var spriteX = Random.Range(0, spritesheetWidth);
var spriteY = Random.Range(0, spritesheetHeight);
_uvs[i * 4] = new Vector2(spriteX * spriteSize.x, spriteY * spriteSize.y);
_uvs[i * 4 + 1] = new Vector2(spriteX * spriteSize.x, spriteY * spriteSize.y + spriteSize.y);
_uvs[i * 4 + 2] = new Vector2(spriteX * spriteSize.x + spriteSize.x, spriteY * spriteSize.y + spriteSize.y);
_uvs[i * 4 + 3] = new Vector2(spriteX * spriteSize.x + spriteSize.x, spriteY * spriteSize.y);
_indices[i * 6] = i * 4;
_indices[i * 6 + 1] = i * 4 + 1;
_indices[i * 6 + 2] = i * 4 + 3;
_indices[i * 6 + 3] = i * 4 + 3;
_indices[i * 6 + 4] = i * 4 + 1;
_indices[i * 6 + 5] = i * 4 + 2;
}
for (var i = 0; i < _normals.Length; i++) _normals[i] = -Vector3.forward;
_mesh = new Mesh();
_mesh.vertices = _vertices;
_mesh.uv = _uvs;
_mesh.triangles = _indices;
_mesh.normals = _normals;
_mesh.bounds = new Bounds(Vector3.zero, Vector3.one * maxDist);
_size = maxDist;
}
public void Update(float4[] transforms, float height)
{
var parentPosition = DrawAhead.Orbit(_zone, _orbitParent, 0f);
for (var i = 0; i < transforms.Length; i++)
{
var transform = transforms[i];
var rotation = Quaternion.Euler(90, transform.z, 0);
var position = new Vector3(transform.x, height, transform.y);
_vertices[i * 4] = rotation * new Vector3(-transform.w * _scale, -transform.w * _scale, 0) + position;
_vertices[i * 4 + 1] = rotation * new Vector3(-transform.w * _scale, transform.w * _scale, 0) + position;
_vertices[i * 4 + 2] = rotation * new Vector3(transform.w * _scale, transform.w * _scale, 0) + position;
_vertices[i * 4 + 3] = rotation * new Vector3(transform.w * _scale, -transform.w * _scale, 0) + position;
}
_mesh.bounds = new Bounds(new Vector3(parentPosition.x, 0, parentPosition.y), Vector3.one * (_size * 2));
_mesh.vertices = _vertices;
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: c3e6b631c94f2384c87cf1906907af9f

View file

@ -1,119 +0,0 @@
using System;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
// Proof that a planet icon and a ping tint reach the pixels of the real map cameras when the planet's well is deeper
// than 1000 below the camera. The ARPG scene's own "Map Overlays" and "Map Tint" cameras (position, orthographic size,
// clip planes and culling mask as authored) render into a render texture on a solid black background. For each, in
// order: a control render with nothing drawn; a render with the thing placed 1100 below the camera (a plain quad, and the
// PingEffect prefab), which must equal the control because the far plane clips it; then MapPresenter's draw of the sun
// (well deeper than 2000) or the PingEffect prefab placed where EntityInstance places it, which must differ from the
// control at its pixel. A capture that draws nothing fails the last check, and one that cannot tell drawn from clipped
// fails the middle one.
//
// Unity -batchmode -projectPath <project> -executeMethod MapPresenterCapture.Run -logFile <log>
//
// Needs a GPU device; do not pass -nographics or -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
public static class MapPresenterCapture
{
private const int Size = 64;
public static void Run()
{
var work = Path.Combine(Path.GetTempPath(), $"map-presenter-capture-{Guid.NewGuid():N}");
try
{
Directory.CreateDirectory(work);
Check(work);
Debug.Log("[MapPresenterCapture] ok");
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void Require(bool condition, string what)
{
if (!condition) throw new InvalidOperationException(what);
}
private static Camera SceneCamera(string name)
{
foreach (var camera in UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Include, FindObjectsSortMode.None))
if (camera.name == name) return camera;
throw new InvalidOperationException($"No camera named {name} in the scene.");
}
private static Color Render(Camera camera, RenderTexture target, Vector3 at)
{
camera.targetTexture = target;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = Color.black;
camera.Render();
var viewport = camera.WorldToViewportPoint(new Vector3(at.x, 0, at.z));
var previous = RenderTexture.active;
RenderTexture.active = target;
var read = new Texture2D(Size, Size, TextureFormat.RGBA32, false);
read.ReadPixels(new Rect(0, 0, Size, Size), 0, 0);
RenderTexture.active = previous;
var pixel = read.GetPixel(Mathf.Clamp((int)(viewport.x * Size), 0, Size - 1), Mathf.Clamp((int)(viewport.y * Size), 0, Size - 1));
UnityEngine.Object.DestroyImmediate(read);
return pixel;
}
private static float Distance(Color a, Color b) => Mathf.Abs(a.r - b.r) + Mathf.Abs(a.g - b.g) + Mathf.Abs(a.b - b.b);
private static void Check(string work)
{
EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Single);
var f = MapPresenterSmoke.Build(work, flatIcons: true);
f.Map.SetMinimapScale(300f);
var target = new RenderTexture(Size, Size, 24, RenderTextureFormat.ARGB32);
var layer = LayerMask.NameToLayer("Minimap");
// Planet: the sun's icon through the map overlays camera.
var overlays = SceneCamera("Map Overlays");
Require((overlays.cullingMask & (1 << layer)) != 0, "The Map Overlays camera does not see the Minimap layer.");
var well = f.Zone.GetHeight(DrawAhead.Orbit(f.Zone, f.Zone.PlanetInstances[f.SunKey].BodyData.Orbit.Key, 0), 0);
Require(well < -1000f, $"The fixture sun's well is {well}, not deeper than 1000.");
Require(f.Map.TryDrawn(f.SunKey, out var sun), "The sun registered no icon.");
var sunAt = sun.GetPosition();
// Control: the pixel with nothing queued. Then a quad 1100 below the camera, deeper than the far plane, which must
// leave the pixel as it was. Then the presenter's draw, which must change it. Draws queue for the cameras that
// render after them, so the order is empty, deep, drawn.
var empty = Render(overlays, target, sunAt);
var quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
var deepMesh = quad.GetComponent<MeshFilter>().sharedMesh;
var deepMaterial = new Material(Shader.Find("Unlit/Color")) { color = Color.white };
UnityEngine.Object.DestroyImmediate(quad);
Graphics.RenderMesh(new RenderParams(deepMaterial) { layer = layer }, deepMesh, 0,
Matrix4x4.TRS(new Vector3(sunAt.x, -1100f, sunAt.z), Quaternion.Euler(90, 0, 0), Vector3.one * 300f));
var deep = Render(overlays, target, sunAt);
Require(Distance(deep, empty) < .01f, $"A mesh 1100 below the camera drew: {deep} against {empty}.");
f.Map.Draw(f.Zone, 0);
var drawn = Render(overlays, target, sunAt);
Require(Distance(drawn, empty) > .3f, $"The sun's icon is not in the pixels: drawn {drawn}, empty {empty}.");
// Ping tint: the PingEffect prefab where EntityInstance places it, through the map tint camera.
var tint = SceneCamera("Map Tint");
var ping = (GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Content/Prefabs/PingEffect.prefab"));
ping.transform.localScale = Vector3.one * 300f;
var pingAt = new Vector3(sunAt.x, MapPresenter.PlaneY, sunAt.z);
ping.transform.position = pingAt;
ping.SetActive(false);
var untinted = Render(tint, target, pingAt);
ping.SetActive(true);
ping.transform.position = new Vector3(sunAt.x, -1100f, sunAt.z);
var deepPing = Render(tint, target, pingAt);
Require(Distance(deepPing, untinted) < .01f, $"A ping 1100 below the camera drew: {deepPing} against {untinted}.");
ping.transform.position = pingAt;
var tinted = Render(tint, target, pingAt);
Require(Distance(tinted, untinted) > .02f, $"The ping tint is not in the pixels: with {tinted}, without {untinted}.");
Debug.Log($"[MapPresenterCapture] well={well} sun drawn={drawn} empty={empty} deep={deep}; ping tinted={tinted} untinted={untinted} deep={deepPing}");
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: cfa012aaae63b5044bdfd1b6b44ca9da

View file

@ -1,233 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using GameCult.Caching;
using UnityEditor;
using UnityEngine;
using CultMath;
using CultMath.UnityBridge;
using static CultMath.math;
using float2 = CultMath.float2;
using float3 = CultMath.float3;
using float4 = CultMath.float4;
// Batch proof of MapPresenter's contract on a real Zone, without a scene: a sun with a very deep well, a belt, and ships
// registered through EntityInstance.SetEntity as the game does. Planet icons draw at the map plane inside both map
// cameras' clip range however deep the well, a rolled and pitched hull's icon turns by yaw alone, every kind registers one
// draw and leaves no icon child behind, a belt is one instanced draw of every asteroid, and an entity's fade reaches its
// icon the frame it is set. Not reached: prefab, mod ship, station and turret instances (they register through the same
// call), and the rendered pixels (a GPU capture).
//
// Unity -batchmode -projectPath <project> -executeMethod MapPresenterSmoke.Run -logFile <log>
//
// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
public static class MapPresenterSmoke
{
private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
private const int Asteroids = 24;
public static void Run()
{
var work = Path.Combine(Path.GetTempPath(), $"map-presenter-smoke-{Guid.NewGuid():N}");
try
{
Directory.CreateDirectory(work);
Check(work);
Debug.Log("[MapPresenterSmoke] ok");
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void Require(bool condition, string what)
{
if (!condition) throw new InvalidOperationException(what);
}
private static void Awaken(Component component) => component.GetType().GetMethod("Awake", Any)?.Invoke(component, null);
// flat: authored lying on the map plane (facing up), as a real icon is; the smoke's own icons are authored upright
// so that their rotation is exactly the yaw.
private static MeshRenderer NewIcon(Transform parent, bool flat)
{
var icon = GameObject.CreatePrimitive(PrimitiveType.Quad);
icon.layer = LayerMask.NameToLayer("Minimap");
icon.transform.SetParent(parent, false);
if (flat) icon.transform.localRotation = Quaternion.Euler(90, 0, 0);
var renderer = icon.GetComponent<MeshRenderer>();
renderer.sharedMaterial = new Material(Shader.Find("Unlit/Color")) { color = new Color(1, 1, 1, .8f) };
return renderer;
}
// What Build leaves registered with a presenter, for the checks here and for MapPresenterCapture.
internal sealed class Fixture
{
public Zone Zone;
public MapPresenter Map;
public Ship Ship, Other;
public EntityInstance ShipInstance, OtherInstance;
public CultRecordKey SunKey, BeltKey;
public float4[] Belt;
public Transform Roots;
}
private static void Check(string work)
{
var f = Build(work, flatIcons: false);
Checks(f);
}
internal static Fixture Build(string work, bool flatIcons)
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")
?? throw new InvalidOperationException("No Settings.asset.");
var planetSettings = new PlanetSettings
{
BodyRadius = new ExponentialCurve(),
WellStrength = new ExponentialCurve { Multiplier = 1f, Exponent = 1f },
RocheLimit = new ExponentialCurve { Constant = 2f },
AsteroidSize = new ExponentialLerp { Minimum = 2f, Maximum = 9f, Exponent = 1.7f },
AsteroidHitpoints = new ExponentialLerp { Minimum = 10f, Maximum = 300f, Exponent = 2f },
AsteroidRespawnTime = new ExponentialLerp { Minimum = 5f, Maximum = 60f, Exponent = 1f }
};
var cache = AetheriaStores.Open(Path.Combine(work, "c.cc"), Path.Combine(work, "r.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
var shape = new Shape(5, 4);
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
cache.Upsert(new HullData { Name = "Smoke Hull", HullType = HullType.Ship, Shape = shape, Durability = 1000, Mass = 1000, Armor = 0 });
var sunOrbit = cache.Upsert(new OrbitData()).Key;
var sunKey = cache.Upsert(new PlanetData { Name = "Sun", Orbit = new CultRecordRef<OrbitData>(sunOrbit), Mass = 1e6f }).Key;
var rootOrbit = cache.Upsert(new OrbitData { Parent = new CultRecordRef<OrbitData>(sunOrbit), Distance = 4000f, Phase = .17f }).Key;
var beltOrbit = cache.Upsert(new OrbitData { Parent = new CultRecordRef<OrbitData>(rootOrbit), Distance = 900f, Phase = .42f }).Key;
var asteroids = Enumerable.Range(0, Asteroids).Select(i => new Asteroid
{ Distance = 120f + i * 11.3f, Phase = frac(i * .618f), Size = frac(i * .37f + .1f), RotationSpeed = .15f + i * .037f }).ToArray();
var beltKey = cache.Upsert(new AsteroidBeltData { Orbit = new CultRecordRef<OrbitData>(beltOrbit), Asteroids = asteroids }).Key;
var pack = new ZonePack
{
Orbits = { new CultRecordRef<OrbitData>(sunOrbit), new CultRecordRef<OrbitData>(rootOrbit), new CultRecordRef<OrbitData>(beltOrbit) },
Planets = { new CultRecordRef<BodyData>(sunKey), new CultRecordRef<BodyData>(beltKey) },
Radius = 5000f, Mass = 10000f, Time = 0
};
var ledger = new ProvenanceLedger();
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
var items = new ItemManager(1u, cache, ledger, settings.GameplaySettings, _ => { });
var zone = new Zone(items, planetSettings, pack, new GalaxyZone { Name = "Smoke", Owner = null }, null);
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hull"));
var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
var other = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 2 }, new EntitySettings());
ship.Position = float3(300, 50, 200);
other.Position = float3(-300, -90, 100);
zone.Admit(ship, piloted: false);
zone.Admit(other, piloted: false);
// A hull heading 40 degrees from +z, pitched up 89.9 and rolled 30: its forward is almost vertical, where the pose's
// eulerAngles.y is the gimbal decomposition (yaw and roll folded together), not the heading. The heading is the
// forward projected onto the map plane, 40. The fixture asserts the two differ, so a yaw read from eulerAngles
// cannot pass.
var pose = Quaternion.Euler(0, 40, 0) * Quaternion.Euler(-89.9f, 0, 30);
var flatForward = pose * Vector3.forward;
Require(Mathf.Abs(Mathf.DeltaAngle(Mathf.Atan2(flatForward.x, flatForward.z) * Mathf.Rad2Deg, 40f)) < .5f, "The fixture's forward does not head 40.");
Require(Mathf.Abs(Mathf.DeltaAngle(pose.eulerAngles.y, 40f)) > 10f, $"The fixture's eulerAngles.y {pose.eulerAngles.y} is aligned with its heading.");
ship.Direction = float2(Mathf.Sin(40 * Mathf.Deg2Rad), Mathf.Cos(40 * Mathf.Deg2Rad));
typeof(Ship).GetProperty("Rotation", Any).GetSetMethod(true).Invoke(ship, new object[] { new CultMath.quaternion(pose.x, pose.y, pose.z, pose.w) });
var map = new MapPresenter(new Material[0], AssetDatabase.LoadAssetAtPath<Material>("Assets/Materials/UI/MapAsteroid.mat"), 4, 4, 3f);
var roots = new GameObject("Smoke Roots").transform;
EntityInstance Bound(Entity entity)
{
var body = new GameObject(entity.Name);
body.transform.SetParent(roots);
var instance = body.AddComponent<EntityInstance>();
Awaken(instance);
instance.HullColliders = new HullCollider[0];
instance.RadiatorHardpoints = new RadiatorHardpoint[0];
instance.ThrusterHardpoints = new ThrusterHardpoint[0];
instance.WeaponHardpoints = new WeaponHardpoint[0];
instance.ArticulationPoints = new ArticulationPoint[0];
instance.MapIcon = NewIcon(body.transform, flatIcons);
var zoneRenderer = new GameObject("Smoke Zone Renderer").AddComponent<ZoneRenderer>();
zoneRenderer.Settings = settings;
zoneRenderer.MapGravityMaterials = new Material[0];
Awaken(zoneRenderer);
typeof(ZoneRenderer).GetProperty("Map").GetSetMethod(true).Invoke(zoneRenderer, new object[] { map });
instance.SetEntity(zoneRenderer, entity);
return instance;
}
var shipInstance = Bound(ship);
var otherInstance = Bound(other);
var sunRoot = new GameObject("Smoke Sun").transform;
sunRoot.SetParent(roots);
map.RegisterPlanet(sunKey, NewIcon(sunRoot, flatIcons), sunRoot);
var belt = new float4[Asteroids];
map.RegisterBelt(beltKey, belt);
zone.EvaluateBelt(beltKey, belt, DrawAhead.Time(zone, 0));
map.SetFade(ship, 1f);
map.SetFade(other, 1f);
map.SetMinimapScale(2f);
map.Draw(zone, 0);
return new Fixture { Zone = zone, Map = map, Ship = ship, Other = other, ShipInstance = shipInstance,
OtherInstance = otherInstance, SunKey = sunKey, BeltKey = beltKey, Belt = belt, Roots = roots };
}
private static void Checks(Fixture f)
{
var (zone, map, ship, other, sunKey, beltKey, belt, roots) = (f.Zone, f.Map, f.Ship, f.Other, f.SunKey, f.BeltKey, f.Belt, f.Roots);
// Deep planet: the sun's field is deeper than 2000 below the plane, and its icon sits on the map plane inside both
// map cameras' clip range (minimap at y 10, map screen at y 1; near 0.3, far 1000, looking down).
var well = zone.GetHeight(DrawAhead.Orbit(zone, zone.PlanetInstances[sunKey].BodyData.Orbit.Key, 0), 0);
Require(well < -2000f, $"The fixture sun's well is {well}, not below -2000.");
Require(map.TryDrawn(sunKey, out var sunIcon), "The sun registered no icon.");
var y = sunIcon.GetPosition().y;
Require(y < 1f - .3f && y > 10f - 1000f && Mathf.Abs(y - MapPresenter.PlaneY) < .5f, $"The sun's icon is drawn at y {y}, not on the map plane.");
// Axes: each icon sits at its sim position's planar x and z (a ship at (300, 200), the other at (-300, 100), the sun at
// its orbit), never swapped, and never at the 3D height (the ships ride at y 50 and -90, the sun's well is below -2000).
var shipAt = DrawAhead.Position(ship, 0);
Require(Mathf.Abs(shipAt.x - 300f) < 1e-3f && Mathf.Abs(shipAt.z - 200f) < 1e-3f, $"The ship's sim position is ({shipAt.x}, {shipAt.z}), not (300, 200).");
var sunAt = DrawAhead.Orbit(zone, zone.PlanetInstances[sunKey].BodyData.Orbit.Key, 0);
Require(map.TryDrawn(ship, out var shipIcon, out _), "The ship registered no icon.");
Require(Mathf.Abs(shipIcon.GetPosition().x - 300f) < 1e-3f && Mathf.Abs(shipIcon.GetPosition().z - 200f) < 1e-3f,
$"The ship's icon is at ({shipIcon.GetPosition().x}, {shipIcon.GetPosition().z}), not its sim position (300, 200).");
Require(map.TryDrawn(other, out var otherIcon, out _), "The second ship registered no icon.");
Require(Mathf.Abs(otherIcon.GetPosition().x + 300f) < 1e-3f && Mathf.Abs(otherIcon.GetPosition().z - 100f) < 1e-3f,
$"The second ship's icon is at ({otherIcon.GetPosition().x}, {otherIcon.GetPosition().z}), not (-300, 100).");
Require(Mathf.Abs(sunIcon.GetPosition().x - sunAt.x) < 1e-3f && Mathf.Abs(sunIcon.GetPosition().z - sunAt.y) < 1e-3f,
$"The sun's icon is at ({sunIcon.GetPosition().x}, {sunIcon.GetPosition().z}), not its orbit ({sunAt.x}, {sunAt.y}).");
Require(Mathf.Abs(otherIcon.GetPosition().y - (MapPresenter.PlaneY + MapPresenter.ShipOffset)) < 1e-4f, "The second ship's icon took the 3D height.");
// Tilted hull: the icon turns by the heading projected onto the map plane (40) times its authored rotation (identity here), with no roll or pitch.
var want = Quaternion.Euler(0, 40, 0);
Require(Quaternion.Angle(shipIcon.rotation, want) < .1f, $"The icon is rotated {shipIcon.rotation.eulerAngles}, not yaw 40 alone.");
Require(Vector3.Angle(shipIcon.rotation * Vector3.up, Vector3.up) < .1f, "The icon's up axis tilted with the hull.");
Require(Mathf.Abs(shipIcon.GetPosition().y - (MapPresenter.PlaneY + MapPresenter.ShipOffset)) < 1e-4f, "The ship's icon left the map plane.");
// Every kind registers once: three icons drew, and no icon child remains under any registered transform.
Require(map.IconCount == 3 && map.IconDraws == 3, $"{map.IconCount} icons, {map.IconDraws} drawn; expected 3 and 3.");
Require(roots.GetComponentsInChildren<MeshRenderer>(true).Length == 0, "An icon child remains under an entity or planet transform.");
// A belt is one instanced draw of every asteroid, and its buffer holds this frame's floats.
Require(map.BeltDraws == 1 && map.LastBeltInstances == belt.Length, $"{map.BeltDraws} belt draws of {map.LastBeltInstances} instances; expected 1 of {belt.Length}.");
var read = new float4[belt.Length];
map.ReadBelt(beltKey, read);
for (var i = 0; i < belt.Length; i++)
Require(read[i].x == belt[i].x && read[i].y == belt[i].y && read[i].z == belt[i].z && read[i].w == belt[i].w, $"Belt buffer differs from the frame's floats at {i}.");
// Belts draw on the minimap with the map screen closed.
map.OpenMapScreen(false);
map.Draw(zone, 0);
Require(map.BeltDraws == 1, "A belt did not draw on the minimap with the map screen closed.");
// Fade: half faded reaches the icon's colour alpha on the next draw; faded out draws nothing.
map.SetFade(ship, .5f);
map.Draw(zone, 0);
Require(map.TryDrawn(ship, out _, out var alpha) && Mathf.Abs(alpha - .4f) < 1e-3f, $"Icon alpha {alpha}, expected 0.8 * 0.5.");
map.SetFade(ship, 0f);
map.Draw(zone, 0);
Require(map.IconDraws == 2, $"{map.IconDraws} icons drew with one fully faded out; expected 2.");
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 58982a1c397cc2f4d9706fd103af6654

View file

@ -0,0 +1,93 @@
using System.Buffers.Text;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using MessagePack;
using CultMath;
using UnityEngine;
using UnityEditor;
using Random = CultMath.Random;
public class NameTools : EditorWindow
{
public int NameGeneratorMinLength = 5;
public int NameGeneratorMaxLength = 10;
public int NameGeneratorOrder = 4;
private int minWordLength = 4;
private TextAsset nameFile;
private MarkovNameGenerator _nameGenerator;
private bool _stripNumberTokens;
// Add menu named "My Window" to the Window menu
[MenuItem("Window/Aetheria/Name Tools")]
static void Init()
{
// Get existing open window or if none, make a new one:
NameTools window = (NameTools)EditorWindow.GetWindow(typeof(NameTools));
window.Show();
}
bool HasNonASCIIChars(string str)
{
return (System.Text.Encoding.UTF8.GetByteCount(str) != str.Length);
}
void OnGUI()
{
nameFile = (TextAsset) EditorGUILayout.ObjectField("Name File", nameFile, typeof(TextAsset), false);
minWordLength = EditorGUILayout.IntField("Minimum File Word Length", minWordLength);
NameGeneratorMinLength = EditorGUILayout.IntField("Generated Minimum Word Length", NameGeneratorMinLength);
NameGeneratorMaxLength = EditorGUILayout.IntField("Generated Maximum Word Length", NameGeneratorMaxLength);
NameGeneratorOrder = EditorGUILayout.IntField("Generator Order", NameGeneratorOrder);
_stripNumberTokens = GUILayout.Toggle(_stripNumberTokens, "Strip Number Tokens");
if (GUILayout.Button("Clean Name File"))
{
var lines = nameFile.text.Split('\n');
using StreamWriter outputFile = new StreamWriter(Path.Combine(Application.dataPath, nameFile.name + ".csv"));
var names = new HashSet<string>();
foreach (var line in lines)
{
var tokens = line.Split(',', ' ');
foreach (var t in tokens)
{
if (!HasNonASCIIChars(t))
{
var s = new string(t.Where(c => char.IsLetter(c) || c == '-' || c == '`' || c == '\'').ToArray()).Trim().Trim('`','-');
if(s.Length >= minWordLength && !names.Contains(s))
{
names.Add(s);
outputFile.WriteLine(s);
}
}
}
}
}
if (GUILayout.Button("Process Name File") && nameFile != null)
{
var names = new HashSet<string>();
var lines = nameFile.text.Split('\n');
foreach (var line in lines)
{
foreach(var word in line.ToUpperInvariant().Split(' ', ',', '.', '"'))
if (word.Length >= minWordLength && !names.Contains(word))
names.Add(word);
}
Debug.Log($"Found {lines.Length} lines, with {names.Count} unique names!");
var random = new Random(1337);
_nameGenerator = new MarkovNameGenerator(ref random, names, NameGeneratorOrder, NameGeneratorMinLength, NameGeneratorMaxLength);
}
if (_nameGenerator != null)
{
if (GUILayout.Button("Generate Name"))
{
Debug.Log(_nameGenerator.NextName);
}
}
}
}

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1ceb2bdab0804463b1998471d47e82fa
guid: 88b5f578fe63a904b997331630e61416
MonoImporter:
externalObjects: {}
serializedVersion: 2

View file

@ -1,112 +0,0 @@
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.SceneManagement;
using UnityEngine;
// The radius of each rock mesh ZoneRenderer draws (ruling rock-meshes-by-size): the farthest vertex from the mesh origin, which bounds the
// mesh's top under any rotation. DrawAhead.RockScale divides the gameplay radius by it, so the drawn rock is the gameplay rock. It is stored on
// InstancedMesh.Radius in ARPG.unity, because a build cannot read vertices of a mesh without Read/Write import.
//
// Unity -batchmode -nographics -projectPath <project> -executeMethod Aetheria.EditorTools.RockMeshRadius.Fill -logFile <log>
// Unity -batchmode -nographics -projectPath <project> -executeMethod Aetheria.EditorTools.RockMeshRadius.Verify -logFile <log>
//
// Fill measures every AsteroidMeshes entry and saves the scene; Verify fails (exit 1) when any stored radius is not the measured one.
// RockMeshRadiusBuildGate runs the same check before every player build and fails the build, so a mesh with no filled radius cannot ship;
// Gate runs that gate in batch mode.
// Unity -batchmode -nographics -projectPath <project> -executeMethod Aetheria.EditorTools.RockMeshRadius.Gate -logFile <log>
namespace Aetheria.EditorTools
{
public static class RockMeshRadius
{
private const string Scene = "Assets/Scenes/ARPG.unity";
private const float Tolerance = 1e-4f;
public static float Measure(Mesh mesh) => Mathf.Sqrt(mesh.vertices.Max(v => v.sqrMagnitude));
public static void Fill() => Batch(() =>
{
var scene = EditorSceneManager.OpenScene(Scene, OpenSceneMode.Single);
var (renderer, meshes, measured) = Load(scene);
for (var i = 0; i < meshes.Length; i++) meshes[i].Radius = measured[i];
EditorUtility.SetDirty(renderer);
EditorSceneManager.MarkSceneDirty(scene);
if (!EditorSceneManager.SaveScene(scene)) throw new InvalidOperationException("The scene did not save.");
return Summary("filled", measured);
});
public static void Verify() => Batch(() => Check(OpenSceneMode.Single));
// The build gate's own entry in batch mode: exit 1 exactly when a build would fail (RockMeshRadiusBuildGate).
public static void Gate() => Batch(() =>
{
try { new RockMeshRadiusBuildGate().OnPreprocessBuild(null); }
catch (BuildFailedException failure) { throw new InvalidOperationException(failure.Message); }
return "the build gate passes";
});
// Throws unless every AsteroidMeshes entry of ARPG.unity has a stored radius equal to its mesh's measured one (a zero radius never does).
public static string Check(OpenSceneMode mode)
{
var scene = EditorSceneManager.OpenScene(Scene, mode);
try
{
var (_, meshes, measured) = Load(scene);
var failures = meshes.Select((m, i) => (m, i)).Where(p => !(measured[p.i] > 0f) || Mathf.Abs(p.m.Radius - measured[p.i]) > Tolerance).ToArray();
if (failures.Length > 0)
throw new InvalidOperationException("InstancedMesh.Radius differs from the measured one, or is unset, on mesh (run RockMeshRadius.Fill): " + string.Join(", ",
failures.Select(f => f.m.Mesh.name)));
return Summary("ok", measured);
}
finally
{
if (mode == OpenSceneMode.Additive) EditorSceneManager.CloseScene(scene, true);
}
}
private static (ZoneRenderer renderer, InstancedMesh[] meshes, float[] measured) Load(UnityEngine.SceneManagement.Scene scene)
{
var renderer = UnityEngine.Object.FindObjectsByType<ZoneRenderer>(FindObjectsInactive.Include, FindObjectsSortMode.None)
.Single(r => r.gameObject.scene == scene);
var meshes = renderer.AsteroidMeshes;
if (meshes == null || meshes.Length == 0) throw new InvalidOperationException("ARPG.unity's ZoneRenderer has no AsteroidMeshes.");
var bad = meshes.Where(m => m.Mesh == null).ToArray();
if (bad.Length > 0) throw new InvalidOperationException($"{bad.Length} AsteroidMeshes entries have no mesh.");
return (renderer, meshes, meshes.Select(m => Measure(m.Mesh)).ToArray());
}
private static string Summary(string what, float[] measured)
{
var sorted = measured.OrderBy(r => r).ToArray();
return $"{what}: {sorted.Length} meshes, radius min {sorted[0]:F3} median {sorted[sorted.Length / 2]:F3} max {sorted[^1]:F3}";
}
private static void Batch(Func<string> run)
{
try
{
Debug.Log($"[RockMeshRadius] {run()}");
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
}
// A mesh in AsteroidMeshes with no filled radius must not ship: the build fails, naming the field.
public class RockMeshRadiusBuildGate : IPreprocessBuildWithReport
{
public int callbackOrder => 0;
public void OnPreprocessBuild(BuildReport report)
{
try { RockMeshRadius.Check(OpenSceneMode.Additive); }
catch (InvalidOperationException failure) { throw new BuildFailedException(failure.Message); }
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 9ea93f3d164825c4b99a0d07f24d5cf6

View file

@ -1,108 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UI;
// A render of the New Game screen as the game builds it: the real Main Menu scene's panel prototype, docked and filled
// by ScenarioPicker.Dock and ScenarioPicker.Show with every scenario group, on a plain dark background (the cloud
// backdrop is a play-mode renderer). Writes <project>/Temp/scenario-picker.png at 1920x1080, or the path after -pngOut (Unity empties Temp on exit).
//
// Unity -batchmode -projectPath <project> -executeMethod ScenarioPickerCapture.Run [-pngOut <file>] -logFile <log>
//
// Needs a GPU device; do not pass -nographics or -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
public static class ScenarioPickerCapture
{
private const int Width = 1920, Height = 1080;
public static void Run()
{
try
{
Capture();
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
// The menu's two panes built the way MainMenu.Start builds them, against the Main Menu scene.
internal static (MainMenu menu, PropertiesPanel list, PropertiesPanel detail) Build()
{
EditorSceneManager.OpenScene("Assets/Scenes/Main Menu.unity", OpenSceneMode.Single);
var menu = UnityEngine.Object.FindObjectsByType<MainMenu>(FindObjectsInactive.Include, FindObjectsSortMode.None)[0];
menu.PanelPrototype.gameObject.SetActive(false);
var list = menu.PanelPrototype.Instantiate<PropertiesPanel>();
var detail = menu.PanelPrototype.Instantiate<PropertiesPanel>();
// Edit mode runs no Awake, which is what activates each row a panel adds.
list.Awake();
detail.Awake();
ScenarioPicker.Dock(detail, list);
return (menu, list, detail);
}
internal static List<(string label, Scenario[] scenarios)> AllGroups() => new List<(string label, Scenario[] scenarios)>
{
("modes", Scenarios.Modes), ("development", Scenarios.Development), ("test arenas", Scenarios.Tests)
};
private static void Capture()
{
var (menu, list, detail) = Build();
menu.Dialog.gameObject.SetActive(false);
list.Title.text = MainMenu.TitleSubtitle("new game", "scenarios");
ScenarioPicker.Show(list, detail, AllGroups(), _ => { }, () => { }, menu.Dialog.Confirm);
list.RefreshValues();
detail.RefreshValues();
var canvas = menu.GetComponentInParent<Canvas>(true).rootCanvas;
var target = new RenderTexture(Width, Height, 24, RenderTextureFormat.ARGB32);
var cameraObject = new GameObject("ScenarioPickerCapture camera");
var camera = cameraObject.AddComponent<Camera>();
camera.orthographic = true;
camera.orthographicSize = 5;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = new Color(.04f, .05f, .08f);
camera.cullingMask = 1 << LayerMask.NameToLayer("UI");
camera.targetTexture = target;
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.worldCamera = camera;
canvas.planeDistance = 10;
canvas.gameObject.SetActive(true);
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) list.transform);
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) detail.transform);
foreach (var text in list.transform.GetComponentsInChildren<TMPro.TMP_Text>())
text.ForceMeshUpdate();
Canvas.ForceUpdateCanvases();
list.Content.GetComponentInParent<ScrollRect>().onValueChanged.Invoke(Vector2.zero);
camera.Render();
var previous = RenderTexture.active;
RenderTexture.active = target;
var read = new Texture2D(Width, Height, TextureFormat.RGBA32, false);
read.ReadPixels(new Rect(0, 0, Width, Height), 0, 0);
RenderTexture.active = previous;
var background = read.GetPixel(0, 0);
var differing = 0;
foreach (var pixel in read.GetPixels32())
if (Mathf.Abs(pixel.r / 255f - background.r) + Mathf.Abs(pixel.g / 255f - background.g) + Mathf.Abs(pixel.b / 255f - background.b) > .05f)
differing++;
if (differing < Width * Height / 100)
throw new InvalidOperationException($"The capture is uniform: only {differing} pixels differ from the background.");
var path = Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Temp", "scenario-picker.png"));
var args = Environment.GetCommandLineArgs();
var outIndex = Array.IndexOf(args, "-pngOut");
if (outIndex >= 0) path = Path.GetFullPath(args[outIndex + 1]);
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllBytes(path, read.EncodeToPNG());
Debug.Log($"[ScenarioPickerCapture] {path} ({differing} of {Width * Height} pixels differ from the background)");
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 2e79f280179150e4080250c45e07cd69

View file

@ -1,265 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEditor;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
// The picker's selection and keyboard rules, driven through the events the game delivers: Move and Cancel reach the
// focused Launch button through ExecuteEvents, as the Input System UI module sends them, and a click is Button.onClick.
//
// Unity -batchmode -projectPath <project> -executeMethod ScenarioPickerVerify.Run -logFile <log>
//
// Exits the Editor itself, 0 when every rule holds and 1 on the first that does not.
public static class ScenarioPickerVerify
{
private sealed class Fake : Scenario
{
private readonly uint _seed;
private readonly bool _ambient;
public Fake(string name, uint seed = 0, bool ambient = true) { Name = name; _seed = seed; _ambient = ambient; }
public override string Name { get; }
public override string Id => Name.ToLowerInvariant();
public override string Brief => $"brief of {Name}";
public override uint Seed => _seed;
public override bool Ambient => _ambient;
public override Galaxy Generate(GalaxyStage stage) => throw new NotSupportedException();
public override void Stage(ScenarioStage stage) => throw new NotSupportedException();
}
private sealed class Screen
{
public ScenarioPicker Picker;
public PropertiesPanel List, Detail;
public Button[] Rows;
public Button Back, Launch;
public readonly List<Scenario> Launched = new List<Scenario>();
public int Backs;
public Scenario[] A, B, C;
}
public static void Run()
{
try
{
SelectionFollowsClicksAndArrows();
ArrowsStopAtTheEnds();
LeftAndRightAreIgnored();
LaunchIsOnePathAndFocusClears();
CancelAndBackAreOnePath();
DetailPaneFollowsSelection();
OneGroupHasNoSectionLabel();
LaunchesOnceHoweverItIsReached();
ArrowingThroughTheRealListKeepsTheSelectionVisible();
Debug.Log("[ScenarioPickerVerify] ok");
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static void Require(bool condition, string what)
{
if (!condition) throw new InvalidOperationException(what);
}
private static Screen Build(bool oneGroup = false, bool real = false)
{
var (menu, list, detail) = ScenarioPickerCapture.Build();
// Build opens the scene afresh, which destroys the previous EventSystem.
var events = new GameObject("verify EventSystem").AddComponent<EventSystem>();
// Edit mode never calls OnEnable, which is what registers an EventSystem as current.
typeof(EventSystem).GetMethod("OnEnable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(events, null);
var screen = new Screen
{
List = list, Detail = detail,
A = new Scenario[] { new Fake("Alpha", 7), new Fake("Beta") },
B = new Scenario[] { new Fake("Gamma", ambient: false) },
C = new Scenario[] { new Fake("Delta"), new Fake("Echo") }
};
var groups = real ? ScenarioPickerCapture.AllGroups() : oneGroup
? new List<(string label, Scenario[] scenarios)> { ("modes", screen.A) }
: new List<(string label, Scenario[] scenarios)> { ("modes", screen.A), ("development", screen.B), ("test arenas", screen.C) };
// The menu builds a panel while it is inactive and shows it afterwards; the picker must find its scroll all the same.
if (real) list.gameObject.SetActive(false);
screen.Picker = ScenarioPicker.Show(list, detail, groups, screen.Launched.Add, () => screen.Backs++, menu.Dialog.Confirm);
var buttons = list.GetComponentsInChildren<PropertyButton>(false).Where(button => !button.transform.IsChildOf(detail.transform)).Select(button => button.Button).ToArray();
screen.Rows = buttons.Take(buttons.Length - 1).ToArray();
screen.Back = buttons.Last();
screen.Launch = detail.GetComponentsInChildren<Button>(false).Single();
if (real) { list.gameObject.SetActive(true); Layout(menu, list, detail); }
return screen;
}
// A live 1920x1080 layout, as the game has one a frame after Show; edit mode has no frames, so it is forced here.
private static void Layout(MainMenu menu, PropertiesPanel list, PropertiesPanel detail)
{
menu.Dialog.gameObject.SetActive(false);
var canvas = menu.GetComponentInParent<Canvas>(true).rootCanvas;
var camera = new GameObject("verify camera").AddComponent<Camera>();
camera.targetTexture = new RenderTexture(1920, 1080, 24);
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.worldCamera = camera;
canvas.planeDistance = 10;
canvas.gameObject.SetActive(true);
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) canvas.transform);
foreach (var text in list.GetComponentsInChildren<TMP_Text>()) text.ForceMeshUpdate();
Canvas.ForceUpdateCanvases();
Scroll(list).onValueChanged.Invoke(Vector2.zero);
}
private static ScrollRect Scroll(PropertiesPanel list) => list.Content.GetComponentInParent<ScrollRect>();
// Wholly inside the viewport, in world space, and not hidden by FitViewport's CanvasGroup.
private static bool Shown(Button row, ScrollRect scroll)
{
var view = new Vector3[4];
((RectTransform) scroll.viewport).GetWorldCorners(view);
var rect = new Vector3[4];
((RectTransform) row.transform).GetWorldCorners(rect);
var alpha = 1f;
for (var t = row.transform; t != null && t != scroll.content; t = t.parent)
if (t.TryGetComponent<CanvasGroup>(out var group)) alpha = Mathf.Min(alpha, group.alpha);
return rect[0].y >= view[0].y - .01f && rect[1].y <= view[1].y + .01f && alpha == 1;
}
private static void Move(Screen screen, MoveDirection direction) =>
ExecuteEvents.Execute(screen.Launch.gameObject, new AxisEventData(EventSystem.current) { moveDir = direction }, ExecuteEvents.moveHandler);
private static void Cancel(Screen screen) =>
ExecuteEvents.Execute(screen.Launch.gameObject, new BaseEventData(EventSystem.current), ExecuteEvents.cancelHandler);
private static string Selected(Screen screen) => screen.Picker.Selected.Name;
private static void SelectionFollowsClicksAndArrows()
{
var screen = Build();
Require(screen.Rows.Length == 5, $"Expected 5 scenario rows, found {screen.Rows.Length}.");
Require(Selected(screen) == "Alpha", "The first scenario is not selected on Show.");
Require(EventSystem.current.currentSelectedGameObject == screen.Launch.gameObject, "Launch is not focused on Show.");
Move(screen, MoveDirection.Down);
Require(Selected(screen) == "Beta", "Down did not select the next row.");
Move(screen, MoveDirection.Down);
Require(Selected(screen) == "Gamma", "Down did not cross into the next group.");
Move(screen, MoveDirection.Up);
Require(Selected(screen) == "Beta", "Up did not select the previous row.");
screen.Rows[3].onClick.Invoke();
Require(Selected(screen) == "Delta", "Clicking a name did not select it.");
Require(screen.Launched.Count == 0, "Clicking a name launched it.");
Require(EventSystem.current.currentSelectedGameObject == screen.Launch.gameObject, "A click on a name took focus off Launch.");
Require(screen.Rows.All(row => row.navigation.mode == Navigation.Mode.None) && screen.Launch.navigation.mode == Navigation.Mode.None
&& screen.Back.navigation.mode == Navigation.Mode.None, "A picker button still takes Unity navigation.");
var accent = screen.Rows[3].targetGraphic.color;
Require(screen.Rows.Where((row, i) => i != 3).All(row => row.targetGraphic.color != accent), "Only the selected row is drawn in the accent.");
Move(screen, MoveDirection.Up);
Require(screen.Rows[2].targetGraphic.color == accent && screen.Rows[3].targetGraphic.color != accent, "The accent did not follow the selection.");
}
private static void ArrowsStopAtTheEnds()
{
var screen = Build();
Move(screen, MoveDirection.Up);
Require(Selected(screen) == "Alpha", "Up wrapped off the first row.");
for (var i = 0; i < 10; i++) Move(screen, MoveDirection.Down);
Require(Selected(screen) == "Echo", "Down wrapped or stopped short of the last row.");
}
private static void LeftAndRightAreIgnored()
{
var screen = Build();
Move(screen, MoveDirection.Down);
Move(screen, MoveDirection.Left);
Move(screen, MoveDirection.Right);
Require(Selected(screen) == "Beta", "Left or Right moved the selection.");
}
private static void LaunchIsOnePathAndFocusClears()
{
var screen = Build();
Move(screen, MoveDirection.Down);
screen.Launch.onClick.Invoke();
Require(screen.Launched.Count == 1 && screen.Launched[0] == screen.A[1], "Launch did not launch the selected scenario once.");
Require(EventSystem.current.currentSelectedGameObject == null, "Focus survived Launch, so a second Enter could launch again.");
Require(screen.Backs == 0, "Launch went back.");
}
private static void CancelAndBackAreOnePath()
{
var screen = Build();
Cancel(screen);
Require(screen.Backs == 1 && screen.Launched.Count == 0, "Cancel did not go back exactly once.");
Require(EventSystem.current.currentSelectedGameObject == null, "Focus survived Cancel.");
screen = Build();
screen.Back.onClick.Invoke();
Require(screen.Backs == 1 && screen.Launched.Count == 0, "The Back button did not go back exactly once.");
Require(EventSystem.current.currentSelectedGameObject == null, "Focus survived Back.");
}
private static string DetailText(Screen screen)
{
screen.Detail.RefreshValues();
return string.Join("|", screen.Detail.GetComponentsInChildren<TextMeshProUGUI>(false).Select(text => text.text));
}
private static void DetailPaneFollowsSelection()
{
var screen = Build();
var first = DetailText(screen);
Require(first.Contains("brief of Alpha") && first.Contains("7") && first.Contains("on"), $"Detail does not describe Alpha: {first}");
Require(first.Contains("modes") && first.Contains("Alpha"), "Detail heading is not the group and name.");
Move(screen, MoveDirection.Down);
Move(screen, MoveDirection.Down);
var third = DetailText(screen);
Require(third.Contains("brief of Gamma") && third.Contains("new each launch") && third.Contains("off"), $"Detail does not describe Gamma: {third}");
Require(third.Contains("development") && !third.Contains("Alpha"), "Detail heading did not follow the selection.");
Require(screen.Detail.GetComponentsInChildren<Button>(false).Single() == screen.Launch, "Selecting rebuilt the Launch button.");
}
private static void OneGroupHasNoSectionLabel()
{
var many = Build();
var one = Build(oneGroup: true);
Require(many.List.Children == 5 + 3 + 2 + 1, $"Three groups should list 5 rows, 3 section labels, 2 spacers and Back, not {many.List.Children} children.");
Require(one.List.Children == 2 + 1, $"One group should list its 2 rows and Back, not {one.List.Children} children.");
}
private static void LaunchesOnceHoweverItIsReached()
{
var screen = Build();
screen.Launch.onClick.Invoke();
screen.Launch.onClick.Invoke();
ExecuteEvents.Execute(screen.Launch.gameObject, new BaseEventData(EventSystem.current), ExecuteEvents.submitHandler);
Require(screen.Launched.Count == 1, $"Launch ran {screen.Launched.Count} times after a click, a second click and Submit.");
Require(screen.Launch.navigation.mode == Navigation.Mode.None && Scroll(screen.List).verticalScrollbar.navigation.mode == Navigation.Mode.None,
"The list scrollbar or Launch takes Unity navigation.");
}
// The real scenario groups overflow the list's viewport; every row the arrows reach must end wholly visible.
private static void ArrowingThroughTheRealListKeepsTheSelectionVisible()
{
var screen = Build(real: true);
var scroll = Scroll(screen.List);
var content = (RectTransform) scroll.content;
var viewport = (RectTransform) scroll.viewport;
Require(content.rect.height > viewport.rect.height + 1, $"The real list ({content.rect.height}) does not overflow its viewport ({viewport.rect.height}); the scroll rules are unmeasured.");
Require(Shown(screen.Rows[0], scroll), "The first row is not shown on Show.");
for (var i = 1; i < screen.Rows.Length; i++)
{
Move(screen, MoveDirection.Down);
Canvas.ForceUpdateCanvases();
Require(Shown(screen.Rows[i], scroll), $"Down to row {i} ({Selected(screen)}) left it hidden or cut off.");
}
for (var i = screen.Rows.Length - 2; i >= 0; i--)
{
Move(screen, MoveDirection.Up);
Canvas.ForceUpdateCanvases();
Require(Shown(screen.Rows[i], scroll), $"Up to row {i} ({Selected(screen)}) left it hidden or cut off.");
}
}
}

Some files were not shown because too many files have changed in this diff Show more