Compare commits

..
Author SHA1 Message Date
Meta Vens
0c60997d7b Fauna lore r6 sweep: the pillar wall is a steepening ramp (ruling pillar-wall-steepening-ramp)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:33:24 +02:00
Meta Vens
b17447ceb9 Fauna lore r6 sweep: the pillar is a sun-wide wall (ruling pillar-is-a-wall); anchors on the bowl floor, waves off the face, meeting the wall
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:28:26 +02:00
Meta Vens
4a464c8558 Fauna lore r6 sweep: rulings after r6 (no flatspace pillars, rare pairs, ships feel the swell, Ink stays a theory); pillar as a Bundt-tin bowl
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:21:05 +02:00
Meta Vens
e930ab2937 Fauna lore r6: Ink pillars stand where suns would (ruling ink-pillars-are-suns), flatspace and blind-gate rulings applied
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:11:21 +02:00
Meta Vens
74c7e21221 Fauna lore r5: gates built against anchors, the slow way, blind gates, rare tears
Applies rulings gates-need-anchors and mostly-gates-few-tears. New note
Anchors and Gates; Wonderland, Flatspace, Adrasteia Space, Hyperspace,
The Glass and The Fold revised so footholds rest on where anchors hold;
review note Fauna Lore Review r5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:33:54 +02:00
Meta Vens
c9236bc684 Fauna lore r4: flatspace and Wonderland, four layers (Ink, Glass, Fold, hyperspace), beacon economy
Flatspace is Sol's bubble plus the vanilla galaxy, with r2's ecology renamed
Open Water as its wildlife and pirates as its scourge. Wonderland replaces
the flavours note: separate layers with their own maps, joined by links,
in the fae register. New layer notes for the Glass (Wavecrafters), the Fold
and hyperspace (beacons, keepers); the Ink revised as Adrasteia's layer.
Review r4 lists where the pass would change the rulings, Self's proposals,
r3 and canon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 03:54:12 +02:00
Meta Vens
c8d6ba7040 Fauna lore r3: pseudospace flavours, the Ink (Adrasteia space) and its bestiary
Pseudospace comes in flavours (ruling aetheria-fauna:ruling:adrasteia-space-flavour).
Pseudospace Flavours gives the grammar (light, heat, surface, flow, distance;
the stars set the temper), crossing tells, and four flavours: Open Water
(r2's ecology, Shallows to Deeps), the Ink (Adrasteia space), the Glass
(stiff Aether, nothing fades) and the Fold (non-Euclidean knots and seams).

Adrasteia Space develops the operator's sketch: a liquid surface under a
fog that eats light and so drinks heat, star-pillars breathing strange-matter
dust in waves whose pressure opens travelling pockets. Dust is stellar; the
animals concentrate it as a heat-eating organ, so the richest dust sits in
the deadliest animal: Soot, Rays, Squid, Cachalots. The monopoly is kept by
an unfindable, cold-only sea that needs negent to work.

The whale-bone strange-matter rumour is cut from Whales, Spaceborne Fauna and
Pseudospace Ecology (ruling strange-matter-not-biogenic). Fauna Lore Review r3
argues the changes to the sketch, r2 and canon; the one-or-many fork is
question aetheria-fauna❓ink-one-or-many.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 03:22:09 +02:00
Meta Vens
da593914be Fauna lore r2: an ecology designed by niche, twelve fauna, and a review note
The r1 pass took the operator's four example animals as the whole
ecosystem (ruling aetheria-fauna:ruling:fauna-bestiary-open). r2 designs
the ecology first: four energy budgets (starlight, wild negent heat-light,
gravity waves, mind-light), transport by the Aether current (fronts, shear,
slack, the downslope drain), a food web, how each level feature tells the
story, whale-fall succession, and recruitment from upstream.

Bestiary: Glim, Skein, Rime, Krill, Minnows, Whales, Sharks, Lanterns,
Void Spiders, Gleaners, Limpets, Moths. Her four kept and changed. The
review note argues where the sketches, canon and r1 should change;
the strange-matter fork is question aetheria-fauna❓strange-matter-source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:46:18 +02:00
Meta Vens
05658c593f Fauna lore: Sol is one flatspace zone, Aether pressing in at its Oort edge
Per ruling sol-flatspace-bubble: no fauna inside Sol; the first fauna a Sol pilot sees cling to the Oort edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:27:12 +02:00
Meta Vens
406397147a Fauna lore draft: pseudospace fauna hub and krill, minnow, shark, whale notes
Imagination lore pass for campaign aetheria-fauna, for operator review.
Origin per ruling fauna-origin-pseudospace; [new] marks invention touching canon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:25:47 +02:00
882 changed files with 19305 additions and 55670 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

@ -4818,3 +4818,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
ParticleSystem: {fileID: 5087162364086924483}
Radius: 25
Traction: 25
Distance: 75

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

@ -811,9 +811,6 @@ MonoBehaviour:
Camera: {fileID: 1153356615}
TestField: {fileID: 909026488}
Properties: {fileID: 401779935}
GrabExtendTime: 1.5
GrabEnvelopTime: 0.75
GrabPullTime: 1.5
--- !u!4 &699508827
Transform:
m_ObjectHideFlags: 0
@ -1119,6 +1116,9 @@ MonoBehaviour:
TendrilBaseDamping: 2
TendrilTipRadiusAnimationExponent: 2
TendrilFadeExponent: 2
GrabExtendTime: 1.5
GrabEnvelopTime: 0.75
GrabPullTime: 1.5
RefractionAxis: 1
--- !u!114 &909026489
MonoBehaviour:

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

@ -1,217 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using GameCult.Caching;
using UnityEditor;
using UnityEngine;
using CultMath;
using static CultMath.math;
// Batch proof of the capability presenters' wiring, without a scene: a zone ship is bound to an EntityInstance through the
// game's own SetEntity, and impacts and grabs are published on the ship's Capabilities exactly as the simulation does. A hull absorb spawns
// one hull effect under the instance and, for the player's ship at ScreenShake 1, raises one camera impulse; ScreenShake 0
// or another ship raises none; a shield absorb spawns no hull effect. The shield ripple itself (ShieldManager.ShowHit
// needs a shield material and a rig) is looked at in the editor, not here. Grabs: a Tractor Beam prefab under the instance
// is bound by the same SetEntity and presents a grab from events alone (emission at Power and the aim at the held point
// from Started to Completed or Cancelled, nothing otherwise, nothing for another entity's grab).
//
// Unity -batchmode -nographics -projectPath <project> -executeMethod CapabilityPresentationSmoke.Run -logFile <log>
//
// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
public static class CapabilityPresentationSmoke
{
private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
public static void Run()
{
var work = Path.Combine(Path.GetTempPath(), $"capability-presentation-smoke-{Guid.NewGuid():N}");
GameObject[] objects = null;
try
{
Directory.CreateDirectory(work);
objects = Check(work);
Clean(objects, work);
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
Clean(objects, work);
EditorApplication.Exit(1);
}
}
// Edit mode runs no Awake or Start; the smoke runs the ones the game's scene would have.
private static void Awaken(Component component) => component.GetType().GetMethod("Awake", Any)?.Invoke(component, null);
private static GameObject[] Check(string work)
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")?.GameplaySettings
?? throw new InvalidOperationException("No Settings.asset.");
var shape = new Shape(5, 4);
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
var hullData = new HullData
{
Name = "Smoke Hull", HullType = HullType.Ship, Shape = shape, Durability = 1000, Mass = 1000, Armor = 0
};
var cache = AetheriaStores.Open(Path.Combine(work, "smoke.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
cache.Upsert(hullData);
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 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());
var other = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 2 }, new EntitySettings());
zone.Admit(ship, piloted: false);
zone.Admit(other, piloted: false);
// The scene's side: the tuning object with a pooled effect, and the game manager that names the player's ship.
var feedbackObject = new GameObject("Smoke Impact Feedback");
var feedback = feedbackObject.AddComponent<ImpactFeedback>();
Awaken(feedback);
var effectObject = new GameObject("Smoke Hull Effect");
feedback.HullEffect = effectObject.AddComponent<Prototype>();
var managerObject = new GameObject("Smoke Game Manager");
var manager = managerObject.AddComponent<ActionGameManager>();
typeof(ActionGameManager).GetProperty("Instance", Any).SetValue(null, manager);
manager.CurrentEntity = ship;
var beamPrefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Content/Prefabs/Tractor Beam.prefab")
?? 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);
var bound = go.AddComponent<EntityInstance>();
Awaken(bound);
bound.HullColliders = new HullCollider[0];
bound.RadiatorHardpoints = new RadiatorHardpoint[0];
bound.ThrusterHardpoints = new ThrusterHardpoint[0];
bound.WeaponHardpoints = new WeaponHardpoint[0];
bound.ArticulationPoints = new ArticulationPoint[0];
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);
return (go, bound, tractor);
}
var (instanceObject, instance, beam) = Bound("Smoke Entity Instance", ship);
var (otherObject, _, otherBeam) = Bound("Smoke Other Instance", other);
int Effects() => instance.GetComponentsInChildren<Prototype>(true).Count(p => p != null && !p.isOriginalPrototype);
void Expect(string what, int effects, int kicks)
{
if (Effects() != effects || feedback.KicksRaised != kicks)
throw new InvalidOperationException($"{what}: {Effects()} hull effects (want {effects}), {feedback.KicksRaised} impulses (want {kicks}).");
}
AbsorbEvent Hit(AbsorbLayer layer) => new AbsorbEvent(float3(1f, 0f, 2f), float3(0f, 0f, 1f), 100f, DamageType.Kinetic, layer);
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 1f;
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
Expect("player's hull hit", 1, 1);
var effect = instance.GetComponentsInChildren<Prototype>(true).Single(p => !p.isOriginalPrototype);
var drawn = DrawAhead.Carried(ship, float3(1f, 0f, 2f), ActionGameManager.Clock.Lead);
if (abs(effect.transform.position.x - drawn.x) > 1e-3f || abs(effect.transform.position.z - drawn.z) > 1e-3f)
throw new InvalidOperationException("The hull effect is not at the drawn impact point.");
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 0f;
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
Expect("hull hit at ScreenShake 0", 2, 1);
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 1f;
manager.CurrentEntity = other;
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
// An untracked ship is not drawn at (ActionGameManager.DrawsAt, since contact-hud): no hull effect, no impulse.
Expect("hull hit on an untracked ship", 2, 1);
// Once the player's instruments track it, the same hit is drawn.
other.VisibleEntities.Add(ship);
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
Expect("hull hit on a tracked ship", 3, 1);
other.VisibleEntities.Remove(ship);
manager.CurrentEntity = ship;
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Shield));
Expect("player's shield hit", 3, 2);
// Grabs: the beam presents the simulation's events and nothing else.
float Emission() => beam.ParticleSystem.emission.rateOverTimeMultiplier;
void ExpectBeam(string what, float emission, bool towardHeld)
{
beam.Update();
var forward = beam.transform.forward;
var aimed = towardHeld ? forward.x > .999f : forward.z > .999f;
if (abs(Emission() - emission) > 1e-4f || !aimed)
throw new InvalidOperationException($"{what}: emission {Emission()} (want {emission}), forward {forward} (want {(towardHeld ? "+x" : "+z")}).");
}
GrabEvent Grab(GrabPhase phase, float3 at) => new GrabEvent(new FloatingBodyId(7), phase, at, 1f);
ExpectBeam("idle beam", 0f, false);
ship.Capabilities.PublishGrab(Grab(GrabPhase.Started, float3(20f, 0f, 0f)));
ExpectBeam("grab started", .8f, true);
foreach (var phase in new[] { GrabPhase.Extend, GrabPhase.Envelop, GrabPhase.Pull })
{
ship.Capabilities.PublishGrab(Grab(phase, float3(20f, 0f, 0f)));
ExpectBeam($"grab {phase}", .8f, true);
}
ship.Capabilities.PublishGrab(Grab(GrabPhase.Completed, float3(20f, 0f, 0f)));
ExpectBeam("grab completed", 0f, false);
// A grab cancelled after its item vanished carries no position: the beam goes dark and never turns toward it.
ship.Capabilities.PublishGrab(Grab(GrabPhase.Started, float3(20f, 0f, 0f)));
ExpectBeam("second grab started", .8f, true);
ship.Capabilities.PublishGrab(Grab(GrabPhase.Cancelled, float3(0f, 0f, 0f)));
ExpectBeam("grab cancelled", 0f, false);
// Aim is on the ground plane: a beam above the held point does not pitch toward it.
var beamHome = beam.transform.position;
beam.transform.position = beamHome + new Vector3(0f, 10f, 0f);
ship.Capabilities.PublishGrab(Grab(GrabPhase.Started, float3(20f, 7f, 0f)));
ExpectBeam("grab from above", .8f, true);
ship.Capabilities.PublishGrab(Grab(GrabPhase.Completed, float3(20f, 7f, 0f)));
beam.transform.position = beamHome;
// A grab held at the beam's own position has no direction to take: the beam keeps the aim it had.
beam.Direction = Vector3.right;
var atBeam = float3(beam.transform.position.x, 0f, beam.transform.position.z);
ship.Capabilities.PublishGrab(Grab(GrabPhase.Started, atBeam));
ExpectBeam("grab at the beam", .8f, true);
ship.Capabilities.PublishGrab(Grab(GrabPhase.Completed, atBeam));
beam.Direction = Vector3.forward;
ExpectBeam("after the grab at the beam", 0f, false);
// The beam belongs to the ship's entity: another entity's grab, with its own beam bound, moves only that beam.
other.Capabilities.PublishGrab(Grab(GrabPhase.Started, float3(20f, 0f, 0f)));
ExpectBeam("another entity's grab", 0f, false);
otherBeam.Update();
if (abs(otherBeam.ParticleSystem.emission.rateOverTimeMultiplier - .8f) > 1e-4f)
throw new InvalidOperationException("The other entity's own beam did not present its grab.");
Debug.Log("CAPABILITY_PRESENTATION_SMOKE ok effects=3 impulses=2 grabs=ok");
return new[] { feedbackObject, effectObject, managerObject, instanceObject, otherObject };
}
private static void Clean(GameObject[] objects, string work)
{
if (objects != null)
foreach (var go in objects)
if (go != null) UnityEngine.Object.DestroyImmediate(go);
try { if (Directory.Exists(work)) Directory.Delete(work, true); }
catch (Exception error) { Debug.LogWarning($"Impact presenter smoke left {work}: {error.Message}"); }
}
}

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}.");
}
@ -186,35 +180,6 @@ public static class FloatingItemsDrawnSmoke
At(drawn, released, "step");
Require(drawn.transform.position != before, "the pickup did not move with the body.");
// A held item keeps its label lit, so a pull is read as it happens. The label is let go dark first (nothing views
// it), then the zone holds the item through its own TryHold and the label must come up.
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;
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;
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
// is where the zone puts a held item at the same progress.
var (extend, envelop, pull) = (1.5f, .75f, 1.5f);
var holderAt = new CultMath.float3(taker.Position.x, 0f, taker.Position.z);
var claimedAt = new CultMath.float3(early.HeldFrom.x, 0f, early.HeldFrom.y);
foreach (var elapsed in new[] { 0f, extend + envelop - .01f, extend + envelop + .3f, extend + envelop + .75f, extend + envelop + pull })
{
zone.SetHeldProgress(early.Id, Mathf.Clamp01((elapsed - extend - envelop) / pull)); // the pull starts after Extend and Envelop
zone.Update(.01f);
var want = FieldTester.PulledPosition(claimedAt, holderAt, elapsed, extend, envelop, pull);
Require(Mathf.Abs(early.Body.Position.x - want.x) < 1e-3f && Mathf.Abs(early.Body.Position.y - want.z) < 1e-3f,
$"stand-in pose at {elapsed}s is {want}, the zone holds the item at {early.Body.Position}.");
}
zone.Free(early.Id);
// Pickup destroys exactly the picked-up item's pickup.
Require(zone.TryPickUp(taker, released.Id), "the station refused a one-cell stack.");
yield return null;

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

