This repository has been archived on 2026-10-29. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
EveUnity/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs
2026-07-10 04:03:09 +01:00

255 lines
12 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using GameCult.Caching;
using GameCult.Caching.MessagePack;
using GameCult.Eve.Surface;
using GameCult.Eve.UnityScene;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace GameCult.EveUnity.GenericClient.PlayModeTests
{
public sealed class GenericWorldCaptureTests
{
[Test]
public void CoreProviderDocumentsRoundTripThroughCultCacheSerialization()
{
var advertisement = new EveProviderAdvertisementDocument(
"eve.world-smoke",
"eve-world-smoke",
"eve.local",
"Generic world",
"game.daemon",
"127.0.0.1:7777",
"2026-07-10T00:00:00Z",
new EveProviderFreshness("fresh", "2026-07-10T00:00:00Z", 5000),
new[] { EveSurfaceDocument.SchemaId, EveCommandReceiptDocument.SchemaId },
Array.Empty<EveProviderWitness>(),
new[]
{
new EveAdvertisedSurface(
"eve.world-smoke.surface",
EveSurfaceDocument.SchemaId,
"surface:eve.world-smoke",
"cultmesh-record",
"active",
"interactive-world",
new EveWorldInteractionAdvertisement(
"provider-authored-world-surface",
Array.Empty<string>(),
"world.commands",
"commands:eve.world-smoke",
EveCommandReceiptDocument.SchemaId,
"receipts:eve.world-smoke",
"assets:eve.world-smoke",
new[] { "unity-scene" },
"provider-owns-world-state-command-acceptance-and-receipts"))
},
Array.Empty<EveAdvertisedCommand>());
var payload = CultDocumentMessagePackSerialization.Serialize(advertisement);
var decoded = CultDocumentMessagePackSerialization.Deserialize<EveProviderAdvertisementDocument>(payload);
Assert.That(decoded.ProviderId, Is.EqualTo("eve.world-smoke"));
Assert.That(decoded.Surfaces.Count, Is.EqualTo(1));
Assert.That(decoded.Surfaces[0].WorldInteraction.CommandRecordRef, Is.EqualTo("commands:eve.world-smoke"));
}
[UnityTest]
public IEnumerator GenericClientRendersWorldSmokeAndEmitsCommand()
{
var hostObject = new GameObject("EveUnity Generic Client");
var sceneRoot = new GameObject("World Projection");
var provider = hostObject.AddComponent<WorldSmokeProvider>();
var host = hostObject.AddComponent<EveUnityPlayableWorldClientHost>();
var cameraObject = new GameObject("Capture Camera");
var lightObject = new GameObject("World Light");
var ground = GameObject.CreatePrimitive(PrimitiveType.Plane);
RenderTexture target = null;
Texture2D pixels = null;
try
{
provider.CurrentDocument = WorldDocument();
host.Configure(sceneRoot.transform, provider, provider);
var presentation = host.Connect();
Assert.That(presentation.ActiveEntities, Is.EqualTo(3));
Assert.That(
sceneRoot.GetComponentsInChildren<EveUnityPlayableWorldEntityMarker>(),
Has.Length.EqualTo(3));
foreach (var marker in sceneRoot.GetComponentsInChildren<EveUnityPlayableWorldEntityMarker>())
{
var renderer = marker.GetComponent<Renderer>();
renderer.material.color = marker.Controllable
? new Color(0.18f, 0.75f, 1f)
: marker.EntityKind == "enemy"
? new Color(1f, 0.25f, 0.2f)
: new Color(1f, 0.78f, 0.15f);
}
ground.name = "Projection Ground";
ground.transform.position = new Vector3(0f, -0.65f, 3f);
ground.transform.localScale = new Vector3(2.2f, 1f, 2.2f);
ground.GetComponent<Renderer>().material.color = new Color(0.12f, 0.16f, 0.18f);
host.SubmitMoveVectorIntent("player", 0f, 1f);
Assert.That(provider.Submitted, Has.Count.EqualTo(1));
Assert.That(provider.Submitted[0].ProviderId, Is.EqualTo("eve.world-smoke"));
var light = lightObject.AddComponent<Light>();
light.type = LightType.Directional;
light.intensity = 1.4f;
lightObject.transform.rotation = Quaternion.Euler(45f, -35f, 0f);
var camera = cameraObject.AddComponent<Camera>();
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = new Color(0.035f, 0.055f, 0.08f, 1f);
camera.transform.position = new Vector3(10f, 8f, -12f);
camera.transform.LookAt(new Vector3(0f, 1f, 3f));
target = new RenderTexture(640, 360, 24, RenderTextureFormat.ARGB32);
camera.targetTexture = target;
yield return null;
camera.Render();
RenderTexture.active = target;
pixels = new Texture2D(640, 360, TextureFormat.RGB24, false);
pixels.ReadPixels(new Rect(0, 0, 640, 360), 0, 0);
pixels.Apply();
var changedPixels = 0;
var background = camera.backgroundColor;
foreach (var pixel in pixels.GetPixels())
{
if (Mathf.Abs(pixel.r - background.r) +
Mathf.Abs(pixel.g - background.g) +
Mathf.Abs(pixel.b - background.b) > 0.08f)
changedPixels++;
}
Assert.That(changedPixels, Is.GreaterThan(500));
var capturePath = Environment.GetEnvironmentVariable("EVEUNITY_CAPTURE_PATH");
if (string.IsNullOrWhiteSpace(capturePath))
{
capturePath = Path.GetFullPath(Path.Combine(
Application.dataPath,
"..",
"..",
"artifacts",
"playmode",
"world-smoke.png"));
}
Directory.CreateDirectory(Path.GetDirectoryName(capturePath));
File.WriteAllBytes(capturePath, pixels.EncodeToPNG());
Assert.That(new FileInfo(capturePath).Length, Is.GreaterThan(1024));
}
finally
{
RenderTexture.active = null;
var camera = cameraObject.GetComponent<Camera>();
if (camera != null) camera.targetTexture = null;
if (target != null) UnityEngine.Object.DestroyImmediate(target);
if (pixels != null) UnityEngine.Object.DestroyImmediate(pixels);
UnityEngine.Object.DestroyImmediate(lightObject);
UnityEngine.Object.DestroyImmediate(cameraObject);
UnityEngine.Object.DestroyImmediate(ground);
UnityEngine.Object.DestroyImmediate(sceneRoot);
UnityEngine.Object.DestroyImmediate(hostObject);
}
}
private static EveUnitySceneProviderSurfaceDocument WorldDocument()
{
var entities = new[]
{
Entity("player-node", "player", "player", "Vanguard", "0,0,0", "1.2", true),
Entity("enemy-node", "enemy", "enemy", "Raider", "3,0,4", "1", false),
Entity("landmark-node", "landmark", "landmark", "Beacon", "-4,0,6", "1.8", false)
};
var document = new EveSurfaceDocument(
"eve.world-smoke",
"game.runtime",
"Generic interactive world",
1,
"2026-07-10T00:00:00Z",
new EveSurfaceTree(
"eve.world-smoke.surface",
new EveSurfaceComponent(
"world.root",
"surface",
Props(),
new[]
{
new EveSurfaceComponent(
"world.scene",
"world.scene3d",
Props(
("playerEntityId", "player"),
("movementCommand", "world.move"),
("inputProfile", "third-person"),
("cameraRig", "third-person-orbit")),
entities)
}),
Array.Empty<EveStyleToken>()),
Array.Empty<EveCommandTemplate>());
return new EveUnitySceneProviderSurfaceDocument(
document,
new EveUnitySceneProviderSurfaceAdvertisement(
"eve.world-smoke.surface",
"interactive-world",
new EveUnitySceneWorldInteraction(
"provider-authored-world-surface",
"world.commands",
"gamecult.eve.command_receipt.v1",
"provider-owns-world-state-command-acceptance-and-receipts")),
"cultmesh://eve.world-smoke/surfaces/world",
1);
}
private static EveSurfaceComponent Entity(
string nodeId,
string entityId,
string entityKind,
string label,
string position,
string radius,
bool controllable)
{
return new EveSurfaceComponent(
nodeId,
"world.entity3d",
Props(
("entityId", entityId),
("entityKind", entityKind),
("label", label),
("position", position),
("radius", radius),
("assetRef", $"cultmesh://eve.world-smoke/assets/{entityKind}"),
("selectable", "true"),
("controllable", controllable ? "true" : "false"),
("moveCommand", controllable ? "world.move" : "")),
Array.Empty<EveSurfaceComponent>());
}
private static Dictionary<string, string> Props(params (string Key, string Value)[] values)
{
var props = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var value in values) props[value.Key] = value.Value;
return props;
}
private sealed class WorldSmokeProvider : MonoBehaviour,
IEveUnitySceneProviderSurfaceDocumentSource,
IEveUnitySceneCommandSink
{
public EveUnitySceneProviderSurfaceDocument CurrentDocument { get; set; }
public string SinkKind => "world-smoke-command-sink";
public List<EveSurfaceCommandRequest> Submitted { get; } = new List<EveSurfaceCommandRequest>();
public event Action<EveUnitySceneProviderSurfaceDocument> DocumentAvailable;
public void Submit(EveSurfaceCommandRequest request) => Submitted.Add(request);
}
}
}