Aetheria/Assets/Editor/AetheriaStardustContinuityVerifier.cs
2026-07-16 12:27:19 +01:00

156 lines
7 KiB
C#

using System;
using System.Runtime.InteropServices;
using UnityEngine;
namespace Aetheria.Editor
{
internal static class AetheriaStardustContinuityVerifier
{
private const int Span = 16;
private const int GroupSize = 128;
private const float Spacing = 6f;
[StructLayout(LayoutKind.Sequential)]
private struct Particle
{
public Vector3 Position;
public Vector3 Color;
public float Size;
}
internal static void VerifyOneCellShift(ComputeShader program)
{
if (program == null) throw new ArgumentNullException(nameof(program));
if (!SystemInfo.supportsComputeShaders)
throw new InvalidOperationException("Stardust continuity proof requires compute-shader support.");
var kernel = program.FindKernel("UpdateParticles");
using var particles = new ComputeBuffer(Span * Span, 7 * sizeof(float), ComputeBufferType.Structured);
var field = ConstantTexture(new Color(0.5f, 0.5f, 0.5f, 1f));
var tint = ConstantTexture(Color.white);
var hue = ConstantTexture(Color.white);
try
{
program.EnableKeyword("FLOW_GLOBAL");
program.EnableKeyword("NOISE_SLOPE");
program.SetBuffer(kernel, "particles", particles);
program.SetTexture(kernel, "_NebulaSurfaceHeight", field);
program.SetTexture(kernel, "_NebulaTint", tint);
program.SetTexture(kernel, "HueTexture", hue);
program.SetVector("_NebulaSurfaceHeight_TexelSize", new Vector4(0.25f, 0.25f, 4f, 4f));
program.SetVector("_NebulaTint_TexelSize", new Vector4(0.25f, 0.25f, 4f, 4f));
program.SetVector("_Time", new Vector4(0.625f, 12.5f, 25f, 37.5f));
program.SetFloat("time", 12.5f);
program.SetFloat("period", 2f);
program.SetFloat("spacing", Spacing);
program.SetFloat("ceilingHeight", 0f);
program.SetFloat("floorHeight", -10f);
program.SetFloat("heightExponent", 3f);
program.SetFloat("maximumSize", 0.75f);
program.SetFloat("minimumSize", 0.25f);
program.SetFloat("minHeadroom", 25f);
program.SetFloat("maxHeadroom", 100f);
program.SetFloat("_FlowScale", 512f);
program.SetFloat("_FlowAmplitude", 15f);
program.SetFloat("_FlowScroll", 0.3125f);
program.SetFloat("_DynamicLodHigh", 7f);
program.SetFloat("_DynamicLodLow", 2f);
program.SetInt("span", Span);
var before = DispatchAndRead(program, kernel, particles, 0f, 0f);
AssertWholeBlockRemapIsInvisible(
before,
DispatchAndRead(program, kernel, particles, Spacing, 0f),
1,
0);
AssertWholeBlockRemapIsInvisible(
before,
DispatchAndRead(program, kernel, particles, 0f, Spacing),
0,
1);
AssertWholeBlockRemapIsInvisible(
before,
DispatchAndRead(program, kernel, particles, Spacing, Spacing),
1,
1);
Debug.Log("Aetheria Stardust continuity: 705 overlapping world cells remained bit-identical " +
"across X, Y, and diagonal whole-buffer remaps.");
}
finally
{
UnityEngine.Object.DestroyImmediate(field);
UnityEngine.Object.DestroyImmediate(tint);
UnityEngine.Object.DestroyImmediate(hue);
}
}
private static Particle[] DispatchAndRead(
ComputeShader program,
int kernel,
ComputeBuffer particles,
float gridCenterX,
float gridCenterY)
{
program.SetVector("_GridTransform", new Vector4(gridCenterX, gridCenterY, 1536f, 1536f));
program.Dispatch(kernel, Mathf.CeilToInt((float)(Span * Span) / GroupSize), 1, 1);
var result = new Particle[Span * Span];
particles.GetData(result);
return result;
}
private static void AssertWholeBlockRemapIsInvisible(
Particle[] before,
Particle[] after,
int cellDeltaX,
int cellDeltaY)
{
var half = Span / 2;
var sameSlot = half * Span + half;
if (BitwiseEqual(before[sameSlot], after[sameSlot]))
throw new InvalidOperationException(
"Stardust continuity fixture did not reassign the sampled buffer slot to a new world cell.");
var minimumWorldX = Math.Max(-half, -half + cellDeltaX);
var maximumWorldX = Math.Min(half - 1, half - 1 + cellDeltaX);
var minimumWorldY = Math.Max(-half, -half + cellDeltaY);
var maximumWorldY = Math.Min(half - 1, half - 1 + cellDeltaY);
for (var worldY = minimumWorldY; worldY <= maximumWorldY; worldY++)
for (var worldX = minimumWorldX; worldX <= maximumWorldX; worldX++)
{
var beforeIndex = (worldY + half) * Span + worldX + half;
var afterIndex = (worldY - cellDeltaY + half) * Span +
(worldX - cellDeltaX) + half;
if (!BitwiseEqual(before[beforeIndex], after[afterIndex]))
throw new InvalidOperationException(
$"Stardust cell ({worldX},{worldY}) changed while crossing one spatial cell; " +
$"buffer slots {beforeIndex} and {afterIndex} must describe the same stateless particle.");
}
}
private static bool BitwiseEqual(Particle left, Particle right) =>
Bits(left.Position.x) == Bits(right.Position.x) &&
Bits(left.Position.y) == Bits(right.Position.y) &&
Bits(left.Position.z) == Bits(right.Position.z) &&
Bits(left.Color.x) == Bits(right.Color.x) &&
Bits(left.Color.y) == Bits(right.Color.y) &&
Bits(left.Color.z) == Bits(right.Color.z) &&
Bits(left.Size) == Bits(right.Size);
private static int Bits(float value) => BitConverter.SingleToInt32Bits(value);
private static Texture2D ConstantTexture(Color color)
{
var texture = new Texture2D(4, 4, TextureFormat.RGBA32, false, true)
{
filterMode = FilterMode.Bilinear,
wrapMode = TextureWrapMode.Clamp,
hideFlags = HideFlags.HideAndDontSave
};
var pixels = new Color[16];
for (var index = 0; index < pixels.Length; index++) pixels[index] = color;
texture.SetPixels(pixels);
texture.Apply(false, false);
return texture;
}
}
}