@ -0,0 +1,132 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using GameCult.Caching;
using UnityEditor;
using UnityEngine;
using static CultMath.math;
// Batch proof of ImpactPresenter's wiring, without a scene: a zone ship is bound to an EntityInstance through the game's
// own SetEntity, and impacts are published on the ship's Capabilities exactly as FireControl does. A hull absorb spawns
// one hull effect under the instance and, for the player's ship at ScreenShake 1, raises one camera impulse; ScreenShake 0
// or another ship raises none; a shield absorb spawns no hull effect. The shield ripple itself (ShieldManager.ShowHit
// needs a shield material and a rig) is looked at in the editor, not here.
//
// Unity -batchmode -nographics -projectPath <project> -executeMethod ImpactPresenterSmoke.Run -logFile <log>
//
// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
public static class ImpactPresenterSmoke
{
private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
public static void Run()
{
var work = Path.Combine(Path.GetTempPath(), $"impact-presenter-smoke-{Guid.NewGuid():N}");
GameObject[] objects = null;
try
{
Directory.CreateDirectory(work);
objects = Check(work);
Clean(objects, work);
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
Clean(objects, work);
EditorApplication.Exit(1);
}
}
// Edit mode runs no Awake or Start; the smoke runs the ones the game's scene would have.
private static void Awaken(Component component) => component.GetType().GetMethod("Awake", Any)?.Invoke(component, null);
private static GameObject[] Check(string work)
{
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")?.GameplaySettings
?? throw new InvalidOperationException("No Settings.asset.");
var shape = new Shape(5, 4);
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
var hullData = new HullData
{
Name = "Smoke Hull", HullType = HullType.Ship, Shape = shape, Durability = 1000, Mass = 1000, Armor = 0
};
var cache = AetheriaStores.Open(Path.Combine(work, "smoke.cc"), catalogWritable: true);
cache.Upsert(new VerseGrammar { Revision = 1 });
cache.Upsert(hullData);
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(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());
var other = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 2 }, new EntitySettings());
zone.Admit(ship, piloted: false);
zone.Admit(other, piloted: false);
// The scene's side: the tuning object with a pooled effect, and the game manager that names the player's ship.
var feedbackObject = new GameObject("Smoke Impact Feedback");
var feedback = feedbackObject.AddComponent<ImpactFeedback>();
Awaken(feedback);
var effectObject = new GameObject("Smoke Hull Effect");
feedback.HullEffect = effectObject.AddComponent<Prototype>();
var managerObject = new GameObject("Smoke Game Manager");
var manager = managerObject.AddComponent<ActionGameManager>();
typeof(ActionGameManager).GetProperty("Instance", Any).SetValue(null, manager);
manager.CurrentEntity = ship;
var instanceObject = new GameObject("Smoke Entity Instance");
var instance = instanceObject.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.SetEntity(null, ship);
int Effects() => instance.GetComponentsInChildren<Prototype>(true).Count(p => p != null && !p.isOriginalPrototype);
void Expect(string what, int effects, int kicks)
{
if (Effects() != effects || feedback.KicksRaised != kicks)
throw new InvalidOperationException($"{what}: {Effects()} hull effects (want {effects}), {feedback.KicksRaised} impulses (want {kicks}).");
}
AbsorbEvent Hit(AbsorbLayer layer) => new AbsorbEvent(float3(1f, 0f, 2f), float3(0f, 0f, 1f), 100f, DamageType.Kinetic, layer);
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 1f;
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
Expect("player's hull hit", 1, 1);
var effect = instance.GetComponentsInChildren<Prototype>(true).Single(p => !p.isOriginalPrototype);
var drawn = DrawAhead.Carried(ship, float3(1f, 0f, 2f), ActionGameManager.Clock.Lead);
if (abs(effect.transform.position.x - drawn.x) > 1e-3f || abs(effect.transform.position.z - drawn.z) > 1e-3f)
throw new InvalidOperationException("The hull effect is not at the drawn impact point.");
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 0f;
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
Expect("hull hit at ScreenShake 0", 2, 1);
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 1f;
manager.CurrentEntity = other;
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
Expect("hull hit on another ship", 3, 1);
manager.CurrentEntity = ship;
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Shield));
Expect("player's shield hit", 3, 2);
Debug.Log("IMPACT_PRESENTER_SMOKE ok effects=3 impulses=2");
return new[] { feedbackObject, effectObject, managerObject, instanceObject };
}
private static void Clean(GameObject[] objects, string work)
{
if (objects != null)
foreach (var go in objects)
if (go != null) UnityEngine.Object.DestroyImmediate(go);
try { if (Directory.Exists(work)) Directory.Delete(work, true); }
catch (Exception error) { Debug.LogWarning($"Impact presenter smoke left {work}: {error.Message}"); }
}
}

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); }
}
}
}

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