Initial commit: AIGame Unity client with URP, HybridCLR, YooAsset and tooling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-09-21 14:21:07 +08:00
co-authored by Cursor
commit f3f66ab063
931 changed files with 112366 additions and 0 deletions
@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using NUnit.Framework;
using UnityEngine;
using Unity.Pipeline.Compilation;
using Unity.Pipeline.Editor.BuildProcessors;
using Unity.Pipeline.CodeReload;
using IlInterpreter.Interpreter;
namespace Unity.Pipeline.Tests.Editor
{
/// <summary>
/// Locks the host-surface drift guarantee: the runtime interpreter binding and the build-time
/// <c>link.xml</c> preservation set are both projections of the one builder
/// <see cref="IlInterpreterHostBindings.CreateStandard"/>. If a future change reconstructs the surface
/// at any one site instead of going through the builder, these tests fail.
/// </summary>
class IlInterpreterHostSurfaceTests
{
// Normalize a registered type to the link.xml fullname the generator emits:
// constructed generics collapse to their open definition; nested '+' becomes '/'.
static string LinkName(Type t)
{
var def = t.IsGenericType && !t.IsGenericTypeDefinition ? t.GetGenericTypeDefinition() : t;
return def.FullName?.Replace('+', '/');
}
static HashSet<string> RegisteredLinkNames(HostBinding binding) =>
binding.RegisteredTypes
.Select(LinkName)
.Where(n => !string.IsNullOrEmpty(n))
.ToHashSet(StringComparer.Ordinal);
[Test]
public void CreateStandard_IsDeterministic()
{
var a = RegisteredLinkNames(IlInterpreterHostBindings.CreateStandard());
var b = RegisteredLinkNames(IlInterpreterHostBindings.CreateStandard());
Assert.That(a, Is.EquivalentTo(b), "CreateStandard() must build an identical surface every call.");
}
[Test]
public void CreateStandard_IncludesDebugAndKeyHazardTypes()
{
var names = RegisteredLinkNames(IlInterpreterHostBindings.CreateStandard());
// Debug consolidated into the canonical surface (was eval-only string-wired before).
Assert.That(names, Contains.Item("UnityEngine.Debug"), "Debug must be in RegisteredTypes so link.xml preserves it.");
// Reflection-walked types that IL2CPP would strip without link.xml.
Assert.That(names, Contains.Item("UnityEngine.GameObject"));
Assert.That(names, Contains.Item("UnityEngine.Transform"));
Assert.That(names, Contains.Item("UnityEngine.Vector3"));
}
[Test]
public void AutoBindPolicy_SkipsBcl_ButNotSystemPrefixedGameAssemblies()
{
// The BCL must stay authoritative (the AllowBcl shims collapse doubles into the
// script's float number space), but a game assembly that merely starts with the word —
// "SystemsRuntime" — must stay auto-bindable, or reloaded code calling its types fails
// with "not registered" even though the dev link.xml preserved it.
foreach (var skipped in new[]
{ "System", "System.Core", "mscorlib", "netstandard", "Unity.Pipeline", "Unity.Pipeline.IlInterpreter" })
Assert.IsTrue(IlInterpreterHostBindings.IsAutoBindSkippedAssembly(skipped),
$"'{skipped}' must be skipped by the auto-bind policy.");
foreach (var bindable in new[]
{ "SystemsRuntime", "UnityChanDemo", "Assembly-CSharp", "UnityEngine.CoreModule", "Unity.Burst" })
Assert.IsFalse(IlInterpreterHostBindings.IsAutoBindSkippedAssembly(bindable),
$"'{bindable}' must remain resolvable by demand-time auto-bind.");
}
// The code-reload sink builds CreateStandard() then overlays AllowType(targetType). The parity
// claim is "identical modulo the target type": the overlay only adds, never removes.
class DummyReloadTarget { public int Value; public void Tick() { } }
[Test]
public void CodeReloadOverlay_OnlyAdds_TargetType()
{
var baseNames = RegisteredLinkNames(IlInterpreterHostBindings.CreateStandard());
var overlay = RegisteredLinkNames(
IlInterpreterHostBindings.CreateStandard().AllowType(typeof(DummyReloadTarget)));
Assert.That(overlay, Is.SupersetOf(baseNames), "Overlay must not drop any standard type.");
Assert.That(overlay, Contains.Item(LinkName(typeof(DummyReloadTarget))), "Overlay must register the target type.");
}
// The drift guarantee: link.xml preserves the full CreateStandard() surface (no host type dropped),
// plus reloadable [CodeReload] target components (so their private fields survive IL2CPP stripping).
// Every emitted entry must be one or the other — nothing else leaks in.
[Test]
public void LinkXml_Preserves_HostSurface_And_Only_ReloadTargets_Beyond()
{
var hostSurface = RegisteredLinkNames(IlInterpreterHostBindings.CreateStandard());
var xml = CodeReloadLinkXmlGenerator.GenerateLinkXml();
var emitted = Regex.Matches(xml, "<type fullname=\"([^\"]+)\"")
.Select(m => m.Groups[1].Value)
.ToHashSet(StringComparer.Ordinal);
Assert.That(emitted, Is.SupersetOf(hostSurface),
"link.xml must preserve every host type CreateStandard() registers.");
foreach (var name in emitted.Except(hostSurface))
Assert.IsTrue(IsReloadableTargetByLinkName(name),
$"link.xml preserved a type that is neither a host-surface type nor a [CodeReload] target: {name}");
}
// Resolve a link.xml fullname ('/' = nested separator) to a loaded Type and check whether it carries
// a class-level [CodeReload] or any [CodeReload] method — the same predicate
// CodeReloadLinkXmlGenerator uses to decide what reload targets to preserve.
static bool IsReloadableTargetByLinkName(string linkName)
{
var full = linkName.Replace('/', '+');
foreach (var asm in PipelineUtils.GetLoadedAssemblies())
{
Type t;
try { t = asm.GetType(full, throwOnError: false); }
catch { continue; }
if (t == null) continue;
if (t.IsDefined(typeof(CodeReloadAttribute), inherit: false))
return true;
const System.Reflection.BindingFlags f = System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.DeclaredOnly;
foreach (var m in t.GetMethods(f))
if (m.IsDefined(typeof(CodeReloadAttribute), inherit: false))
return true;
return false;
}
return false;
}
}
}