197 results

global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
using Braxnet;
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			// var go = new GameObject();
			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
			Assert.IsTrue( scene.Directory.FindByName( "LibraryTestComponent" ) != null );
		}
	}

}

[Autoload]
public class LibraryTestComponent : Component
{
	
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	public static Sandbox.TestAppSystem AppSystem;

	[AssemblyInitialize]
	public static void AssemblyInitialize( TestContext context )
	{
		AppSystem = new Sandbox.TestAppSystem();
		AppSystem.Init();
	}

	[AssemblyCleanup]
	public static void AssemblyCleanup()
	{
		AppSystem.Shutdown();
	}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	public static Sandbox.TestAppSystem AppSystem;

	[AssemblyInitialize]
	public static void AssemblyInitialize( TestContext context )
	{
		AppSystem = new Sandbox.TestAppSystem();
		AppSystem.Init();
	}

	[AssemblyCleanup]
	public static void AssemblyCleanup()
	{
		AppSystem.Shutdown();
	}
}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
using Hexagon.V2.Application;

namespace Hexagon.V2.Tests.Application;

[TestClass]
public sealed class CharacterNameSkeletonTests
{
	[TestMethod]
	public void WithinScriptLookAlikesCollapseOntoTheNameTheyImitate()
	{
		var alice = CharacterNameSkeleton.Of( "Alice" );
		// Digit-for-letter and the i/l/1 shape collision.
		Assert.AreEqual( alice, CharacterNameSkeleton.Of( "A1ice" ) );
		Assert.AreEqual( alice, CharacterNameSkeleton.Of( "Alice" ) );
		Assert.AreEqual( alice, CharacterNameSkeleton.Of( "AIice" ) );
		Assert.AreEqual( alice, CharacterNameSkeleton.Of( "4lice" ) );
		// Separators and punctuation carry no identity.
		Assert.AreEqual( alice, CharacterNameSkeleton.Of( "A l i c e" ) );
		Assert.AreEqual( alice, CharacterNameSkeleton.Of( "A-l-i-c-e" ) );
		// Accents are decoration, not identity.
		Assert.AreEqual( CharacterNameSkeleton.Of( "Jose" ), CharacterNameSkeleton.Of( "Jos\u00E9" ) );
		// The classic digraph pair.
		Assert.AreEqual( CharacterNameSkeleton.Of( "Bemard" ), CharacterNameSkeleton.Of( "Bernard" ) );
		Assert.AreEqual( CharacterNameSkeleton.Of( "Walker" ), CharacterNameSkeleton.Of( "VValker" ) );
	}

	[TestMethod]
	public void WholeNameWrittenInAnotherScriptCollapsesOntoTheLatinNameItImitates()
	{
		// The script profile permits these - they are single-script names. Only the skeleton
		// sees that they are drawn to read as "Alice" and "Petro".
		// Cyrillic es-o-o-er-ie-ghe, which draws "Cooper" without one Latin letter in it.
		Assert.AreEqual(
			CharacterNameSkeleton.Of( "Cooper" ),
			CharacterNameSkeleton.Of( "\u0441\u043E\u043E\u0440\u0435\u0433" ) );
		// Greek rho-omicron-rho-omicron-nu, which draws "Popov".
		Assert.AreEqual(
			CharacterNameSkeleton.Of( "Popov" ),
			CharacterNameSkeleton.Of( "\u03C1\u03BF\u03C1\u03BF\u03BD" ) );
	}

	[TestMethod]
	public void FoldingFollowsUnicodeRatherThanLocalIntuition()
	{
		// A documented residual, asserted so a table bump surfaces it rather than hiding it:
		// UTS #39 judges Cyrillic em confusable with a turned w, NOT with Latin m, so a Cyrillic
		// name using it does not collapse onto the Latin name it arguably resembles. The script
		// profile still blocks the mixed-script form, which is the reachable attack.
		Assert.AreNotEqual(
			CharacterNameSkeleton.Of( "Maxwell" ),
			CharacterNameSkeleton.Of( "\u043C\u0430\u0445\u051D\u0435\u0456\u0456" ) );

		// Latin i and l stay distinct - the standard folds the digit 1, capital I and the bar
		// onto l, but never merges lowercase i with it.
		Assert.AreEqual( CharacterNameSkeleton.Of( "Alice" ), CharacterNameSkeleton.Of( "A1ice" ) );
		Assert.AreNotEqual( CharacterNameSkeleton.Of( "Bili" ), CharacterNameSkeleton.Of( "Bill" ) );
	}

	[TestMethod]
	public void DistinctNamesKeepDistinctSkeletons()
	{
		Assert.AreNotEqual( CharacterNameSkeleton.Of( "Alice" ), CharacterNameSkeleton.Of( "Alicia" ) );
		Assert.AreNotEqual( CharacterNameSkeleton.Of( "Alice" ), CharacterNameSkeleton.Of( "Alison" ) );
		Assert.AreNotEqual( CharacterNameSkeleton.Of( "Barney" ), CharacterNameSkeleton.Of( "Gordon" ) );
		Assert.AreNotEqual( CharacterNameSkeleton.Of( "Judith Mossman" ), CharacterNameSkeleton.Of( "Eli Vance" ) );
		// A name in a script with no Latin look-alikes is left alone rather than flattened.
		Assert.AreNotEqual(
			CharacterNameSkeleton.Of( "\u0410\u043B\u0438\u0441\u0430" ),      // Cyrillic "Alisa"
			CharacterNameSkeleton.Of( "\u0411\u043E\u0440\u0438\u0441" ) );    // Cyrillic "Boris"
	}

	[TestMethod]
	public void NameWithNoIdentityBearingCharactersHasAnEmptySkeleton()
	{
		Assert.AreEqual( string.Empty, CharacterNameSkeleton.Of( "..." ) );
		Assert.AreEqual( string.Empty, CharacterNameSkeleton.Of( "---" ) );
		Assert.AreEqual( string.Empty, CharacterNameSkeleton.Of( string.Empty ) );
	}
}
using System.Text.Json;
using Hexagon.V2.Application;
using Hexagon.V2.Domain;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Hexagon.V2.Tests.Application;

[TestClass]
public sealed class CurrencyAndCharacterRulesTests
{
	[TestMethod]
	public void CurrencyRejectsUnderflowAndOverflow()
	{
		var character = CreateCharacter( 10 );
		Assert.IsTrue( CurrencyService.Debit( character, 11 ).Failed );
		Assert.IsTrue( CurrencyService.Credit( character with { Balance = long.MaxValue }, 1 ).Failed );
	}

	[TestMethod]
	public void SlotAllocatorUsesLowestGap()
	{
		var characters = new[] { CreateCharacter( 0 ) with { Slot = 0 }, CreateCharacter( 0 ) with { Slot = 2 } };
		Assert.AreEqual( 1, CharacterRules.FindLowestFreeSlot( characters ) );
	}

	[TestMethod]
	public void SlotAllocatorReturnsMinusOneWhenEverySlotIsOccupied()
	{
		var characters = Enumerable.Range( 0, CharacterRules.MaximumSlots )
			.Select( slot => CreateCharacter( 0 ) with { Slot = slot } )
			.ToArray();
		Assert.AreEqual( -1, CharacterRules.FindLowestFreeSlot( characters ) );

		var oneFree = characters.Where( character => character.Slot != CharacterRules.MaximumSlots - 1 ).ToArray();
		Assert.AreEqual( CharacterRules.MaximumSlots - 1, CharacterRules.FindLowestFreeSlot( oneFree ) );
	}

	[TestMethod]
	public void CreationRejectsUnregisteredFields()
	{
		var request = new CharacterCreationRequest
		{
			Name = "Valid Name",
			Description = "A sufficiently long character description.",
			Model = new DefinitionId( "test.model" ),
			Faction = new FactionId( "test.faction" ),
			Fields = new Dictionary<string, CreationValue> { ["money"] = CreationValue.Integer( long.MaxValue ) }
		};

		Assert.IsTrue( CharacterRules.ValidateCreationRequest( request, new HashSet<string>() ).Failed );
	}

	[TestMethod]
	public void IdentityTextRejectsControlAndFormatCharacters()
	{
		// A name reaches other players verbatim through recognition records and nameplates, so
		// spoofing payloads must not survive the creation boundary. These are written as escapes
		// on purpose: as literals they are invisible in an editor and lost by copy/paste.
		foreach ( var name in new[]
		{
			"Bob\nCombine Overwatch", // U+000A interior line break
			"Bob\u0007Smith",         // C0 control
			"Bob\u202Eelbat",         // right-to-left override
			"Bo\u200Bb Smith",        // zero-width space
			"Bob\u200DSmith",         // zero-width joiner
			"Bob\u2066Smith\u2069",   // bidirectional isolates
			"Bob\uFEFFSmith"          // zero-width no-break space
		} )
		{
			Assert.IsTrue(
				CharacterRules.NormalizeName( name ).Failed,
				$"Name '{name.Replace( "\n", "\\n" )}' should have been rejected." );
			Assert.IsTrue( CharacterRules.ValidateCreationRequest( Request( name: name ), NoFields ).Failed );
		}

		// Zero-width characters are not whitespace, so they survive Trim: a length check
		// alone can never catch a name built entirely from them.
		Assert.IsTrue( CharacterRules.NormalizeName( "\u200B\u200B\u200B\u200B" ).Failed );
	}

	[TestMethod]
	public void DescriptionKeepsLineBreaksButRejectsOtherControlCharacters()
	{
		var prose = "A tall citizen in a worn jacket.\nHe keeps his hands in his pockets.";
		Assert.AreEqual( prose, CharacterRules.NormalizeDescription( prose ).Value );
		Assert.IsTrue( CharacterRules.NormalizeDescription( "A citizen\u202Ewith a reversed tail." ).Failed );
		Assert.IsTrue( CharacterRules.NormalizeDescription( "A citizen with a\u0000 null byte." ).Failed );
	}

	[TestMethod]
	public void IdentityTextIsCanonicalizedSoValidationAndStorageAgree()
	{
		// Combining and precomposed acutes render identically, so they must not be
		// storable as two distinct names.
		var decomposed = CharacterRules.NormalizeName( "  Ame\u0301lie  " );
		Assert.IsTrue( decomposed.Succeeded );
		Assert.AreEqual( "Am\u00E9lie", decomposed.Value );
		Assert.AreEqual( decomposed.Value, CharacterRules.NormalizeName( "Am\u00E9lie" ).Value );

		// An unpaired surrogate is rejected rather than thrown out of Normalize.
		Assert.IsTrue( CharacterRules.NormalizeName( "Bob\uD800Smith" ).Failed );

		// Ordinary names still pass.
		Assert.IsTrue( CharacterRules.NormalizeName( "Valid Name" ).Succeeded );
		Assert.IsTrue( CharacterRules.NormalizeName( "Baßmann-O'Neill" ).Succeeded );
		Assert.IsTrue( CharacterRules.ValidateCreationRequest( Request(), NoFields ).Succeeded );
	}

	[TestMethod]
	public void NameRejectsMixedScriptImpersonation()
	{
		// Each of these renders as ordinary Latin text but smuggles one letter from another
		// script, which is exactly how a name is made to look like someone else's.
		Assert.IsTrue( CharacterRules.NormalizeName( "\u0410lice" ).Failed );  // Cyrillic A
		Assert.IsTrue( CharacterRules.NormalizeName( "Alic\u0435" ).Failed );  // Cyrillic e
		Assert.IsTrue( CharacterRules.NormalizeName( "\u03A1eter" ).Failed );  // Greek Rho
		Assert.IsTrue( CharacterRules.NormalizeName( "\u13AAlice" ).Failed );  // Cherokee
		Assert.IsTrue( CharacterRules.ValidateCreationRequest( Request( name: "\u0410lice" ), NoFields ).Failed );

		// A wholly Cyrillic or Greek name is a real name, not an impersonation.
		Assert.IsTrue( CharacterRules.NormalizeName( "Алиса" ).Succeeded );
		Assert.IsTrue( CharacterRules.NormalizeName( "Γεωργος" ).Succeeded );
	}

	[TestMethod]
	public void NameAllowsTheScriptCombinationsRealNamesNeed()
	{
		// Japanese mixes Han, Hiragana and Katakana by nature, and may carry Latin.
		Assert.IsTrue( CharacterRules.NormalizeName( "田中 ひろし" ).Succeeded );
		Assert.IsTrue( CharacterRules.NormalizeName( "タナカ Tanaka" ).Succeeded );
		// Korean mixes Hangul with Latin.
		Assert.IsTrue( CharacterRules.NormalizeName( "김철수 Kim" ).Succeeded );
		// Digits, spaces and punctuation are script-neutral and never constrain a name.
		Assert.IsTrue( CharacterRules.NormalizeName( "Dr. Judith Mossman II" ).Succeeded );
		// Greek alongside Latin is not a combination any real name needs.
		Assert.IsTrue( CharacterRules.NormalizeName( "Alice Γεω" ).Failed );
	}

	[TestMethod]
	public void NameFoldsCompatibilityLookAlikesOntoWhatTheyImitate()
	{
		// Fullwidth Latin is Latin script, so mixed-script detection cannot see it. NFKC folding
		// is what stops it being a second, visually identical spelling of an existing name.
		var fullwidth = CharacterRules.NormalizeName( "\uFF21lice" );
		Assert.IsTrue( fullwidth.Succeeded );
		Assert.AreEqual( "Alice", fullwidth.Value );

		// A description is prose, not identity: it keeps compatibility characters and may mix
		// scripts freely. Only its control- and format-character rules apply.
		Assert.IsTrue( CharacterRules.NormalizeDescription(
			"A citizen who mutters Алиса under their breath." ).Succeeded );
		Assert.IsTrue( CharacterRules.NormalizeDescription(
			"A citizen wearing a \uFF21-series jumpsuit, collar upturned." ).Succeeded );
	}

	[TestMethod]
	public void TwoUnenumeratedScriptsAreStillTwoScripts()
	{
		// Neither Coptic nor Runic is in the named script table. They must not share one "other"
		// bucket, or a name could mix two writing systems the profile never actually looked at.
		Assert.IsTrue( CharacterRules.NormalizeName( "\u2C81\u2C83\u2C85" ).Succeeded );  // Coptic alone
		Assert.IsTrue( CharacterRules.NormalizeName( "\u16A0\u16A2\u16A6" ).Succeeded );  // Runic alone
		Assert.IsTrue( CharacterRules.NormalizeName( "\u2C81\u2C83\u16A0" ).Failed );     // the two mixed
		Assert.IsTrue( CharacterRules.NormalizeName( "\u2C81\u2C83Ab" ).Failed );         // one with Latin
	}
	private static IReadOnlySet<string> NoFields => new HashSet<string>();

	private static CharacterCreationRequest Request( string name = "Valid Name" ) =>
		new()
		{
			Name = name,
			Description = "A sufficiently long character description.",
			Model = new DefinitionId( "test.model" ),
			Faction = new FactionId( "test.faction" ),
			Fields = new Dictionary<string, CreationValue>()
		};

	private static CharacterRecord CreateCharacter( long balance )
	{
		using var document = JsonDocument.Parse( "{}" );
		return new CharacterRecord
		{
			Id = CharacterId.New(),
			AccountId = new AccountId( 1 ),
			Slot = 0,
			Name = "Test",
			Description = "A sufficiently long description.",
			Model = new DefinitionId( "test.model" ),
			Faction = new FactionId( "test.faction" ),
			Balance = balance,
			CreatedAt = DateTimeOffset.UtcNow,
			LastPlayedAt = DateTimeOffset.UtcNow,
			SchemaState = new TypedPayload
			{
				TypeId = new PersistedTypeId( "test.character" ),
				TypeVersion = 1,
				Data = document.RootElement.Clone()
			}
		};
	}
}
#nullable enable

using Hexagon.V2.Client;
using Hexagon.V2.Domain;
using Hexagon.V2.Kernel;
using Hexagon.V2.Networking;

namespace Hexagon.V2.Tests.Client;

[TestClass]
public sealed class ClientControllerTests
{
	[TestMethod]
	public async Task ControllerMapsEveryUiIntentToAnExplicitCommand()
	{
		var transport = new RecordingTransport();
		var controller = new HexClientController(transport);
		var characterId = CharacterId.New();
		var sourceId = InventoryId.New();
		var targetId = InventoryId.New();
		var itemId = ItemId.New();
		var actionId = new ActionId("use");
		var actionInstance = Guid.NewGuid();
		var actionArguments = new Dictionary<string, SnapshotValue>( StringComparer.Ordinal )
		{
			["amount"] = SnapshotValue.Integer( 3 )
		};
		var creation = new CharacterCreationInput(
			"Alyx", "Description", new DefinitionId("citizen_model"),
			new FactionId("citizen"), null);

		await controller.RequestCharactersAsync();
		await controller.CreateCharacterAsync(creation);
		await controller.LoadCharacterAsync(characterId);
		await controller.DeleteCharacterAsync(characterId);
		await controller.UnloadCharacterAsync();
		await controller.MoveItemAsync(sourceId, targetId, itemId, 2, 3);
		await controller.RunItemActionAsync(sourceId, itemId, actionId, actionArguments);
		actionArguments["amount"] = SnapshotValue.Integer( 99 );
		await controller.DropItemAsync(sourceId, itemId);
		await controller.PickUpItemAsync(itemId, targetId);
		await controller.SendChatAsync("ic", "Hello");
		await controller.CancelActionAsync(actionInstance);

		Assert.HasCount(11, transport.Commands);
		Assert.IsInstanceOfType<RequestCharacterListCommand>(transport.Commands[0]);
		Assert.AreSame(creation, ((CreateCharacterCommand)transport.Commands[1]).Input);
		Assert.AreEqual(characterId, ((LoadCharacterCommand)transport.Commands[2]).CharacterId);
		Assert.IsInstanceOfType<DeleteCharacterCommand>(transport.Commands[3]);
		Assert.IsInstanceOfType<UnloadCharacterCommand>(transport.Commands[4]);
		Assert.AreEqual((2, 3),
			(((MoveInventoryItemCommand)transport.Commands[5]).X,
				((MoveInventoryItemCommand)transport.Commands[5]).Y));
		Assert.AreEqual(actionId, ((RunItemActionCommand)transport.Commands[6]).ActionId);
		Assert.AreEqual( 3L, ((RunItemActionCommand)transport.Commands[6]).Arguments["amount"].IntegerValue );
		Assert.IsInstanceOfType<DropItemCommand>(transport.Commands[7]);
		Assert.IsInstanceOfType<PickUpItemCommand>(transport.Commands[8]);
		Assert.AreEqual("Hello", ((SendChatCommand)transport.Commands[9]).Text);
		Assert.AreEqual(actionInstance, ((CancelActionCommand)transport.Commands[10]).InstanceId);
	}

	[TestMethod]
	public async Task TransportFailureAndCancellationTokenArePropagated()
	{
		var expected = OperationResult.Failure(ErrorCode.Unauthorized, "Denied.");
		var transport = new RecordingTransport(expected);
		var controller = new HexClientController(transport);
		using var source = new CancellationTokenSource();

		var result = await controller.SendChatAsync("ic", "Hello", source.Token);

		Assert.AreEqual(ErrorCode.Unauthorized, result.Error!.Code);
		Assert.AreEqual(source.Token, transport.LastCancellationToken);
	}

	private sealed class RecordingTransport : IClientCommandTransport
	{
		private readonly OperationResult _result;

		public RecordingTransport(OperationResult? result = null) =>
			_result = result ?? OperationResult.Success();

		public List<ClientCommand> Commands { get; } = new();
		public CancellationToken LastCancellationToken { get; private set; }

		public ValueTask<OperationResult> SendAsync(
			ClientCommand command,
			CancellationToken cancellationToken = default)
		{
			Commands.Add(command);
			LastCancellationToken = cancellationToken;
			return ValueTask.FromResult(_result);
		}
	}
}
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;

using Microsoft.VisualStudio.TestTools.UnitTesting;

[assembly: Parallelize( Scope = ExecutionScope.MethodLevel )]
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
using System.Numerics;
using System.Text;
using HumanoidRetargeter.Cleanup;
using HumanoidRetargeter.Formats.Bvh;
using HumanoidRetargeter.Formats.Fbx;
using HumanoidRetargeter.Skeleton;
using HumanoidRetargeter.Target;
using Xunit;

namespace HumanoidRetargeter.Tests.Solve;

public class PosedLocomotionTests
{
    [Theory]
    [InlineData(1, 500)]
    [InlineData(1, -500)]
    [InlineData(2, 500)]
    [InlineData(2, -500)]
    public void InPlaceCentersClipsWhoseStaticReferenceIsFarFromTheTake(int upAxis, float restTravel)
    {
        var target = Target(upAxis);
        var frames = Convert(Fixture(upAxis, restTravel: restTravel), target, RootMotionMode.InPlace);
        var hips = target.Rig.BoneForRole(HumanoidRetargeter.Mapping.BoneRole.Hips)!.Value;
        var center = frames.Aggregate(Vector3.Zero, (sum, frame) => sum + new Pose(frame).ToWorld(target.Rig.Skeleton)[hips].Pos) / frames.Count;
        var delta = center - target.Rig.Skeleton.RestWorld[hips].Pos;
        if (upAxis == 1) delta.Y = 0; else delta.Z = 0;
        Assert.True(delta.Length() < .01f, $"In-place clip is displaced from the target bind by {delta}.");
    }

    [Theory]
    [InlineData(1, false, 0)]
    [InlineData(1, true, -90)]
    [InlineData(2, false, 90)]
    [InlineData(2, true, 90)]
    public void SerializedRootYawMatchesTargetImportConvention(int upAxis, bool embedsMesh, float degrees)
    {
        var original = Target(upAxis);
        var target = new RetargetTargetSpec
        {
            Rig = original.Rig, UpAxis = original.UpAxis, VmdlScale = 1,
            MeshFilePath = embedsMesh ? "test.fbx" : "",
        };
        var frame = target.Rig.Skeleton.Bones.Select(b => b.RestLocal).ToArray();
        var before = frame.ToArray();
        var serialized = Retargeter.TestHook_CompensateEmbeddedMeshRootYaw(new[] { frame }, target).Single();
        var yaw = Quaternion.CreateFromAxisAngle(upAxis == 1 ? Vector3.UnitY : Vector3.UnitZ, degrees * MathF.PI / 180);
        for (var i = 0; i < frame.Length; i++)
        {
            var root = target.Rig.Skeleton[i].ParentIndex < 0;
            var expectedPosition = root ? Vector3.Transform(before[i].Pos, yaw) : before[i].Pos;
            var expectedRotation = root ? Quaternion.Normalize(yaw * before[i].Rot) : before[i].Rot;
            Assert.True(Vector3.Distance(expectedPosition, serialized[i].Pos) < .0001f);
            Assert.True(MathF.Abs(Quaternion.Dot(expectedRotation, serialized[i].Rot)) > .99999f);
            Assert.Equal(before[i], frame[i]); // Serialization must not mutate solved poses.
        }
    }

    [Theory]
    [InlineData(1)]
    [InlineData(2)]
    public void LeaningSourceRestDoesNotTurnForwardTravelIntoVerticalMotion(int upAxis)
    {
        var target = Target(upAxis);
        var result = Convert(Fixture(upAxis, torsoLean: 70), target, RootMotionMode.InPlace);
        var hip = target.Rig.BoneForRole(HumanoidRetargeter.Mapping.BoneRole.Hips)!.Value;
        var heights = result.Select(frame => Vertical(new Pose(frame).ToWorld(target.Rig.Skeleton)[hip].Pos, upAxis)).ToArray();
        Assert.True(heights.Max() - heights.Min() < .05f,
            $"Horizontal travel changed pelvis height by {heights.Max() - heights.Min()}.");
    }

    [Theory]
    [InlineData(1)]
    [InlineData(2)]
    public void RemovingRootTravelPreservesGroundedHeightsOnLeaningTarget(int upAxis)
    {
        var target = Target(upAxis, torsoLean: 20);
        var bytes = Fixture(upAxis);
        var moving = Convert(bytes, target, RootMotionMode.Off);
        var inPlace = Convert(bytes, target, RootMotionMode.InPlace);
        for (var f = 0; f < moving.Count; f++)
        {
            var before = new Pose(moving[f]).ToWorld(target.Rig.Skeleton);
            var after = new Pose(inPlace[f]).ToWorld(target.Rig.Skeleton);
            for (var b = 0; b < before.Length; b++)
                Assert.True(MathF.Abs(Vertical(before[b].Pos - after[b].Pos, upAxis)) < .005f,
                    $"Frame {f}, bone {b}: root-motion removal changed ground clearance.");
        }
    }

    [Fact]
    public void LiftedFootInExportedRestDoesNotPushPlantedFootBelowGround()
    {
        var target = Target(1);
        var result = Convert(Fixture(1, liftedRest: 35, travel: 0), target, RootMotionMode.Off);
        var toe = target.Rig.BoneForRole(HumanoidRetargeter.Mapping.BoneRole.ToeR)!.Value;
        var floor = target.Rig.Skeleton.RestWorld[toe].Pos.Y;
        foreach (var frame in result)
            Assert.True(new Pose(frame).ToWorld(target.Rig.Skeleton)[toe].Pos.Y >= floor - .1f,
                "A mid-step rest was incorrectly used as the planted-foot height reference.");
    }

    static float Vertical(Vector3 value, int axis) => axis == 1 ? value.Y : value.Z;

    static RetargetTargetSpec Target(int upAxis, float torsoLean = 0)
    {
        var skeleton = FbxImporter.Import(Fixture(upAxis, torsoLean)).Skeleton;
        var (map, _) = Retargeter.ResolveMapping(skeleton);
        return new RetargetTargetSpec
        {
            Rig = TargetRig.FromSkeleton(skeleton, map), VmdlScale = 1,
            UpAxis = upAxis == 1 ? TargetUpAxis.YUpCm : TargetUpAxis.ZUpEngine,
        };
    }

    static List<HumanoidRetargeter.Maths.XForm[]> Convert(byte[] bytes, RetargetTargetSpec target, RootMotionMode mode)
    {
        var result = Retargeter.Convert(new RetargetRequest
        {
            SourceData = bytes, SourceFileName = "posed-locomotion.fbx",
            RootMotion = mode, FootPlantCleanup = true,
        }, target);
        Assert.True(result.Success, string.Join("; ", result.Clips.Select(c => c.Error)));
        return result.Clips[0].SolvedFrames!;
    }

    // A tiny animation-only FBX: no proprietary pack files or optional corpus needed.
    // The static rig can be bent over or caught mid-step; the take moves horizontally.
    static byte[] Fixture(int upAxis, float torsoLean = 0, float liftedRest = 0, float travel = 100, float restTravel = 0)
    {
        var skeleton = BvhImporter.Import(Encoding.UTF8.GetBytes(PosedSkeletonFixture.SyntheticWalkBvh())).Skeleton;
        var objects = new StringBuilder();
        var connections = new StringBuilder();
        foreach (var bone in skeleton.Bones)
        {
            var p = bone.RestLocal.Pos;
            if (bone.ParentIndex < 0) { p.Y = 93; p.Z = restTravel; }
            if (upAxis == 2) p = new Vector3(p.X, -p.Z, p.Y);
            var angle = bone.Name == "mixamorig:Spine" ? torsoLean
                : bone.Name == "mixamorig:RightUpLeg" ? liftedRest : 0;
            objects.AppendLine(FormattableString.Invariant($$"""
                Model: {{bone.Index + 1}}, "Model::{{bone.Name}}", "LimbNode" {
                    Properties70: {
                        P: "Lcl Translation", "Lcl Translation", "", "A",{{p.X}},{{p.Y}},{{p.Z}}
                        P: "Lcl Rotation", "Lcl Rotation", "", "A",{{angle}},0,0
                    }
                }
                """));
            connections.AppendLine($"C: \"OO\",{bone.Index + 1},{bone.ParentIndex + 1}");
        }
        void Curve(int id, int bone, string property, string axis, float end)
        {
            objects.AppendLine(FormattableString.Invariant($$"""
                AnimationCurveNode: {{id}}, "AnimCurveNode::curve", "" { }
                AnimationCurve: {{id + 1}}, "AnimCurve::value", "" {
                    KeyTime: *2 { a: 0,46186158000 }
                    KeyValueFloat: *2 { a: 0,{{end}} }
                }
                """));
            connections.AppendLine($"C: \"OO\",{id},10001\nC: \"OP\",{id},{bone},\"{property}\"\nC: \"OP\",{id + 1},{id},\"d|{axis}\"");
        }
        Curve(10002, 1, "Lcl Translation", upAxis == 1 ? "Z" : "Y", upAxis == 1 ? travel : -travel);
        Curve(10004, skeleton.IndexOf("mixamorig:RightUpLeg") + 1, "Lcl Rotation", "X", 0);
        return Encoding.UTF8.GetBytes(FormattableString.Invariant($$"""
            GlobalSettings: { Properties70: {
                P: "UpAxis", "int", "Integer", "",{{upAxis}}
                P: "UpAxisSign", "int", "Integer", "",1
                P: "UnitScaleFactor", "double", "Number", "",1
            } }
            Objects: {
                {{objects}}
                AnimationStack: 10000, "AnimStack::walk", "" { }
                AnimationLayer: 10001, "AnimLayer::base", "" { }
            }
            Connections: {
                {{connections}}
                C: "OO",10001,10000
            }
            """));
    }
}
using System.Numerics;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Skeleton;
using HumanoidRetargeter.Target;
using HumanoidRetargeter.Tests.Skeleton;
using Xunit;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Tests.Target;

public class SmartPortAdditiveIkTests
{
    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public void RecoilDoesNotInventGoalTranslationFromDifferentArmBindPoses(bool authoredGoalMotion)
    {
        var original = TargetRig.Load(TargetRigGenerator.Generate(File.ReadAllText(SkeletonTests.FixturePath("rig_human_male.json")))).Skeleton;
        const string goal = "support_goal";
        var definitions = original.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : original[b.ParentIndex].Name, b.RestLocal)).ToList();
        definitions.Add(new(goal, "hand_R", XForm.ToLocal(original.RestWorld[original.IndexOf("hand_R")],
            original.RestWorld[original.IndexOf("hand_L")])));
        var source = SkeletonModel.Create(definitions);
        var target = SkeletonModel.Create(source.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : source[b.ParentIndex].Name,
            new XForm(b.RestLocal.Pos * .8f, b.RestLocal.Rot *
                (b.Name.StartsWith("arm_upper_") ? Quaternion.CreateFromAxisAngle(Vector3.UnitY, .6f) : Quaternion.Identity)))).ToArray());
        var rig = new SmartPortRig(source, target, new Dictionary<string, string> { [goal] = "hand_L" });
        var ordinary = new SmartPortRig(source, target);
        var pose = Enumerable.Repeat(XForm.Identity, source.Count).ToArray();
        pose[source.IndexOf("arm_upper_R")].Rot = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, .2f);
        pose[source.IndexOf("arm_lower_L")].Rot = Quaternion.CreateFromAxisAngle(Vector3.UnitX, -.3f);
        pose[source.IndexOf(goal)] = new XForm(authoredGoalMotion ? new Vector3(.2f, -.1f, .3f) : Vector3.Zero,
            Quaternion.CreateFromAxisAngle(Vector3.UnitY, .1f));
        var actual = rig.Transfer(pose, true)[rig.Target.IndexOf(goal)];
        var expected = ordinary.Transfer(pose, true)[ordinary.Target.IndexOf(goal)];
        // Additive goals are local deltas on the already fitted grip, not absolute
        // effector positions computed against a reconstructed rest-pose animation.
        Assert.True(Vector3.Distance(expected.Pos, actual.Pos) < .0001f, $"Expected {expected.Pos}, got {actual.Pos}");
        Assert.True(MathQ.AngleBetween(expected.Rot, actual.Rot) < .001f);
        if (!authoredGoalMotion) Assert.True(actual.Pos.Length() < .0001f);
        else Assert.True(actual.Pos.Length() > .1f); // Preserve authored motion; do not freeze the goal.
    }
}
using System.Numerics;
using HumanoidRetargeter.Cleanup;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Skeleton;
using HumanoidRetargeter.Target;
using HumanoidRetargeter.Tests.Skeleton;
using Xunit;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Tests.Target;

public class SmartPortGripReachTests
{
    [Fact]
    public void SharedGripFitsShorterArmsWithoutStretching()
    {
        var original = TargetRig.Load(TargetRigGenerator.Generate(File.ReadAllText(SkeletonTests.FixturePath("rig_human_male.json")))).Skeleton;
        var definitions = original.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : original[b.ParentIndex].Name, b.RestLocal)).ToList();
        foreach (var side in new[] { "L", "R" }) definitions.Add(new("grip_" + side, "hand_" + side, XForm.Identity));
        definitions.Add(new("support_goal", "hand_R", XForm.Identity));
        var source = SkeletonModel.Create(definitions);
        var target = SkeletonModel.Create(original.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : original[b.ParentIndex].Name,
            new XForm(b.RestLocal.Pos * (b.Name is "arm_lower_L" or "hand_L" ? .55f : 1), b.RestLocal.Rot))).ToArray());
        var pose = source.Bones.Select(b => b.RestLocal).ToArray();
        var left = source.IndexOf("arm_upper_L"); var right = source.IndexOf("arm_upper_R");
        var center = (source.RestWorld[left].Pos + source.RestWorld[right].Pos) * .5f;
        var up = Vector3.Normalize(source.RestWorld[source.IndexOf("head")].Pos - source.RestWorld[source.IndexOf("pelvis")].Pos);
        var lateral = Vector3.Normalize(source.RestWorld[right].Pos - source.RestWorld[left].Pos);
        var forward = Vector3.Normalize(Vector3.Cross(up, lateral));
        var reach = source[source.IndexOf("arm_lower_R")].RestLocal.Pos.Length() + source[source.IndexOf("hand_R")].RestLocal.Pos.Length();
        foreach (var side in new[] { "L", "R" })
        {
            var chain = new LimbChain { Upper = source.IndexOf("arm_upper_" + side), Lower = source.IndexOf("arm_lower_" + side), End = source.IndexOf("hand_" + side) };
            var goal = center + forward * reach * (side == "L" ? .75f : .5f) + lateral * (side == "L" ? -4 : 4) - up * reach * .1f;
            EffectorIk.ApplyGoals(new() { pose }, source, chain, new[] { goal }, lateral, soften: 0);
        }
        var input = new Pose(pose).ToWorld(source);
        pose[source.IndexOf("support_goal")] = XForm.ToLocal(input[source.IndexOf("hand_R")], input[source.IndexOf("hand_L")]);
        var targets = new Dictionary<string, string> { ["support_goal"] = "hand_L" };
        var rig = new SmartPortRig(source, target, targets, new[] { "grip_L", "grip_R" });
        var result = rig.Transfer(pose);
        var world = new Pose(result).ToWorld(rig.Target);
        var error = Vector3.Distance(world[rig.Target.IndexOf("hand_L")].Pos, world[rig.Target.IndexOf("support_goal")].Pos);
        Assert.True(error < .01f, $"Support hand missed its goal by {error}");
        foreach (var side in new[] { "L", "R" })
        foreach (var name in new[] { "arm_upper_", "arm_lower_", "hand_" })
        {
            var index = rig.Target.IndexOf(name + side);
            Assert.True(Vector3.Distance(rig.Target[index].RestLocal.Pos, result[index].Pos) < .0001f);
        }
        var unfitted = new SmartPortRig(source, target, targets);
        var before = new Pose(unfitted.Transfer(pose)).ToWorld(unfitted.Target);
        Assert.True(Vector3.Distance(before[unfitted.Target.IndexOf("hand_L")].Pos, before[unfitted.Target.IndexOf("support_goal")].Pos) > .1f);
        Assert.All(rig.Transfer(Enumerable.Repeat(XForm.Identity, source.Count).ToArray(), true), delta =>
        {
            Assert.True(delta.Pos.Length() < .0001f);
            Assert.True(MathQ.AngleBetween(delta.Rot, Quaternion.Identity) < .001f);
        });
    }
}
using System.Numerics;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Skeleton;
using HumanoidRetargeter.Target;
using HumanoidRetargeter.Tests.Skeleton;
using Xunit;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Tests.Target;

public class SmartPortHandSocketTests
{
    [Theory]
    [InlineData(0)]
    [InlineData(2)]
    public void CopiedSocketFitsTheTargetHandNotItsLegProportions(int helperDepth)
    {
        var original = TargetRig.Load(TargetRigGenerator.Generate(File.ReadAllText(SkeletonTests.FixturePath("rig_human_male.json")))).Skeleton;
        var names = new[] { "finger_index_0_R", "finger_middle_0_R", "finger_ring_0_R" };
        Vector3 Center(SkeletonModel sk, XForm[] world) => names.Select(n => world[sk.IndexOf(n)].Pos).Aggregate(Vector3.Zero, (a,b) => a+b) / names.Length;
        const string socket = "new_hand_socket";
        var bind = original.RestWorld.ToArray();
        var hand = original.IndexOf("hand_R");
        var definitions = original.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : original[b.ParentIndex].Name, b.RestLocal)).ToList();
        definitions.Add(new(socket, "hand_R", new XForm(bind[hand].Inverse().TransformPoint(Center(original, bind)), Quaternion.Identity)));
        var attachment = socket;
        for (var i = 0; i < helperDepth; i++)
        {
            var child = "nested_socket_" + i;
            definitions.Add(new(child, attachment, XForm.Identity));
            attachment = child;
        }
        var source = SkeletonModel.Create(definitions);
        var target = SkeletonModel.Create(original.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : original[b.ParentIndex].Name,
            new XForm(b.RestLocal.Pos * (b.Name.StartsWith("finger_") ? .5f : 1f), b.RestLocal.Rot))).ToArray());
        var rig = new SmartPortRig(source, target, attachmentBones: new[] { attachment, socket });
        var pose = source.Bones.Select(b => b.RestLocal).ToArray();
        var result = rig.Transfer(pose);
        var world = new Pose(result).ToWorld(rig.Target);
        var unfitted = new SmartPortRig(source, target);
        var before = new Pose(unfitted.Transfer(pose)).ToWorld(unfitted.Target);
        Assert.True(Vector3.Distance(Center(unfitted.Target, before), before[unfitted.Target.IndexOf(socket)].Pos) > .1f);
        Assert.True(Vector3.Distance(Center(rig.Target, world), world[rig.Target.IndexOf(socket)].Pos) < .001f,
            $"Expected {Center(rig.Target, world)}, got {world[rig.Target.IndexOf(socket)].Pos}");
        Assert.True(Vector3.Distance(Center(rig.Target, world), world[rig.Target.IndexOf(attachment)].Pos) < .001f);
        foreach (var bone in target.Bones)
            Assert.Equal(bone.RestLocal, rig.Target[rig.Target.IndexOf(bone.Name)].RestLocal);
        Assert.All(rig.Transfer(Enumerable.Repeat(XForm.Identity, source.Count).ToArray(), true), delta =>
        {
            Assert.True(delta.Pos.Length() < .0001f);
            Assert.True(MathQ.AngleBetween(delta.Rot, Quaternion.Identity) < .001f);
        });
    }

    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public void ExistingFittedSocketIsNotReplacedByHandGeometry(bool nestedSource)
    {
        var original = TargetRig.Load(TargetRigGenerator.Generate(File.ReadAllText(SkeletonTests.FixturePath("rig_human_male.json")))).Skeleton;
        var definitions = original.Bones.Select(b => new BoneDefinition(b.Name,
            nestedSource && b.Name == "hold_R" ? "weapon_pivot" : b.ParentIndex < 0 ? null : original[b.ParentIndex].Name, b.RestLocal)).ToList();
        if (nestedSource) definitions.Add(new("weapon_pivot", "hand_R", XForm.Identity));
        var source = SkeletonModel.Create(definitions);
        var target = SkeletonModel.Create(original.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : original[b.ParentIndex].Name,
            new XForm(b.RestLocal.Pos * (b.Name.StartsWith("finger_") ? .5f : 1f)
                + (b.Name == "hold_R" ? new Vector3(.2f, .1f, .3f) : Vector3.Zero), b.RestLocal.Rot))).ToArray());
        var fitted = new SmartPortRig(source, target, attachmentBones: new[] { "hold_R" });
        var ordinary = new SmartPortRig(source, target);
        var pose = source.Bones.Select(b => b.RestLocal).ToArray();
        Assert.Equal(ordinary.Transfer(pose), fitted.Transfer(pose));
    }
}
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Text.Json;
using Editor;
using Sandbox;

// A custom rig without stock attachments must receive calibrated copied sockets.
// Run only in an explicitly opted-in isolated project, never a user's active editor.
public static class SmartPortCopiedSocketEngineTest
{
    static bool started;
    [EditorEvent.Frame]
    public static void Tick()
    {
        var root = Environment.GetEnvironmentVariable("HR_SMART_PORT_COPIED_SOCKET_PROJECT");
        if (started || Project.Current is null || string.IsNullOrEmpty(root)
            || !string.Equals(Path.GetFullPath(Project.Current.GetRootPath()).TrimEnd('/', '\\'), Path.GetFullPath(root).TrimEnd('/', '\\'), StringComparison.OrdinalIgnoreCase)
            || !AssetSystem.All.Any()) return;
        started = true;
        _ = Run(root);
    }
    static async Task Run(string root)
    {
        var data = new Dictionary<string, object>();
        void Save() => File.WriteAllText(Path.Combine(root, "copied-socket-result.json"),
            JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }));
        try
        {
            data["started"] = DateTime.UtcNow; Save();
            var source = AssetSystem.FindByPath("models/citizen/citizen.vmdl") ?? throw new Exception("Missing Citizen source");
            var target = AssetSystem.FindByPath("humanoid_retargeter_smoke/customfbx/catgirl_2_preview_bind_9e13ad1f.vmdl") ?? throw new Exception("Missing Catgirl target fixture");
            var type = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType("HumanoidRetargeter.Editor.SmartPortModels")).First(t => t != null);
            var name = "copied_socket_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss");
            Action<string> progress = s => { data["stage"] = s; Save(); };
            var task = (Task)type.GetMethod("CreateAsync", BindingFlags.Static | BindingFlags.NonPublic)
                .Invoke(null, new object[] { source, target, "smart_port", name, progress, CancellationToken.None });
            await task;
            var result = task.GetType().GetProperty("Result").GetValue(task);
            data["compiled"] = result.GetType().GetProperty("Compiled").GetValue(result);
            data["errors"] = result.GetType().GetProperty("Errors").GetValue(result);
            data["model"] = "smart_port/" + name + ".vmdl"; Save();
            if (!(bool)data["compiled"]) throw new Exception("Compile failed");
            var session = SceneEditorSession.CreateDefault(); session.MakeActive();
            using (session.Scene.Push())
                foreach (var path in new[] { source.Path, (string)data["model"] })
                {
                    var go = session.Scene.CreateObject(); go.Name = path;
                    var actor = go.Components.Create<SkinnedModelRenderer>();
                    actor.Model = Model.Load(path); actor.UseAnimGraph = true;
                }
            EditorScene.Play(false, session); await Task.Delay(1500);
            if (!Game.IsPlaying || Game.ActiveScene is null || Game.ActiveScene.IsEditor) throw new Exception("Did not enter play mode");
            var actors = Game.ActiveScene.GetAllComponents<SkinnedModelRenderer>().ToArray();
            var observations = new List<object>();
            const int hold = 2; // Source graph's rifle hold type.
            foreach (var weight in new[] { 0f, 1f })
            foreach (var pitch in new[] { 0f, -25f, 25f })
            {
                for (var settle = 0; settle < 50; settle++)
                {
                    foreach (var actor in actors)
                    {
                        actor.Set("b_grounded", true); actor.Set("holdtype", hold); actor.Set("weapon_pose", 0);
                        actor.Set("aim_body", Rotation.From(pitch, 0, 0).Forward); actor.Set("aim_body_weight", weight);
                        actor.Set("aim_head", Rotation.From(pitch, 0, 0).Forward); actor.Set("aim_head_weight", 1f);
                    }
                    await Task.Delay(100);
                }
                var sourceActor = actors.Single(a => a.Model.Name == source.Path);
                var targetActor = actors.Single(a => a.Model.Name == (string)data["model"]);
                var sourceGrip = sourceActor.SceneModel.GetAttachment("hold_R", true).Value;
                var targetGrip = targetActor.SceneModel.GetAttachment("hold_R", true).Value;
                var a = sourceGrip.Rotation; var b = targetGrip.Rotation;
                var error = 2 * MathF.Acos(Math.Clamp(MathF.Abs(a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w), 0, 1)) * 180 / MathF.PI;
                if (!float.IsFinite(error) || error > 3) throw new Exception($"Rifle attachment diverged {error} degrees (aim weight {weight}, pitch {pitch})");
                foreach (var actor in actors)
                {
                    var att = actor.SceneModel.GetAttachment("hold_R", true) ?? throw new Exception("Missing grip");
                    observations.Add(new { model = actor.Model.Name, hold, weight, pitch, error,
                        rotation = att.Rotation.Angles().ToString(), forward = att.Rotation.Forward.ToString(), position = att.Position.ToString() });
                }
            }
            data["observations"] = observations; data["passed"] = true;
        }
        catch (Exception e) { data["error"] = e.ToString(); data["passed"] = false; }
        finally
        {
            data["completed"] = true; Save();
            if (Game.IsPlaying) EditorScene.Stop();
            await Task.Delay(1000); EditorUtility.Quit(true);
        }
    }
}
using HumanoidRetargeter.Editor;
using HumanoidRetargeterVrf;
using HumanoidRetargeterVrf.ResourceTypes;
using Xunit;

namespace SmartPort.Parser.Tests;

public class SmartPortHandSocketMetadataTests
{
    [CompiledFixtureFact]
    public void AttachmentFittingUsesCompiledParentBones()
    {
        var path = Path.Combine(Environment.GetEnvironmentVariable("HR_SMART_PORT_FIXTURE")!, "models/player/human/frank_mp.vmdl_c");
        var names = SmartPortAttachments.BoneNames(path);
        using var resource = new Resource();
        resource.Read(path);
        var model = Assert.IsType<Model>(resource.DataBlock);
        Assert.Contains("hold_R", names);
        Assert.Contains("hold_L", names);
        Assert.Equal(names.Length, names.Distinct().Count());
        Assert.All(names.Where(n => !string.IsNullOrEmpty(n)), n => Assert.Contains(model.Skeleton.Bones, b => b.Name == n));
    }
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	public static Sandbox.TestAppSystem AppSystem;

	[AssemblyInitialize]
	public static void AssemblyInitialize( TestContext context )
	{
		AppSystem = new Sandbox.TestAppSystem();
		AppSystem.Init();
	}

	[AssemblyCleanup]
	public static void AssemblyCleanup()
	{
		AppSystem.Shutdown();
	}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace WackyLib.Tests;

[TestClass]
public class TestInit
{
	private static Sandbox.TestAppSystem s_appSystem;
	
	[AssemblyInitialize]
	public static void AssemblyInitialize( TestContext context )
	{
		s_appSystem = new Sandbox.TestAppSystem();
		s_appSystem.Init();
	}
	
	[AssemblyCleanup]
	public static void AssemblyCleanup()
	{
		s_appSystem.Shutdown();
	}
}
using Sandbox;

[TestClass]
public class SyncToolYamlRendererTests
{
	[TestMethod]
	public void EmptyInputProducesEmptyOutput()
	{
		Assert.AreEqual( "", SyncToolYamlRenderer.RenderFromJson( null ) );
		Assert.AreEqual( "", SyncToolYamlRenderer.RenderFromJson( "" ) );
	}

	[TestMethod]
	public void EmptyObjectAndArrayRenderAsFlowStyle()
	{
		Assert.AreEqual( "{}\n", SyncToolYamlRenderer.RenderFromJson( "{}" ) );
		Assert.AreEqual( "[]\n", SyncToolYamlRenderer.RenderFromJson( "[]" ) );
	}

	[TestMethod]
	public void InvalidJsonReturnsInputUnchanged()
	{
		var notJson = "not: real: json: ::";
		Assert.AreEqual( notJson, SyncToolYamlRenderer.RenderFromJson( notJson ) );
	}

	[TestMethod]
	public void TopLevelKeysAreSortedAlphabetically()
	{
		var json = "{\"zeta\":1,\"alpha\":2,\"mu\":3}";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		Assert.AreEqual( "alpha: 2\nmu: 3\nzeta: 1\n", yaml );
	}

	[TestMethod]
	public void NestedObjectsAreSortedRecursively()
	{
		var json = "{\"outer\":{\"zeta\":1,\"alpha\":2}}";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		Assert.AreEqual( "outer:\n  alpha: 2\n  zeta: 1\n", yaml );
	}

	[TestMethod]
	public void DifferentKeyOrdersProduceIdenticalOutput()
	{
		var a = "{\"slug\":\"hello\",\"method\":\"POST\",\"enabled\":true}";
		var b = "{\"enabled\":true,\"method\":\"POST\",\"slug\":\"hello\"}";

		Assert.AreEqual(
			SyncToolYamlRenderer.RenderFromJson( a ),
			SyncToolYamlRenderer.RenderFromJson( b )
		);
	}

	[TestMethod]
	public void OutputUsesYamlSyntaxNotJsonSyntax()
	{
		var json = "{\"name\":\"hooked\",\"enabled\":true,\"max\":42}";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		// Smoke check: the rendered text must look like YAML, not JSON.
		// This is the regression we're guarding: prior to the fix the diff
		// view rendered structured data as JSON.
		StringAssert.DoesNotMatch( yaml, new System.Text.RegularExpressions.Regex( @"^\s*\{" ) );
		StringAssert.Contains( yaml, "enabled: true" );
		StringAssert.Contains( yaml, "max: 42" );
		StringAssert.Contains( yaml, "name: \"hooked\"" );
	}

	[TestMethod]
	public void StringValuesAreQuotedAndEscapedSafely()
	{
		var json = "{\"text\":\"a:b\\nc\"}";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		// Strings go through JSON quoting which is also valid YAML.
		// The colon/newline must not leak as YAML structure.
		Assert.AreEqual( "text: \"a:b\\nc\"\n", yaml );
	}

	[TestMethod]
	public void BooleanAndNullAreUnquoted()
	{
		var json = "{\"a\":true,\"b\":false,\"c\":null}";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		Assert.AreEqual( "a: true\nb: false\nc: null\n", yaml );
	}

	[TestMethod]
	public void IntegerAndDoubleAreUnquoted()
	{
		var json = "{\"i\":7,\"d\":1.5}";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		Assert.AreEqual( "d: 1.5\ni: 7\n", yaml );
	}

	[TestMethod]
	public void ArraysOfObjectsRenderAsBlockSequence()
	{
		var json = "{\"steps\":[{\"name\":\"first\",\"id\":1},{\"name\":\"second\",\"id\":2}]}";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		var expected =
			"steps:\n" +
			"  -\n" +
			"    id: 1\n" +
			"    name: \"first\"\n" +
			"  -\n" +
			"    id: 2\n" +
			"    name: \"second\"\n";

		Assert.AreEqual( expected, yaml );
	}

	[TestMethod]
	public void ArraysOfScalarsRenderAsBlockSequence()
	{
		var json = "{\"tags\":[\"a\",\"b\",\"c\"]}";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		Assert.AreEqual( "tags:\n  - \"a\"\n  - \"b\"\n  - \"c\"\n", yaml );
	}

	[TestMethod]
	public void TopLevelArrayRenders()
	{
		var json = "[1,2,3]";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		Assert.AreEqual( "- 1\n- 2\n- 3\n", yaml );
	}

	[TestMethod]
	public void KeysWithNonIdentifierCharactersAreQuoted()
	{
		var json = "{\"weird key\":1,\"x:y\":2}";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		// Bare YAML keys must not contain spaces or colons, so the renderer
		// quotes them. Sort order is by raw key (Ordinal).
		StringAssert.Contains( yaml, "\"weird key\": 1" );
		StringAssert.Contains( yaml, "\"x:y\": 2" );
	}

	[TestMethod]
	public void EmptyNestedObjectAndArrayUseFlowStyle()
	{
		var json = "{\"obj\":{},\"arr\":[]}";
		var yaml = SyncToolYamlRenderer.RenderFromJson( json );

		Assert.AreEqual( "arr: []\nobj: {}\n", yaml );
	}
}
#nullable enable

using Hexagon.V2.Application;
using Hexagon.V2.Domain;
using Hexagon.V2.Kernel;
using Hexagon.V2.Kernel.Events;
using Hexagon.V2.Persistence;

namespace Hexagon.V2.Tests.Application;

[TestClass]
public sealed class SceneAndAggregateMutationTests
{
	[TestMethod]
	public async Task SceneStateUsesRegisteredTypesAndPublishesOnlyCommittedReplacement()
	{
		await using var environment = await ApplicationServiceTestEnvironment.CreateAsync();
		var service = new SceneEntityStateService(
			environment.Repositories,
			environment.Schema,
			ApplicationServiceTestEnvironment.AllowPolicy<SceneEntityStateMutationContext>());
		var sceneEntityId = SceneEntityId.New();
		var actor = new AccountId(9101);
		var initial = ApplicationServiceTestEnvironment.StatePayload("closed");

		var ensured = await service.EnsureAsync(sceneEntityId, "door", initial);
		var replaced = await service.ReplaceStateAsync(
			actor, null, sceneEntityId, ApplicationServiceTestEnvironment.StatePayload("open"));
		var incompatible = await service.ReplaceStateAsync(
			actor, null, sceneEntityId, ApplicationServiceTestEnvironment.StatePayload("future", version: 2));
		environment.Provider.FailNextCommit();
		var failedCommit = await service.ReplaceStateAsync(
			actor, null, sceneEntityId, ApplicationServiceTestEnvironment.StatePayload("unpublished"));

		Assert.IsTrue(ensured.Succeeded, ensured.Error?.Message);
		Assert.IsTrue(replaced.Succeeded, replaced.Error?.Message);
		Assert.AreEqual(ErrorCode.PersistedTypeInvalid, incompatible.Error!.Code);
		Assert.AreEqual(ErrorCode.InternalError, failedCommit.Error!.Code);
		var stored = service.Find(sceneEntityId)!;
		Assert.AreEqual("open", stored.State.Data.GetProperty("name").GetString());
		Assert.AreEqual(ApplicationServiceTestEnvironment.StateTypeId, stored.State.TypeId.Value);
		Assert.AreEqual(1, stored.State.TypeVersion);
	}

	[TestMethod]
	public async Task TouchLastPlayedReturnsOnlyProviderIssuedDurableReceipt()
	{
		await using var environment = await ApplicationServiceTestEnvironment.CreateAsync();
		var account = new AccountId( 9103 );
		var character = ApplicationServiceTestEnvironment.Character( account, 0 ) with
		{
			LastPlayedAt = DateTimeOffset.UnixEpoch
		};
		await environment.SeedAsync( unitOfWork =>
			unitOfWork.Create( environment.Repositories.Characters, DomainKeys.Character( character.Id ), character ) );
		var service = new AggregateMutationService(
			environment.Repositories,
			environment.Schema,
			ApplicationServiceTestEnvironment.AllowPolicy<CharacterMutationContext>(),
			ApplicationServiceTestEnvironment.AllowPolicy<ItemTraitMutationContext>() );

		var timestamp = DateTimeOffset.UnixEpoch.AddMinutes( 1 );
		var touched = await service.TouchLastPlayedAsync( account, character.Id, timestamp );

		Assert.IsTrue( touched.Succeeded, touched.Error?.Message );
		Assert.AreEqual( CharacterMutationKind.TouchLastPlayed, touched.Value.Kind );
		Assert.AreEqual( character.LastPlayedAt, touched.Value.Before.LastPlayedAt );
		Assert.AreEqual( timestamp, touched.Value.After.LastPlayedAt );
		Assert.IsGreaterThan( 0L, touched.Value.Commit.Sequence );
		Assert.IsTrue( touched.Value.Commit.Documents.Any( document =>
			document.Address.Collection == DomainCollections.Characters &&
			document.Address.Key == DomainKeys.Character( character.Id ) ) );

		environment.Provider.FailNextCommit();
		var failed = await service.TouchLastPlayedAsync(
			account, character.Id, timestamp.AddMinutes( 1 ) );

		Assert.IsTrue( failed.Failed );
		Assert.AreEqual( ErrorCode.InternalError, failed.Error!.Code );
		Assert.AreEqual( timestamp, environment.Repositories.Characters.Find(
			DomainKeys.Character( character.Id ) )!.Value.LastPlayedAt );
	}

	[TestMethod]
	public async Task PreparedTouchStagesIntoCombinedCommitAndPublishesOnlyOnMatchingCompletion()
	{
		await using var environment = await ApplicationServiceTestEnvironment.CreateAsync();
		var account = new AccountId( 9104 );
		var character = ApplicationServiceTestEnvironment.Character( account, 0 ) with
		{
			LastPlayedAt = DateTimeOffset.UnixEpoch
		};
		var item = ApplicationServiceTestEnvironment.Item();
		await environment.SeedAsync( unitOfWork =>
		{
			unitOfWork.Create(
				environment.Repositories.Characters, DomainKeys.Character( character.Id ), character );
			unitOfWork.Create( environment.Repositories.Items, DomainKeys.Item( item.Id ), item );
		} );
		var events = new RecordingCharacterChangedHandler();
		var service = new AggregateMutationService(
			environment.Repositories,
			environment.Schema,
			ApplicationServiceTestEnvironment.AllowPolicy<CharacterMutationContext>(),
			ApplicationServiceTestEnvironment.AllowPolicy<ItemTraitMutationContext>(),
			new PostCommitEventBus<CharacterChangedEvent>( new[]
			{
				new EventHandlerRegistration<CharacterChangedEvent>( "record", events )
			} ) );

		var timestamp = DateTimeOffset.UnixEpoch.AddMinutes( 1 );
		var prepared = service.PrepareTouchLastPlayed( account, character.Id, timestamp );
		Assert.IsTrue( prepared.Succeeded, prepared.Error?.Message );
		var unitOfWork = environment.Repositories.Provider.BeginUnitOfWork();
		Assert.IsTrue( service.StageTouchLastPlayed( unitOfWork, prepared.Value ).Succeeded );
		var itemDocument = environment.Repositories.Items.Find( DomainKeys.Item( item.Id ) )!;
		var itemEditor = unitOfWork.Edit( environment.Repositories.Items, itemDocument )!;
		itemEditor.Replace( itemEditor.Value with { Revision = 1 } );
		unitOfWork.Save( itemEditor );
		var committed = await unitOfWork.CommitAsync();
		await unitOfWork.DisposeAsync();
		Assert.IsTrue( committed.Succeeded, committed.Error?.Message );
		Assert.HasCount( 2, committed.Value!.Documents );
		Assert.IsEmpty( events.Events );

		var premature = service.CompleteTouchLastPlayed(
			prepared.Value, new CommitReceipt( committed.Value.Sequence, Array.Empty<CommittedDocumentVersion>() ) );
		Assert.AreEqual( ErrorCode.InvariantViolation, premature.Error!.Code );
		Assert.IsEmpty( events.Events );

		var completed = service.CompleteTouchLastPlayed( prepared.Value, committed.Value );
		Assert.IsTrue( completed.Succeeded, completed.Error?.Message );
		Assert.AreSame( committed.Value, completed.Value.Commit );
		Assert.AreEqual( timestamp, completed.Value.After.LastPlayedAt );
		Assert.HasCount( 1, events.Events );
		Assert.AreEqual( committed.Value.Sequence, events.Events[0].CommitSequence );
	}

	[TestMethod]
	public async Task CompletionSucceedsFromTheReceiptAloneAfterAnInterleavedCommit()
	{
		await using var environment = await ApplicationServiceTestEnvironment.CreateAsync();
		var account = new AccountId( 9106 );
		var character = ApplicationServiceTestEnvironment.Character( account, 0 ) with
		{
			LastPlayedAt = DateTimeOffset.UnixEpoch
		};
		await environment.SeedAsync( unitOfWork =>
			unitOfWork.Create(
				environment.Repositories.Characters, DomainKeys.Character( character.Id ), character ) );
		var events = new RecordingCharacterChangedHandler();
		var service = new AggregateMutationService(
			environment.Repositories,
			environment.Schema,
			ApplicationServiceTestEnvironment.AllowPolicy<CharacterMutationContext>(),
			ApplicationServiceTestEnvironment.AllowPolicy<ItemTraitMutationContext>(),
			new PostCommitEventBus<CharacterChangedEvent>( new[]
			{
				new EventHandlerRegistration<CharacterChangedEvent>( "record", events )
			} ) );

		var timestamp = DateTimeOffset.UnixEpoch.AddMinutes( 1 );
		var prepared = service.PrepareTouchLastPlayed( account, character.Id, timestamp );
		Assert.IsTrue( prepared.Succeeded, prepared.Error?.Message );
		var unitOfWork = environment.Repositories.Provider.BeginUnitOfWork();
		Assert.IsTrue( service.StageTouchLastPlayed( unitOfWork, prepared.Value ).Succeeded );
		var committed = await unitOfWork.CommitAsync();
		await unitOfWork.DisposeAsync();
		Assert.IsTrue( committed.Succeeded, committed.Error?.Message );

		var current = environment.Repositories.Characters.Find( DomainKeys.Character( character.Id ) )!;
		var competingUnit = environment.Repositories.Provider.BeginUnitOfWork();
		var competingEditor = competingUnit.Edit( environment.Repositories.Characters, current )!;
		competingEditor.Replace( competingEditor.Value with
		{
			LastPlayedAt = DateTimeOffset.UnixEpoch.AddMinutes( 2 )
		} );
		competingUnit.Save( competingEditor );
		Assert.IsTrue( (await competingUnit.CommitAsync()).Succeeded );
		await competingUnit.DisposeAsync();

		var completed = service.CompleteTouchLastPlayed( prepared.Value, committed.Value! );

		Assert.IsTrue( completed.Succeeded,
			"A durably committed mutation must complete from its receipt even after an interleaved commit." );
		Assert.AreEqual( timestamp, completed.Value.After.LastPlayedAt );
		Assert.HasCount( 1, events.Events );
	}

	[TestMethod]
	public async Task PreparedTouchCommitFailureAndRevisionConflictEmitNoEvent()
	{
		await using var environment = await ApplicationServiceTestEnvironment.CreateAsync();
		var account = new AccountId( 9105 );
		var character = ApplicationServiceTestEnvironment.Character( account, 0 ) with
		{
			LastPlayedAt = DateTimeOffset.UnixEpoch
		};
		await environment.SeedAsync( unitOfWork =>
			unitOfWork.Create(
				environment.Repositories.Characters, DomainKeys.Character( character.Id ), character ) );
		var events = new RecordingCharacterChangedHandler();
		var service = new AggregateMutationService(
			environment.Repositories,
			environment.Schema,
			ApplicationServiceTestEnvironment.AllowPolicy<CharacterMutationContext>(),
			ApplicationServiceTestEnvironment.AllowPolicy<ItemTraitMutationContext>(),
			new PostCommitEventBus<CharacterChangedEvent>( new[]
			{
				new EventHandlerRegistration<CharacterChangedEvent>( "record", events )
			} ) );

		var failedPlan = service.PrepareTouchLastPlayed(
			account, character.Id, DateTimeOffset.UnixEpoch.AddMinutes( 1 ) );
		var failedUnit = environment.Repositories.Provider.BeginUnitOfWork();
		Assert.IsTrue( service.StageTouchLastPlayed( failedUnit, failedPlan.Value ).Succeeded );
		environment.Provider.FailNextCommit();
		var failedCommit = await failedUnit.CommitAsync();
		await failedUnit.DisposeAsync();
		Assert.IsFalse( failedCommit.Succeeded );
		Assert.IsEmpty( events.Events );
		Assert.AreEqual( DateTimeOffset.UnixEpoch, environment.Repositories.Characters.Find(
			DomainKeys.Character( character.Id ) )!.Value.LastPlayedAt );

		var conflictedPlan = service.PrepareTouchLastPlayed(
			account, character.Id, DateTimeOffset.UnixEpoch.AddMinutes( 2 ) );
		var staleUnit = environment.Repositories.Provider.BeginUnitOfWork();
		Assert.IsTrue( service.StageTouchLastPlayed( staleUnit, conflictedPlan.Value ).Succeeded );
		var current = environment.Repositories.Characters.Find( DomainKeys.Character( character.Id ) )!;
		var competingUnit = environment.Repositories.Provider.BeginUnitOfWork();
		var competingEditor = competingUnit.Edit( environment.Repositories.Characters, current )!;
		competingEditor.Replace( competingEditor.Value with
		{
			LastPlayedAt = DateTimeOffset.UnixEpoch.AddMinutes( 3 )
		} );
		competingUnit.Save( competingEditor );
		var competingCommit = await competingUnit.CommitAsync();
		await competingUnit.DisposeAsync();
		Assert.IsTrue( competingCommit.Succeeded, competingCommit.Error?.Message );
		var conflict = await staleUnit.CommitAsync();
		await staleUnit.DisposeAsync();
		Assert.IsFalse( conflict.Succeeded );
		Assert.AreEqual( PersistenceErrorCode.RevisionConflict, conflict.Error!.Code );
		Assert.IsEmpty( events.Events );

		// Completion validates from the receipt alone; a receipt that does not contain the
		// prepared revision is rejected without publishing.
		var wrongCompletion = service.CompleteTouchLastPlayed(
			conflictedPlan.Value,
			new CommitReceipt( competingCommit.Value!.Sequence, Array.Empty<CommittedDocumentVersion>() ) );
		Assert.AreEqual( ErrorCode.InvariantViolation, wrongCompletion.Error!.Code );
		Assert.IsEmpty( events.Events );
	}

	[TestMethod]
	public async Task AggregateMutationFailuresLeaveTimestampBanBalanceStateAndTraitsUntouched()
	{
		await using var environment = await ApplicationServiceTestEnvironment.CreateAsync();
		var account = new AccountId(9102);
		var character = ApplicationServiceTestEnvironment.Character(account, 0) with
		{
			Balance = 10,
			LastPlayedAt = DateTimeOffset.UnixEpoch.AddHours(2)
		};
		var item = ApplicationServiceTestEnvironment.Item() with
		{
			Traits = new Dictionary<string, TypedPayload>(StringComparer.Ordinal)
			{
				["state"] = ApplicationServiceTestEnvironment.StatePayload("old")
			}
		};
		await environment.SeedAsync(unitOfWork =>
		{
			unitOfWork.Create(environment.Repositories.Characters, DomainKeys.Character(character.Id), character);
			unitOfWork.Create(environment.Repositories.Items, DomainKeys.Item(item.Id), item);
		});
		var service = new AggregateMutationService(
			environment.Repositories,
			environment.Schema,
			ApplicationServiceTestEnvironment.AllowPolicy<CharacterMutationContext>(),
			ApplicationServiceTestEnvironment.AllowPolicy<ItemTraitMutationContext>());

		var oldTimestamp = await service.TouchLastPlayedAsync(
			account, character.Id, DateTimeOffset.UnixEpoch);
		var nonUtcBan = await service.SetBanAsync(
			account,
			character.Id,
			true,
			new DateTimeOffset(2030, 1, 1, 0, 0, 0, TimeSpan.FromHours(1)));
		var insufficientFunds = await service.DebitAsync(account, character.Id, 11);
		var unknownState = await service.ReplaceSchemaStateAsync(
			account,
			character.Id,
			ApplicationServiceTestEnvironment.StatePayload(typeId: "unknown.state"));
		var unknownTrait = await service.ReplaceTraitAsync(
			account,
			character.Id,
			item.Id,
			"state",
			ApplicationServiceTestEnvironment.StatePayload(typeId: "unknown.trait"));

		environment.Provider.FailNextCommit();
		var failedCredit = await service.CreditAsync(account, character.Id, 1);
		environment.Provider.FailNextCommit();
		var failedTraitCommit = await service.ReplaceTraitAsync(
			account,
			character.Id,
			item.Id,
			"state",
			ApplicationServiceTestEnvironment.StatePayload("new"));

		Assert.AreEqual(ErrorCode.InvalidArgument, oldTimestamp.Error!.Code);
		Assert.AreEqual(ErrorCode.InvalidArgument, nonUtcBan.Error!.Code);
		Assert.AreEqual(ErrorCode.Conflict, insufficientFunds.Error!.Code);
		Assert.AreEqual(ErrorCode.PersistedTypeInvalid, unknownState.Error!.Code);
		Assert.AreEqual(ErrorCode.PersistedTypeInvalid, unknownTrait.Error!.Code);
		Assert.AreEqual(ErrorCode.InternalError, failedCredit.Error!.Code);
		Assert.AreEqual(ErrorCode.InternalError, failedTraitCommit.Error!.Code);

		var storedCharacter = environment.Repositories.Characters.Find(DomainKeys.Character(character.Id))!.Value;
		var storedItem = environment.Repositories.Items.Find(DomainKeys.Item(item.Id))!.Value;
		Assert.AreEqual(character.LastPlayedAt, storedCharacter.LastPlayedAt);
		Assert.IsFalse(storedCharacter.IsBanned);
		Assert.IsNull(storedCharacter.BanExpiresAt);
		Assert.AreEqual(10L, storedCharacter.Balance);
		Assert.AreEqual("citizen", storedCharacter.SchemaState.Data.GetProperty("name").GetString());
		Assert.AreEqual("old", storedItem.Traits["state"].Data.GetProperty("name").GetString());
	}

	private sealed class RecordingCharacterChangedHandler : IEventHandler<CharacterChangedEvent>
	{
		public List<CharacterChangedEvent> Events { get; } = new();
		public void Handle( CharacterChangedEvent @event ) => Events.Add( @event );
	}
}
using Hexagon.V2.Application;
using Hexagon.V2.Domain;

namespace Hexagon.V2.Tests.Application;

[TestClass]
public sealed class SceneIdentityValidatorTests
{
	[TestMethod]
	public void ProvenanceClassifier_TrustsOnlyCapturedNonNetworkComponents()
	{
		var authored = Guid.NewGuid();
		var laterRuntime = Guid.NewGuid();
		var classifier = new SceneIdentityProvenanceClassifier();

		Assert.AreEqual(
			SceneIdentityProvenance.RuntimeOrNetwork,
			classifier.Classify( authored, hasActiveNetworkRoot: false ) );

		classifier.CaptureAuthoredSnapshot( new[] { authored } );

		Assert.IsTrue( classifier.HasAuthoredSnapshot );
		Assert.AreEqual( 1, classifier.AuthoredComponentCount );
		Assert.AreEqual(
			SceneIdentityProvenance.EditorAuthored,
			classifier.Classify( authored, hasActiveNetworkRoot: false ) );
		Assert.AreEqual(
			SceneIdentityProvenance.RuntimeOrNetwork,
			classifier.Classify( authored, hasActiveNetworkRoot: true ) );
		Assert.AreEqual(
			SceneIdentityProvenance.RuntimeOrNetwork,
			classifier.Classify( laterRuntime, hasActiveNetworkRoot: false ) );
	}

	[TestMethod]
	public void ProvenanceClassifier_NewSceneSnapshotReplacesPriorAuthority()
	{
		var firstScene = Guid.NewGuid();
		var secondScene = Guid.NewGuid();
		var classifier = new SceneIdentityProvenanceClassifier();
		classifier.CaptureAuthoredSnapshot( new[] { firstScene } );

		classifier.CaptureAuthoredSnapshot( new[] { secondScene } );

		Assert.AreEqual(
			SceneIdentityProvenance.RuntimeOrNetwork,
			classifier.Classify( firstScene, hasActiveNetworkRoot: false ) );
		Assert.AreEqual(
			SceneIdentityProvenance.EditorAuthored,
			classifier.Classify( secondScene, hasActiveNetworkRoot: false ) );
	}

	[TestMethod]
	public void EditorRepair_FillsMissingAndRepairsOnlyLaterDuplicate()
	{
		var duplicate = new SceneEntityId( Guid.Parse( "11111111-1111-1111-1111-111111111111" ) );
		var generated = new Queue<SceneEntityId>( new[]
		{
			new SceneEntityId( Guid.Parse( "22222222-2222-2222-2222-222222222222" ) ),
			new SceneEntityId( Guid.Parse( "33333333-3333-3333-3333-333333333333" ) )
		} );
		var result = SceneIdentityValidator.RepairForEditor( new[]
		{
			new SceneIdentityCandidate( "a", duplicate ),
			new SceneIdentityCandidate( "b", duplicate ),
			new SceneIdentityCandidate( "c", null )
		}, () => generated.Dequeue() );

		Assert.IsFalse( result[0].Repaired );
		Assert.IsTrue( result[1].Repaired );
		Assert.IsTrue( result[2].Repaired );
		Assert.AreNotEqual( result[0].EffectiveId, result[1].EffectiveId );
	}

	[TestMethod]
	public void RuntimeValidation_DisablesEveryAmbiguousOrBlankEntity()
	{
		var duplicate = new SceneEntityId( Guid.Parse( "11111111-1111-1111-1111-111111111111" ) );
		var result = SceneIdentityValidator.ValidateRuntime( new[]
		{
			new SceneIdentityCandidate( "a", duplicate ),
			new SceneIdentityCandidate( "b", duplicate ),
			new SceneIdentityCandidate( "c", null )
		} );

		Assert.IsTrue( result.All( value => !value.Enabled && value.FatalDiagnostic is not null ) );
	}

	[TestMethod]
	public void PersistentIndexEnumeratesTenThousandCandidatesOnceAndProvidesConstantTimeLookups()
	{
		var ids = Enumerable.Range( 0, 10_000 ).Select( _ => SceneEntityId.New() ).ToArray();
		var enumerated = 0;
		IEnumerable<SceneIdentityCandidate> Candidates()
		{
			for ( var index = 0; index < ids.Length; index++ )
			{
				enumerated++;
				yield return new SceneIdentityCandidate( $"entity/{index}", ids[index] );
			}
		}

		var index = PersistentSceneIdentityIndex.Build( Candidates() );

		Assert.AreEqual( 10_000, enumerated );
		Assert.AreEqual( 10_000, index.Count );
		for ( var candidate = 0; candidate < ids.Length; candidate++ )
		{
			Assert.IsTrue( index.TryResolveId( ids[candidate], out var byId ) );
			Assert.IsTrue( index.TryResolvePath( $"entity/{candidate}", out var byPath ) );
			Assert.AreSame( byId, byPath );
		}
	}

	[TestMethod]
	public void RuntimeNetworkDuplicateCannotPoisonEditorAuthoredIdentity()
	{
		var duplicate = SceneEntityId.New();
		var initial = PersistentSceneIdentityIndex.Build( new[]
		{
			new SceneIdentityCandidate( "first", duplicate )
		} );
		Assert.IsTrue( initial.TryResolveId( duplicate, out _ ) );

		var rebuilt = PersistentSceneIdentityIndex.Build( new[]
		{
			new SceneIdentityCandidate( "first", duplicate ),
			new SceneIdentityCandidate(
				"first", duplicate, SceneIdentityProvenance.RuntimeOrNetwork )
		} );

		Assert.IsTrue( rebuilt.TryResolveId( duplicate, out var resolved ) );
		Assert.AreEqual( "first", resolved.StablePath );
		Assert.IsTrue( rebuilt.Resolutions[0].Enabled );
		Assert.IsFalse( rebuilt.Resolutions[1].Enabled );
		Assert.AreEqual( SceneIdentityProvenance.RuntimeOrNetwork, rebuilt.Resolutions[1].Provenance );
		Assert.IsNull( rebuilt.Resolutions[1].EffectiveId );
		StringAssert.Contains( rebuilt.Resolutions[1].FatalDiagnostic, "runtime/network root" );
	}

	[TestMethod]
	public void TwoEditorAuthoredDuplicatesStillFailClosed()
	{
		var duplicate = SceneEntityId.New();
		var rebuilt = PersistentSceneIdentityIndex.Build( new[]
		{
			new SceneIdentityCandidate( "first", duplicate ),
			new SceneIdentityCandidate( "second", duplicate )
		} );

		Assert.IsFalse( rebuilt.TryResolveId( duplicate, out _ ) );
		Assert.IsTrue( rebuilt.Resolutions.All( resolution => !resolution.Enabled ) );
	}
}
using System.IO;

namespace Hexagon.V2.Tests.Foundation;

[TestClass]
public sealed class SandboxCompatibilityTests
{
	private static readonly string[] ForbiddenSourceTokens =
	[
		".ConfigureAwait(",
		"Volatile.",
		"ReaderWriterLockSlim",
		"CryptographicOperations.FixedTimeEquals",
		".IsInterface",
		".IsByRef",
		".IsPointer",
		".IsInstanceOfType",
		"ExceptionDispatchInfo",
		"System.Reflection",
		"RuntimeHelpers.",
		"Activator.CreateInstance",
		".GetProperties(",
		".GetFields(",
		".MakeGenericType(",
		".GetGenericArguments(",
		"await using",
		"Task.WhenAll(",
		"TaskScheduler",
		"Environment.ProcessId"
	];

	private static readonly (string Name, string Pattern)[] ForbiddenRawIoSignatures =
	[
		("File static API", @"\bFile\s*\."),
		("Directory static API", @"\bDirectory\s*\."),
		("Path.GetFullPath", @"\bPath\s*\.\s*GetFullPath\s*\("),
		("FileStream", @"\bFileStream\b"),
		("FileInfo", @"\bFileInfo\b"),
		("DirectoryInfo", @"\bDirectoryInfo\b"),
		("FileMode", @"\bFileMode\b"),
		("FileAccess", @"\bFileAccess\b"),
		("FileShare", @"\bFileShare\b"),
		("FileOptions", @"\bFileOptions\b"),
		("SearchOption", @"\bSearchOption\b")
	];

	[TestMethod]
	public void SandboxIndependentV2CodeAvoidsKnownWhitelistViolations()
	{
		var root = FindHexagonRoot();
		var sourceRoots = new[] { Path.Combine( root, "Code", "V2" ) }
			.Where( Directory.Exists )
			.ToArray();

		var violations = new List<string>();
		foreach ( var path in sourceRoots.SelectMany( sourceRoot =>
			Directory.GetFiles( sourceRoot, "*.cs", SearchOption.AllDirectories ) )
			.Where( path => !path.Contains(
				$"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}",
				StringComparison.OrdinalIgnoreCase ) ) )
		{
			var source = File.ReadAllText( path );
			if ( System.Text.RegularExpressions.Regex.IsMatch(
				source,
				@"^\s*(?:private|protected|internal|public)\s+(?:static\s+)?volatile\s+",
				System.Text.RegularExpressions.RegexOptions.Multiline ) )
				violations.Add(
					$"{Path.GetRelativePath( root, path )} declares a volatile field (lowers to forbidden IsVolatile)" );
			if ( System.Text.RegularExpressions.Regex.IsMatch(
				source,
				@"finally\s*\{[^{}]*\bawait\b",
				System.Text.RegularExpressions.RegexOptions.Singleline ) )
				violations.Add(
					$"{Path.GetRelativePath( root, path )} awaits inside a finally block (lowers to forbidden ExceptionDispatchInfo)" );
			if ( System.Text.RegularExpressions.Regex.IsMatch(
				source,
				@"catch(?:\s*\([^)]*\))?\s*\{[^{}]*\bawait\b[^{}]*\bthrow\s*;",
				System.Text.RegularExpressions.RegexOptions.Singleline ) )
				violations.Add(
					$"{Path.GetRelativePath( root, path )} awaits before a bare catch rethrow (lowers to forbidden ExceptionDispatchInfo)" );
			var lines = File.ReadAllLines( path );
			for ( var index = 0; index < lines.Length; index++ )
			{
				foreach ( var token in ForbiddenSourceTokens )
				{
					if ( lines[index].Contains( token, StringComparison.Ordinal ) )
						violations.Add( $"{Path.GetRelativePath( root, path )}:{index + 1} contains '{token}'" );
				}

				foreach ( var signature in FindForbiddenRawIoSignatures( lines[index] ) )
					violations.Add(
						$"{Path.GetRelativePath( root, path )}:{index + 1} uses forbidden raw I/O signature '{signature}'" );
			}
		}

		Assert.HasCount(
			0,
			violations,
			"Known s&box whitelist violations were found:" + Environment.NewLine + string.Join( Environment.NewLine, violations ) );
	}

	[TestMethod]
	public void RawIoSignatureGuardCoversOriginalSb1000SurfaceWithoutFalsePositives()
	{
		// Keep fixtures split so the production-source scanner can never match this test file if its roots expand.
		var separator = string.Empty;
		var forbiddenFixtures = new[]
		{
			"File" + separator + ".Exists( path )",
			"Path" + separator + ".GetFullPath( path )",
			"File" + separator + "Stream? lease",
			"File" + separator + "Info info",
			"Directory" + separator + "Info directory",
			"File" + separator + "Mode.OpenOrCreate",
			"File" + separator + "Access.ReadWrite",
			"File" + separator + "Share.None",
			"File" + separator + "Options.WriteThrough",
			"Search" + separator + "Option.AllDirectories",
			"Directory" + separator + " . EnumerateFiles( root )"
		};

		foreach ( var fixture in forbiddenFixtures )
			Assert.IsNotEmpty(
				FindForbiddenRawIoSignatures( fixture ),
				$"Expected raw I/O fixture to be rejected: {fixture}" );

		var allowedFixtures = new[]
		{
			"var path = issue.Path;",
			"var fileName = record.FileName;",
			"IReadOnlyList<string> directories",
			"storage.ExistsAsync( path, cancellationToken )"
		};

		foreach ( var fixture in allowedFixtures )
			Assert.IsEmpty(
				FindForbiddenRawIoSignatures( fixture ),
				$"Expected non-I/O fixture to remain allowed: {fixture}" );
	}

	private static string[] FindForbiddenRawIoSignatures( string sourceLine ) => ForbiddenRawIoSignatures
		.Where( signature => System.Text.RegularExpressions.Regex.IsMatch(
			sourceLine,
			signature.Pattern,
			System.Text.RegularExpressions.RegexOptions.CultureInvariant ) )
		.Select( signature => signature.Name )
		.ToArray();

	private static string FindHexagonRoot()
	{
		var directory = new DirectoryInfo( AppContext.BaseDirectory );
		while ( directory is not null && !File.Exists( Path.Combine( directory.FullName, "hexagon.sbproj" ) ) )
			directory = directory.Parent;

		Assert.IsNotNull( directory, "Could not locate the Hexagon repository root." );
		return directory.FullName;
	}
}
#nullable enable

using System;
using System.IO;
using System.Text.RegularExpressions;
using Hexagon.V2.Networking;
using Hexagon.V2.Tests.Foundation;

namespace Hexagon.V2.Tests.Networking;

/// <summary>
/// Sustained-run guards for the per-connection command budget, and a pin tying the numbers
/// <c>docs/security.md</c> publishes to the constants that enforce them.
/// <para>
/// The movement validator's failure was that a per-tick allowance was never checked as a rate, so prose
/// describing a bound and code granting one every tick could both read as true while disagreeing by
/// fifty times. Every other rate-limited surface therefore gets the same treatment: the published number
/// is read out of the document and asserted against the constant, and the constant is asserted against
/// what a client can actually extract over a run.
/// </para>
/// </summary>
[TestClass]
public sealed class CommandAdmissionSustainedTests
{
	private const long Frequency = 1000;          // timestamps in milliseconds
	private const int Hz = 50;
	private const int TickMilliseconds = 1000 / Hz;
	private const int Ticks = 10 * Hz;            // ten seconds
	private const double ElapsedSeconds = 10.0;

	[TestMethod]
	public void TheSecurityDocumentPublishesTheBudgetThatIsActuallyEnforced()
	{
		// docs/security.md: "a weighted per-connection token bucket before payload construction:
		// 16-unit burst, 8 units/second refill, and at most 16 active requests."
		// Reading the numbers out of the document makes the prose a claim this suite owns: editing
		// either side without the other fails here rather than shipping a document that describes a
		// system nobody built.
		var document = File.ReadAllText( Path.Combine( RepositoryRoot(), "docs", "security.md" ) );

		var match = Regex.Match( document,
			@"(?<burst>\d+)-unit burst,\s*(?<refill>\d+)\s*units?/second refill,\s*and at most\s*(?<active>\d+)\s*active requests" );
		Assert.IsTrue( match.Success,
			"docs/security.md no longer states the command budget in the form this guard reads. " +
			"Update the guard deliberately rather than letting the published numbers go unchecked." );

		Assert.AreEqual( CommandAdmissionController.BurstUnits, int.Parse( match.Groups["burst"].Value ),
			"Documented burst does not match the enforced constant." );
		Assert.AreEqual( CommandAdmissionController.RefillUnitsPerSecond, int.Parse( match.Groups["refill"].Value ),
			"Documented refill rate does not match the enforced constant." );
		Assert.AreEqual( CommandAdmissionController.MaximumActiveRequests, int.Parse( match.Groups["active"].Value ),
			"Documented active-request cap does not match the enforced constant." );
	}

	[TestMethod]
	public void SustainedAdmissionConvergesOnTheRefillRateNotTheBurst()
	{
		// The burst is a one-off; the refill rate is the ceiling. A bucket that re-granted its burst
		// the way the movement validator re-granted its skin would admit 800 units here, not 96.
		var controller = new CommandAdmissionController( Frequency );
		var admittedCount = 0;

		var run = SustainedEnvelope.Measure( Ticks, 1.0 / Hz, tick =>
		{
			var timestamp = (long)tick * TickMilliseconds;
			var requestId = new CommandRequestId( Guid.NewGuid() );
			var result = controller.TryBegin( requestId, cost: 1, timestamp );
			if ( !result.Accepted ) return SustainedEnvelope.StepOutcome.Rejected;
			controller.Finish( requestId );   // keep the active-request bound out of the way
			admittedCount++;
			return SustainedEnvelope.StepOutcome.Allowed( 1 );
		} );

		var bound = (CommandAdmissionController.RefillUnitsPerSecond * ElapsedSeconds)
			+ CommandAdmissionController.BurstUnits;

		Assert.IsLessThanOrEqualTo( bound, run.TotalWork, SustainedEnvelope.Describe(
			$"Sustained command admission over 10s ({run.WorkPerSecond:F1}/s)",
			run.TotalWork, bound, " units" ) );
		Assert.IsGreaterThan( 0, admittedCount,
			"The run admitted nothing, so the bound above proves nothing about the bucket." );
	}

	[TestMethod]
	public void RejectedAttemptsAreNotRefundedAcrossASustainedRun()
	{
		// docs/security.md: "Rejected, duplicate, and malformed attempts are not refunded." A refund on
		// rejection would make the bucket a no-op under exactly the load it exists to bound.
		var controller = new CommandAdmissionController( Frequency );
		var duplicate = new CommandRequestId( Guid.NewGuid() );

		// Drain the burst with duplicates, which are charged and then refused.
		for ( var i = 0; i < CommandAdmissionController.BurstUnits; i++ )
			controller.TryBegin( duplicate, cost: 1, 0 );

		Assert.IsLessThanOrEqualTo( 0.001, controller.AvailableUnits,
			$"Duplicates were refunded: {controller.AvailableUnits:F2} units remain after draining the burst." );
	}

	[TestMethod]
	public void ACostlierCommandDrainsTheBudgetProportionally()
	{
		// The bucket is weighted, so an expensive command must consume its weight rather than one slot.
		var controller = new CommandAdmissionController( Frequency );
		var admitted = 0;
		for ( var i = 0; i < 100; i++ )
		{
			var requestId = new CommandRequestId( Guid.NewGuid() );
			if ( !controller.TryBegin( requestId, CommandAdmissionController.BurstUnits, 0 ).Accepted ) break;
			controller.Finish( requestId );
			admitted++;
		}

		Assert.AreEqual( 1, admitted,
			"A full-burst-cost command should be admissible exactly once from a full bucket." );
	}

	private static string RepositoryRoot()
	{
		var directory = new DirectoryInfo( AppContext.BaseDirectory );
		while ( directory is not null && !File.Exists( Path.Combine( directory.FullName, "hexagon.sbproj" ) ) )
			directory = directory.Parent;
		Assert.IsNotNull( directory, "Could not locate the Hexagon repository root." );
		return directory!.FullName;
	}
}
#nullable enable

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Hexagon.V2.Domain;
using Hexagon.V2.Kernel;
using Hexagon.V2.Networking;

namespace Hexagon.V2.Tests.Networking;

[TestClass]
public sealed class CommandDispatchOrchestratorTests
{
	[TestMethod]
	public void UnauthenticatedCallerIsRejectedBeforeAnyChargeOrResolution()
	{
		var host = new FakeDispatchHost { Authenticated = false };

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		Assert.IsEmpty( host.Calls, "Unauthenticated traffic must never reach the per-connection bucket." );
		Assert.HasCount( 1, host.Sent );
		Assert.AreEqual( "caller", host.Sent[0].Target );
		Assert.AreEqual( ErrorCode.Unauthorized, host.Sent[0].Result.Error!.Code );
	}

	[TestMethod]
	public void ChargeHappensBeforeResolutionAndCarriesTheDeclaredCost()
	{
		var host = new FakeDispatchHost();

		CommandDispatchOrchestrator.Dispatch( host, Header(), 3, static () => new RequestCharacterListCommand() );

		Assert.AreEqual( "charge:3", host.Calls[0], "The caller is charged before its scope is inspected." );
		Assert.AreEqual( "resolve:initial", host.Calls[1] );
	}

	[TestMethod]
	public void RateLimitedAdmissionCarriesRetryAfterAndSkipsResolution()
	{
		var host = new FakeDispatchHost
		{
			Admission = CommandAdmissionResult.Reject(
				CommandAdmissionFailure.RateLimited, TimeSpan.FromSeconds( 2 ) )
		};

		CommandDispatchOrchestrator.Dispatch( host, Header(), 4, static () => new RequestCharacterListCommand() );

		CollectionAssert.AreEqual( new[] { "charge:4" }, host.Calls );
		Assert.HasCount( 1, host.Sent );
		Assert.AreEqual( ErrorCode.RateLimited, host.Sent[0].Result.Error!.Code );
	}

	[TestMethod]
	public void DuplicateRequestIdsConflict()
	{
		var host = new FakeDispatchHost
		{
			Admission = CommandAdmissionResult.Reject( CommandAdmissionFailure.Duplicate )
		};

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		Assert.AreEqual( ErrorCode.Conflict, host.Sent[0].Result.Error!.Code );
	}

	[TestMethod]
	public void ResolutionFailureIsForwardedAndTheAcceptedChargeIsClosed()
	{
		var host = new FakeDispatchHost
		{
			Resolution = OperationResult<string>.Failure( ErrorCode.Unauthorized, "scope mismatch" )
		};

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		CollectionAssert.AreEqual(
			new[] { "charge:1", "resolve:initial", "finish-rejected" },
			host.Calls );
		Assert.AreEqual( "scope mismatch", host.Sent[0].Result.Error!.Message );
	}

	[TestMethod]
	public void EmptyRequestIdCompletesWithInvalidArgument()
	{
		var host = new FakeDispatchHost();
		// Wire deserialization can materialize a default header; the constructor itself
		// rejects empty request ids, so the guard is only reachable through default structs.
		var header = default( ClientCommandHeader );

		CommandDispatchOrchestrator.Dispatch( host, header, 1, static () => new RequestCharacterListCommand() );

		CollectionAssert.Contains( host.Calls, "complete" );
		Assert.HasCount( 1, host.Sent );
		Assert.AreEqual( "actor", host.Sent[0].Target );
		Assert.AreEqual( ErrorCode.InvalidArgument, host.Sent[0].Result.Error!.Code );
	}

	[TestMethod]
	public void ThrowingCommandFactoryFailsClosedWithoutStartingAHostOperation()
	{
		var host = new FakeDispatchHost();

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1,
			static () => throw new FormatException( "bad wire payload" ) );

		Assert.HasCount( 1, host.MalformedPayloads );
		CollectionAssert.DoesNotContain( host.Calls, "host-operation" );
		Assert.AreEqual( ErrorCode.InvalidArgument, host.Sent[0].Result.Error!.Code );
	}

	[TestMethod]
	public void OversizedPayloadIsRejectedBeforeStableResolutionAndExecution()
	{
		var host = new FakeDispatchHost();

		CommandDispatchOrchestrator.Dispatch( host, Header(), 2,
			static () => new SendChatCommand( "ic", new string( 'a', 1_000_000 ) ) );

		CollectionAssert.DoesNotContain( host.Calls, "resolve:stable" );
		CollectionAssert.DoesNotContain( host.Calls, "host-operation" );
		Assert.HasCount( 1, host.Sent );
		Assert.IsFalse( host.Sent[0].Result.Succeeded );
	}

	[TestMethod]
	public void StableCharacterCommandsAreReResolvedAfterPayloadValidation()
	{
		var host = new FakeDispatchHost();

		CommandDispatchOrchestrator.Dispatch( host, Header(), 2,
			static () => new SendChatCommand( "ic", "hello" ) );

		CollectionAssert.AreEqual(
			new[]
			{
				"charge:2", "resolve:initial", "resolve:stable",
				"host-operation", "lease-check", "execute", "complete"
			},
			host.Calls );
		Assert.AreEqual( "stable-actor", host.Sent[0].Target,
			"Execution and completion must use the re-resolved stable actor." );
	}

	[TestMethod]
	public void StableResolutionFailureCompletesWithoutExecuting()
	{
		var host = new FakeDispatchHost
		{
			StableResolution = OperationResult<string>.Failure(
				ErrorCode.Unauthorized, "character changed" )
		};

		CommandDispatchOrchestrator.Dispatch( host, Header(), 2,
			static () => new SendChatCommand( "ic", "hello" ) );

		CollectionAssert.DoesNotContain( host.Calls, "execute" );
		Assert.AreEqual( "character changed", host.Sent[0].Result.Error!.Message );
	}

	[TestMethod]
	public void CharacterLifecycleCommandsSkipTheStableCharacterResolution()
	{
		foreach ( var factory in new Func<ClientCommand>[]
		{
			static () => new RequestCharacterListCommand(),
			static () => new LoadCharacterCommand( CharacterId.New() ),
			static () => new DeleteCharacterCommand( CharacterId.New() ),
			static () => new UnloadCharacterCommand()
		} )
		{
			var host = new FakeDispatchHost();
			CommandDispatchOrchestrator.Dispatch( host, Header(), 1, factory );
			CollectionAssert.DoesNotContain( host.Calls, "resolve:stable",
				$"{factory().GetType().Name} must dispatch without an active character." );
			Assert.AreEqual( "actor", host.Sent[0].Target );
		}
	}

	[TestMethod]
	public void HostOperationRefusalCompletesWithDrainingConflict()
	{
		var host = new FakeDispatchHost { AcceptHostOperation = false };

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		Assert.AreEqual( ErrorCode.Conflict, host.Sent[0].Result.Error!.Code );
		StringAssert.Contains( host.StartedOperationName!, "command:" );
	}

	[TestMethod]
	public void StaleLeaseInsideTheHostOperationRejectsBeforeExecution()
	{
		var host = new FakeDispatchHost { LeaseCurrent = false };

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		CollectionAssert.DoesNotContain( host.Calls, "execute" );
		Assert.AreEqual( ErrorCode.Unauthorized, host.Sent[0].Result.Error!.Code );
	}

	[TestMethod]
	public void MissingHostApplicationCompletesWithInternalError()
	{
		var host = new FakeDispatchHost { ExecutionAvailable = false };

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		CollectionAssert.DoesNotContain( host.Calls, "execute" );
		Assert.AreEqual( ErrorCode.InternalError, host.Sent[0].Result.Error!.Code );
	}

	[TestMethod]
	public void ExecutionFaultBecomesInternalError()
	{
		var host = new FakeDispatchHost
		{
			Execution = static () => throw new InvalidOperationException( "handler exploded" )
		};

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		Assert.HasCount( 1, host.ExecutionFaults );
		Assert.AreEqual( ErrorCode.InternalError, host.Sent[0].Result.Error!.Code );
	}

	[TestMethod]
	public void ExecutionFaultUnderCancellationBecomesUnauthorized()
	{
		using var cancellation = new CancellationTokenSource();
		cancellation.Cancel();
		var host = new FakeDispatchHost
		{
			Cancellation = cancellation.Token,
			Execution = static () => throw new OperationCanceledException()
		};

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		Assert.AreEqual( ErrorCode.Unauthorized, host.Sent[0].Result.Error!.Code );
	}

	[TestMethod]
	public void DisconnectedCompletionSendsNothing()
	{
		var host = new FakeDispatchHost { CompletionStatus = CommandCompletionStatus.Disconnected };

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		Assert.IsEmpty( host.Sent );
	}

	[TestMethod]
	public void StaleCompletionKeepsASuccessfulExecutionResultButRejectsAFailedOne()
	{
		var success = new FakeDispatchHost { CompletionStatus = CommandCompletionStatus.Stale };
		CommandDispatchOrchestrator.Dispatch( success, Header(), 1, static () => new RequestCharacterListCommand() );
		Assert.IsTrue( success.Sent[0].Result.Succeeded,
			"A successful application result stays authoritative even under a stale lease." );

		var failure = new FakeDispatchHost
		{
			CompletionStatus = CommandCompletionStatus.Stale,
			Execution = static () => ValueTask.FromResult(
				OperationResult.Failure( ErrorCode.NotFound, "gone" ) )
		};
		CommandDispatchOrchestrator.Dispatch( failure, Header(), 1, static () => new RequestCharacterListCommand() );
		Assert.AreEqual( ErrorCode.Unauthorized, failure.Sent[0].Result.Error!.Code );
	}

	[TestMethod]
	public void SuccessfulExecutionForwardsTheApplicationResult()
	{
		var host = new FakeDispatchHost
		{
			Execution = static () => ValueTask.FromResult(
				OperationResult.Failure( ErrorCode.PolicyDenied, "application said no" ) )
		};

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		Assert.AreEqual( ErrorCode.PolicyDenied, host.Sent[0].Result.Error!.Code );
		Assert.AreEqual( "application said no", host.Sent[0].Result.Error!.Message );
	}

	[TestMethod]
	public void ThrowingCompletionHookIsObservedWithoutEscaping()
	{
		var host = new FakeDispatchHost { CompleteThrows = true };

		CommandDispatchOrchestrator.Dispatch( host, Header(), 1, static () => new RequestCharacterListCommand() );

		Assert.HasCount( 1, host.CompletionFaults );
		Assert.IsEmpty( host.Sent );
	}

	[TestMethod]
	public void CommandCostTableIsPinned()
	{
		var table = new Dictionary<string, int>( StringComparer.Ordinal )
		{
			[nameof( ClientCommandCosts.CharacterList )] = ClientCommandCosts.CharacterList,
			[nameof( ClientCommandCosts.CreateCharacter )] = ClientCommandCosts.CreateCharacter,
			[nameof( ClientCommandCosts.LoadCharacter )] = ClientCommandCosts.LoadCharacter,
			[nameof( ClientCommandCosts.DeleteCharacter )] = ClientCommandCosts.DeleteCharacter,
			[nameof( ClientCommandCosts.UnloadCharacter )] = ClientCommandCosts.UnloadCharacter,
			[nameof( ClientCommandCosts.MoveItem )] = ClientCommandCosts.MoveItem,
			[nameof( ClientCommandCosts.ItemAction )] = ClientCommandCosts.ItemAction,
			[nameof( ClientCommandCosts.DropItem )] = ClientCommandCosts.DropItem,
			[nameof( ClientCommandCosts.PickupItem )] = ClientCommandCosts.PickupItem,
			[nameof( ClientCommandCosts.Chat )] = ClientCommandCosts.Chat,
			[nameof( ClientCommandCosts.CancelAction )] = ClientCommandCosts.CancelAction,
			[nameof( ClientCommandCosts.BeginInteraction )] = ClientCommandCosts.BeginInteraction,
			[nameof( ClientCommandCosts.ContinueInteraction )] = ClientCommandCosts.ContinueInteraction,
			[nameof( ClientCommandCosts.CloseInteraction )] = ClientCommandCosts.CloseInteraction,
			[nameof( ClientCommandCosts.SchemaCommandFallback )] = ClientCommandCosts.SchemaCommandFallback
		};
		var expected = new Dictionary<string, int>( StringComparer.Ordinal )
		{
			["CharacterList"] = 1,
			["CreateCharacter"] = 4,
			["LoadCharacter"] = 4,
			["DeleteCharacter"] = 4,
			["UnloadCharacter"] = 1,
			["MoveItem"] = 4,
			["ItemAction"] = 4,
			["DropItem"] = 4,
			["PickupItem"] = 4,
			["Chat"] = 2,
			["CancelAction"] = 1,
			["BeginInteraction"] = 2,
			["ContinueInteraction"] = 1,
			["CloseInteraction"] = 1,
			["SchemaCommandFallback"] = 8
		};
		CollectionAssert.AreEquivalent( expected, table );
	}

	[TestMethod]
	public void OnlyCharacterLifecycleCommandsAreExemptFromTheStableCharacterRequirement()
	{
		Assert.IsFalse( CommandDispatchOrchestrator.RequiresStableCharacter( new RequestCharacterListCommand() ) );
		Assert.IsFalse( CommandDispatchOrchestrator.RequiresStableCharacter(
			new LoadCharacterCommand( CharacterId.New() ) ) );
		Assert.IsFalse( CommandDispatchOrchestrator.RequiresStableCharacter(
			new DeleteCharacterCommand( CharacterId.New() ) ) );
		Assert.IsFalse( CommandDispatchOrchestrator.RequiresStableCharacter( new UnloadCharacterCommand() ) );
		Assert.IsTrue( CommandDispatchOrchestrator.RequiresStableCharacter( new SendChatCommand( "ic", "hi" ) ) );
		Assert.IsTrue( CommandDispatchOrchestrator.RequiresStableCharacter(
			new CloseInteractionCommand( InteractionSessionId.New() ) ) );
	}

	private static ClientSessionScope Scope() =>
		new( ClientSessionNonce.New(), ConnectionEpoch.New() );

	private static ClientCommandHeader Header() =>
		new( Scope(), new CommandRequestId( Guid.NewGuid() ) );

	private sealed class FakeDispatchHost : ICommandDispatchHost<string>
	{
		public List<string> Calls { get; } = new();
		public bool Authenticated { get; set; } = true;
		public bool ExecutionAvailable { get; set; } = true;
		public CommandAdmissionResult Admission { get; set; } = CommandAdmissionResult.Success();
		public OperationResult<string> Resolution { get; set; } = OperationResult<string>.Success( "actor" );
		public OperationResult<string> StableResolution { get; set; } = OperationResult<string>.Success( "stable-actor" );
		public bool LeaseCurrent { get; set; } = true;
		public bool AcceptHostOperation { get; set; } = true;
		public bool CompleteThrows { get; set; }
		public Func<ValueTask<OperationResult>>? Execution { get; set; }
		public CancellationToken Cancellation { get; set; }
		public CommandCompletionStatus CompletionStatus { get; set; } = CommandCompletionStatus.Current;
		public List<(string Target, OperationResult Result)> Sent { get; } = new();
		public List<Exception> MalformedPayloads { get; } = new();
		public List<Exception> ExecutionFaults { get; } = new();
		public List<Exception> CompletionFaults { get; } = new();
		public string? StartedOperationName { get; private set; }

		public bool CallerIsAuthenticated => Authenticated;
		public bool IsExecutionAvailable => ExecutionAvailable;

		public CommandAdmissionResult TryBeginCommand( CommandRequestId requestId, int cost )
		{
			Calls.Add( $"charge:{cost}" );
			return Admission;
		}

		public OperationResult<string> ResolveActor( ClientSessionScope scope, bool requireStableCharacter )
		{
			Calls.Add( requireStableCharacter ? "resolve:stable" : "resolve:initial" );
			return requireStableCharacter ? StableResolution : Resolution;
		}

		public void FinishRejectedCommand( CommandRequestId requestId ) => Calls.Add( "finish-rejected" );

		public bool IsCommandLeaseCurrent( string actor )
		{
			Calls.Add( "lease-check" );
			return LeaseCurrent;
		}

		public bool TryStartHostOperation( string name, Func<Task> operation )
		{
			Calls.Add( "host-operation" );
			StartedOperationName = name;
			if ( !AcceptHostOperation ) return false;
			operation().GetAwaiter().GetResult();
			return true;
		}

		public CancellationToken CommandCancellation( string actor ) => Cancellation;

		public ValueTask<OperationResult> ExecuteAsync(
			string actor,
			ClientCommand command,
			CancellationToken cancellationToken )
		{
			Calls.Add( "execute" );
			return Execution?.Invoke() ?? ValueTask.FromResult( OperationResult.Success() );
		}

		public CommandCompletionStatus CompleteCommand( string actor, CommandRequestId requestId )
		{
			Calls.Add( "complete" );
			if ( CompleteThrows ) throw new InvalidOperationException( "completion registry exploded" );
			return CompletionStatus;
		}

		public void SendResultToCaller( ClientSessionScope scope, CommandRequestId requestId, OperationResult result ) =>
			Sent.Add( ("caller", result) );

		public void SendResult( string actor, CommandRequestId requestId, OperationResult result ) =>
			Sent.Add( (actor, result) );

		public void OnMalformedPayload( Exception exception ) => MalformedPayloads.Add( exception );
		public void OnExecutionFault( CommandRequestId requestId, Exception exception ) => ExecutionFaults.Add( exception );
		public void OnCompletionFault( CommandRequestId requestId, Exception exception ) => CompletionFaults.Add( exception );
	}
}
#nullable enable

using System;
using Hexagon.V2.Runtime;
using static Hexagon.V2.Tests.Foundation.SustainedEnvelope.EngineDefaults;

namespace Hexagon.V2.Tests.Runtime;

/// <summary>
/// Guards for the windowed movement audit.
/// <para>
/// The host observes an INTERPOLATED proxy transform, not what the client reported, so its per-tick
/// deltas are artifacts of network buffering. The audit's soundness rests on one property —
/// <b>summing deltas over a window is invariant to how the travel is chunked</b> — and
/// <see cref="AnIdenticalPathIsMeasuredTheSameHoweverItIsChunked"/> is the test that pins it. Everything
/// else here is a bound stated over a window; nothing is stated per tick, because per tick the signal
/// carries no truth.
/// </para>
/// </summary>
[TestClass]
public sealed class HexMovementValidatorTests
{
	private static readonly HexMovementEnvelope Envelope = HexMovementEnvelope.Default;

	private static MovementSample At( float x, float y, float z ) => new( x, y, z );

	/// <summary>
	/// Walks a straight horizontal path of <paramref name="totalDistance"/> over
	/// <paramref name="seconds"/>, delivered in <paramref name="samples"/> chunks, and returns the
	/// decision of the window that closes.
	/// </summary>
	private static HexMovementValidator.Decision WalkHorizontally(
		float totalDistance, double seconds, int samples, bool frozen = false )
	{
		var audit = MovementAudit.OpenAt( At( 0, 0, 0 ), 0 );
		var last = default( HexMovementValidator.Decision );
		for ( var i = 1; i <= samples; i++ )
		{
			var t = seconds * i / samples;
			var x = totalDistance * i / samples;
			(last, audit) = HexMovementValidator.Observe(
				audit, At( (float)x, 0, 0 ), t, RunSpeed, JumpSpeed, frozen, Envelope );
		}
		return last;
	}

	[TestMethod]
	public void AnIdenticalPathIsMeasuredTheSameHoweverItIsChunked()
	{
		// THE property the whole design rests on. The interpolation buffer decides how travel is sliced
		// — smoothly, in bursts of three ticks, or irregularly — and the audit must be blind to that.
		// Per-tick checking is not: measured live, an honest sprint arrived as 19.2-unit bursts against
		// a ~15-unit per-tick budget and was refused twelve times.
		var smooth = WalkHorizontally( 300f, 1.0, samples: 60 );
		var bursty = WalkHorizontally( 300f, 1.0, samples: 20 );
		var coarse = WalkHorizontally( 300f, 1.0, samples: 5 );

		Assert.AreEqual( smooth.HorizontalPath, bursty.HorizontalPath, 0.01f,
			"Chunking changed the measured path; the audit is not sampling-invariant." );
		Assert.AreEqual( smooth.HorizontalPath, coarse.HorizontalPath, 0.01f,
			"Chunking changed the measured path; the audit is not sampling-invariant." );
	}

	[TestMethod]
	public void AnHonestSprintIsAcceptedHoweverItIsSampled()
	{
		// A player at exactly run speed for a full window, delivered every way the network might.
		foreach ( var samples in new[] { 60, 20, 12, 5 } )
		{
			var decision = WalkHorizontally( RunSpeed, 1.0, samples );
			Assert.IsTrue( decision.WindowClosed, $"Window did not close for {samples} samples." );
			Assert.IsFalse( decision.Corrected,
				$"Honest sprint flagged at {samples} samples/window: " +
				$"path {decision.HorizontalPath:F1} vs budget {decision.HorizontalBudget:F1}." );
		}
	}

	[TestMethod]
	public void ASpeedCheatIsFlagged()
	{
		// Twice run speed for a window: 640 units against a 496-unit budget.
		var decision = WalkHorizontally( RunSpeed * 2f, 1.0, samples: 60 );
		Assert.AreEqual( HexMovementValidator.Verdict.WindowExceeded, decision.Verdict,
			$"path {decision.HorizontalPath:F1} vs budget {decision.HorizontalBudget:F1}" );
	}

	[TestMethod]
	public void SustainedClimbBeyondTheGroundAngleIsFlagged()
	{
		// Rising far faster than the steepest standable surface would permit for the distance walked.
		var audit = MovementAudit.OpenAt( At( 0, 0, 0 ), 0 );
		var decision = default( HexMovementValidator.Decision );
		for ( var i = 1; i <= 60; i++ )
		{
			// 100 units of horizontal travel, 900 units of rise, in one window.
			(decision, audit) = HexMovementValidator.Observe(
				audit, At( 100f * i / 60f, 0, 900f * i / 60f ), i / 60.0,
				RunSpeed, JumpSpeed, false, Envelope );
		}
		Assert.AreEqual( HexMovementValidator.Verdict.WindowExceeded, decision.Verdict,
			$"rise {decision.NetRise:F1} vs budget {decision.RiseBudget:F1}" );
	}

	[TestMethod]
	public void AJumpAndLandIsNotAClimb()
	{
		// Net rise over a window is what "climbing" means. Jumping repeatedly nets nothing, and must
		// not accumulate into a violation the way summed positive rise would.
		var audit = MovementAudit.OpenAt( At( 0, 0, 0 ), 0 );
		var decision = default( HexMovementValidator.Decision );
		for ( var i = 1; i <= 60; i++ )
		{
			var t = i / 60.0;
			var phase = Math.Sin( t * Math.PI * 4 );           // two full jump arcs in the window
			var z = (float)(Math.Max( phase, 0 ) * 56.0);      // apex ~ one jump
			(decision, audit) = HexMovementValidator.Observe(
				audit, At( 200f * i / 60f, 0, z ), t, RunSpeed, JumpSpeed, false, Envelope );
		}
		Assert.IsFalse( decision.Corrected,
			$"Jumping while running was flagged: rise {decision.NetRise:F1} vs {decision.RiseBudget:F1}" );
	}

	[TestMethod]
	public void ATeleportIsRefusedImmediatelyWithoutWaitingForTheWindow()
	{
		var audit = MovementAudit.OpenAt( At( 0, 0, 0 ), 0 );
		var (decision, _) = HexMovementValidator.Observe(
			audit, At( 5000, 0, 0 ), 0.016, RunSpeed, JumpSpeed, false, Envelope );

		Assert.AreEqual( HexMovementValidator.Verdict.Teleport, decision.Verdict );
		Assert.IsFalse( decision.WindowClosed, "A teleport is caught on the sample, not at window end." );
	}

	[TestMethod]
	public void AnInterpolationSizedBurstIsNotMistakenForATeleport()
	{
		// The bursts that broke per-tick checking (~19 units) must be nowhere near the teleport guard.
		var audit = MovementAudit.OpenAt( At( 0, 0, 0 ), 0 );
		var (decision, _) = HexMovementValidator.Observe(
			audit, At( 19.2f, 0, 0 ), 0.016, RunSpeed, JumpSpeed, false, Envelope );
		Assert.AreEqual( HexMovementValidator.Verdict.Accepted, decision.Verdict );
	}

	[TestMethod]
	public void FrozenPlayersAreHeldToTheDriftAllowance()
	{
		var moved = WalkHorizontally( 200f, 1.0, samples: 60, frozen: true );
		Assert.AreEqual( HexMovementValidator.Verdict.WindowExceeded, moved.Verdict );

		var jitter = WalkHorizontally( Envelope.FrozenDriftAllowance * 0.5f, 1.0, samples: 60, frozen: true );
		Assert.IsFalse( jitter.Corrected, "Jitter within the frozen allowance must not be flagged." );
	}

	[TestMethod]
	public void CorrectsNonFinitePosition()
	{
		var audit = MovementAudit.OpenAt( At( 0, 0, 0 ), 0 );
		var (decision, _) = HexMovementValidator.Observe(
			audit, At( float.NaN, 0, 0 ), 0.016, RunSpeed, JumpSpeed, false, Envelope );
		Assert.AreEqual( HexMovementValidator.Verdict.Teleport, decision.Verdict );
	}

	[TestMethod]
	public void AnUnprimedAuditAdoptsTheFirstSampleWithoutFlagging()
	{
		var (decision, audit) = HexMovementValidator.Observe(
			MovementAudit.Unprimed, At( 500, 500, 500 ), 0, RunSpeed, JumpSpeed, false, Envelope );
		Assert.IsFalse( decision.Corrected );
		Assert.IsTrue( audit.Primed );
		Assert.AreEqual( 500f, audit.Anchor.X );
	}

	[TestMethod]
	public void AMalformedEnvelopeIsRejectedRatherThanAuditingNothing()
	{
		Assert.IsTrue( HexMovementEnvelope.Default.IsWellFormed( out _ ) );

		var permissive = HexMovementEnvelope.Default with { HorizontalTolerance = 50f };
		Assert.IsFalse( permissive.IsWellFormed( out var error ) );
		Assert.IsNotEmpty( error );

		var blind = HexMovementEnvelope.Default with { AuditWindowSeconds = 999f };
		Assert.IsFalse( blind.IsWellFormed( out _ ) );
	}
}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
using System.Numerics;
using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Skeleton;
using HumanoidRetargeter.Target;
using Xunit;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Tests.Target;

public class StockBindPoseTests
{
    private const string RigJson = """
    { "name": "stock", "bones": [
        { "name": "pelvis", "parent": null, "class": "Animated", "role": "Hips", "local_pos": [0, 100, 0], "local_rot_xyzw": [0, 0, 0, 1], "tail_world": [0, 110, 0] },
        { "name": "helper", "parent": "pelvis", "class": "ConstraintDriven", "local_pos": [1, 0, 0], "local_rot_xyzw": [0, 0, 0, 1] },
        { "name": "root_IK", "parent": null, "class": "IkBaked", "local_pos": [0, 0, 0], "local_rot_xyzw": [0, 0, 0, 1] }
    ] }
    """;

    [Fact]
    public void RebindingPreservesCuratedHelpersAndAdoptsCompiledRest()
    {
        var stock = TargetRig.Load(RigJson);
        var rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI / 2);
        var definitions = stock.Skeleton.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : stock.Skeleton[b.ParentIndex].Name,
            b.ParentIndex < 0 ? new XForm(Vector3.Transform(b.RestLocal.Pos, rotation), rotation) : b.RestLocal)).ToList();
        definitions.Add(new BoneDefinition("custom_extra", "pelvis", XForm.Identity));
        var bind = SkeletonModel.Create(definitions);
        var rig = stock.WithBindPose(bind);
        Assert.Same(bind, rig.Skeleton);
        Assert.True(rig.HelpersAreConstraintDriven);
        Assert.Equal(BoneClass.ConstraintDriven, rig.ClassOf(bind.IndexOf("helper")));
        Assert.Equal(BoneClass.IkBaked, rig.ClassOf(bind.IndexOf("root_IK")));
        Assert.Equal(BoneClass.Animated, rig.ClassOf(bind.IndexOf("custom_extra")));
        Assert.Equal(bind.IndexOf("pelvis"), rig.BoneForRole(BoneRole.Hips));
        Assert.True(Vector3.Distance(new Vector3(-110, 0, 0), rig.TailWorldOf(bind.IndexOf("pelvis"))!.Value) < .001f);
        Assert.Equal(new Vector3(0, 100, 0), stock.Skeleton.RestWorld[stock.Skeleton.IndexOf("pelvis")].Pos);
    }

    [Fact]
    public void MissingMappedBonesFailRatherThanDroppingMotion()
        => Assert.Throws<ArgumentException>(() => TargetRig.Load(RigJson).WithBindPose(
            SkeletonModel.Create(new[] { new BoneDefinition("unrelated", null, XForm.Identity) })));

    [Fact]
    public void PrefabMeshUsesTheSameDmxRootCorrectionAsAnExplicitMesh()
    {
        var rig = TargetRig.Load(RigJson);
        var frames = new[] { rig.Skeleton.Bones.Select(b => b.RestLocal).ToArray() };
        var prefab = new RetargetTargetSpec { Rig = rig, VmdlScale = .3937f, CompensateDmxRootYaw = true };
        var explicitMesh = new RetargetTargetSpec { Rig = rig, VmdlScale = .3937f, MeshFilePath = "body.fbx" };
        var expected = Retargeter.TestHook_CompensateEmbeddedMeshRootYaw(frames, explicitMesh);
        var actual = Retargeter.TestHook_CompensateEmbeddedMeshRootYaw(frames, prefab);
        Assert.Equal(expected[0], actual[0]);
        Assert.NotEqual(frames[0][0].Rot, actual[0][0].Rot);
        Assert.Equal(rig.Skeleton[0].RestLocal, frames[0][0]);
    }
}
using HumanoidRetargeter.Target;
using Xunit;

namespace HumanoidRetargeter.Tests;

public class Kv3ModelDocNumberTests
{
    [Theory]
    [InlineData(1.0380962e-05)]
    [InlineData(-1.2e-12)]
    [InlineData(1.25e25)]
    [InlineData(double.Epsilon)]
    [InlineData(double.MaxValue)]
    public void ModelDocNumbersHaveNoExponentAndRoundTripExactly(double value)
    {
        var doc = Kv3.Parse(VmdlWriter.Kv3Header + "\n{ value = 0.0 }");
        ((KvObject)doc.Root)["value"] = new KvDouble(value);
        var text = Kv3.Serialize(doc);
        var number = text[(text.IndexOf("value = ", StringComparison.Ordinal) + 8)..].Trim().TrimEnd('}').Trim();
        Assert.DoesNotContain("E", number);
        Assert.DoesNotContain("e", number);
        Assert.Equal(value, ((KvDouble)((KvObject)Kv3.Parse(text).Root)["value"]).Value);
    }
}
using System.Reflection;
using System.Text;
using HumanoidRetargeter.Editor;
using HumanoidRetargeterVrf;
using HumanoidRetargeterVrf.IO;
using Xunit;

namespace SmartPort.Parser.Tests;

public class OptionalMaterialTests
{
    const string Outfit = "models/human_clothes/default_outfit/default_outfit_male_lower.vmat";

    sealed class FailingLoader(Exception error) : IFileLoader
    {
        public Resource LoadFile(string file) => throw error;
        public Resource LoadFileCompiled(string file) => LoadFile(file);
    }

    [Fact]
    public void MissingOutfitMetadataUsesVertexBufferSemantics()
    {
        var loader = new FailingLoader(new FileNotFoundException("Missing material", Outfit));
        Assert.Empty(ModelExtract.ReadMaterialInputSignature(loader, Outfit).Elements);
    }

    [Fact]
    public void CorruptMetadataAndCancellationAreNotHidden()
    {
        Assert.Throws<InvalidDataException>(() => ModelExtract.ReadMaterialInputSignature(
            new FailingLoader(new InvalidDataException("Corrupt material")), Outfit));
        Assert.Throws<OperationCanceledException>(() => ModelExtract.ReadMaterialInputSignature(
            new FailingLoader(new OperationCanceledException()), Outfit));
    }

    [Theory]
    [InlineData("required.vmesh")]
    [InlineData("required.vphys")]
    [InlineData("required.vanim")]
    [InlineData("required.vagrp")]
    [InlineData("required.vmdl")]
    public void RequiredDependenciesStillFailWithTheirFilename(string path)
    {
        var type = typeof(CompiledAssetRecovery).GetNestedType("Loader", BindingFlags.NonPublic)!;
        using var loader = (IDisposable)Activator.CreateInstance(type,
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
            new object[] { new Dictionary<string, string>(), CancellationToken.None }, null)!;
        var error = Assert.Throws<FileNotFoundException>(() => ((IFileLoader)loader).LoadFileCompiled(path));
        Assert.Equal(path, error.FileName);
        Assert.Contains(path, error.Message);
    }

    [CompiledFixtureFact]
    public void MissingMaterialFilesDoNotBlockRecoveryOrEraseMaterialAssignments()
    {
        var fixture = Environment.GetEnvironmentVariable("HR_SMART_PORT_FIXTURE")!;
        var paths = Directory.GetFiles(fixture, "*_c", SearchOption.AllDirectories)
            .Where(p => !p.EndsWith(".vmat_c", StringComparison.OrdinalIgnoreCase))
            .ToDictionary(p => Path.GetRelativePath(fixture, p)[..^2].Replace('\\', '/'), p => p, StringComparer.OrdinalIgnoreCase);
        const string model = "models/player/human/frank_mp.vmdl";
        var output = Path.Combine(Path.GetTempPath(), "hr-smart-port-no-materials-" + Guid.NewGuid().ToString("N"));
        var text = CompiledAssetRecovery.Recover(paths[model], model, output, "recovered", paths, default);
        using var resource = new Resource();
        resource.Read(paths[model]);
        Assert.NotNull(resource.ExternalReferences);
        var materials = resource.ExternalReferences.ResourceRefInfoList.Where(r => r.Name.EndsWith(".vmat")).Select(r => r.Name).ToArray();
        Assert.NotEmpty(materials);
        var files = Directory.GetFiles(output, "*.dmx", SearchOption.AllDirectories);
        Assert.Equal(398, files.Length); // Seven meshes and all 391 animation sources.
        var meshText = string.Join("\n", files.Where(f => Path.GetFileName(f).StartsWith("frank_mp_")).Select(f => Encoding.UTF8.GetString(File.ReadAllBytes(f))));
        foreach (var material in materials) Assert.Contains(material, text + meshText);
    }
}
using Sandbox;

public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

/*
[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
*/
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Sandbox.States.Test;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	public static Sandbox.TestAppSystem AppSystem;

	[AssemblyInitialize]
	public static void AssemblyInitialize( TestContext context )
	{
		AppSystem = new Sandbox.TestAppSystem();
		AppSystem.Init();
	}

	[AssemblyCleanup]
	public static void AssemblyCleanup()
	{
		AppSystem.Shutdown();
	}
}
#nullable enable

using System;
using System.Linq;
using Hexagon.V2.Infrastructure;
using Hexagon.V2.Persistence;

namespace Hexagon.V2.Tests.Infrastructure;

[TestClass]
public sealed class PrefixedPersistenceStorageTests
{
	[TestMethod]
	public async Task EveryOperationIsMappedBelowTheIsolatedPrefix()
	{
		var physical = new InMemoryPersistenceStorage();
		var isolated = new PrefixedPersistenceStorage( physical, "verification/run-42" );

		Assert.IsTrue( await isolated.TryWriteImmutableAsync( "hexagon/v2/schema/format.json", new byte[] { 1, 2 } ) );
		Assert.IsTrue( await physical.ExistsAsync( "verification/run-42/hexagon/v2/schema/format.json" ) );
		Assert.IsFalse( await physical.ExistsAsync( "hexagon/v2/schema/format.json" ) );

		var listed = await isolated.ListAsync( "hexagon/v2/schema" );
		Assert.HasCount( 1, listed );
		Assert.AreEqual( "hexagon/v2/schema/format.json", listed.Single() );
	}

	[TestMethod]
	public void PrefixRejectsParentAndCurrentDirectorySegments()
	{
		var storage = new InMemoryPersistenceStorage();
		Assert.ThrowsExactly<ArgumentException>( () => new PrefixedPersistenceStorage( storage, "../escape" ) );
		Assert.ThrowsExactly<ArgumentException>( () => new PrefixedPersistenceStorage( storage, "safe/./escape" ) );
	}
}
#nullable enable

using Hexagon.V2.Domain;
using Hexagon.V2.Kernel;
using Hexagon.V2.Networking;

namespace Hexagon.V2.Tests.Networking;

[TestClass]
public sealed class ClientPayloadLimitsTests
{
	[TestMethod]
	public void NullReferencePayloadsFailClosedInsteadOfEscapingAdmissionCleanup()
	{
		Assert.AreEqual(
			ErrorCode.InvalidArgument,
			ClientPayloadLimits.Validate( new CreateCharacterCommand( null! ) ).Error!.Code );
		Assert.AreEqual(
			ErrorCode.InvalidArgument,
			ClientPayloadLimits.Validate( new BeginInteractionCommand( null! ) ).Error!.Code );
	}

	[TestMethod]
	public void MapEntryAndPerStringBoundariesAreEnforced()
	{
		var accepted = Enumerable.Range( 0, ClientPayloadLimits.MaximumMapEntries )
			.ToDictionary( index => $"k{index}", _ => SnapshotValue.String( "x" ), StringComparer.Ordinal );
		var rejected = new Dictionary<string, SnapshotValue>( accepted, StringComparer.Ordinal )
		{
			["overflow"] = SnapshotValue.String( "x" )
		};

		Assert.IsTrue( ClientPayloadLimits.Validate( new RunSchemaCommandCommand( "test", accepted ) ).Succeeded );
		Assert.AreEqual( ErrorCode.InvalidArgument,
			ClientPayloadLimits.Validate( new RunSchemaCommandCommand( "test", rejected ) ).Error!.Code );
		Assert.IsTrue( ClientPayloadLimits.Validate(
			new SendChatCommand( "ic", new string( 'a', ClientPayloadLimits.MaximumStringCharacters ) ) ).Succeeded );
		Assert.IsTrue( ClientPayloadLimits.Validate(
			new SendChatCommand( "ic", new string( 'a', ClientPayloadLimits.MaximumStringCharacters + 1 ) ) ).Failed );
	}

	[TestMethod]
	public void InvalidUnicodeAndAggregateUtf8OverflowAreRejected()
	{
		var invalidUnicode = new SendChatCommand( "ic", "\ud800" );
		var fields = new Dictionary<string, SnapshotValue>( StringComparer.Ordinal );
		for ( var index = 0; index < 5; index++ )
			fields[$"key{index}"] = SnapshotValue.String( new string( '\u0800', 4096 ) );

		Assert.IsTrue( ClientPayloadLimits.Validate( invalidUnicode ).Failed );
		Assert.IsTrue( ClientPayloadLimits.Validate(
			new RunSchemaCommandCommand( "expensive", fields ) ).Failed );
	}

	[TestMethod]
	public void CreationIdentifiersContributeToAggregateUtf8Budget()
	{
		var fields = Enumerable.Range( 0, 4 ).ToDictionary(
			index => $"field{index}",
			_ => SnapshotValue.String( new string( 'x', 4090 ) ),
			StringComparer.Ordinal );
		var input = new CharacterCreationInput(
			"name",
			"description",
			new DefinitionId( new string( 'm', 96 ) ),
			new FactionId( new string( 'f', 96 ) ),
			new ClassId( new string( 'c', 96 ) ),
			fields );

		Assert.IsTrue( ClientPayloadLimits.Validate( new CreateCharacterCommand( input ) ).Failed );
	}
}
using Hexagon.V2.Runtime;

namespace Hexagon.V2.Tests.Runtime;

[TestClass]
public sealed class SpawnCandidateSelectionTests
{
	[TestMethod]
	public void NamespacedSpawnsWinOutrightOverMapSpawns()
	{
		// A mounted map ships its own spawn points; a schema-authored one must take every slot,
		// or players are distributed onto arbitrary map geometry.
		var candidates = new[]
		{
			new SpawnCandidate( Guid.Parse( "00000000-0000-0000-0000-0000000000a1" ), false ),
			new SpawnCandidate( Guid.Parse( "00000000-0000-0000-0000-0000000000a2" ), true ),
			new SpawnCandidate( Guid.Parse( "00000000-0000-0000-0000-0000000000a3" ), false )
		};

		CollectionAssert.AreEqual( new[] { 1 }, SpawnCandidateSelection.Prefer( candidates ) );

		Assert.IsTrue( SpawnCandidateSelection.TrySelect( candidates, 0, out var first ) );
		Assert.AreEqual( 1, first.CandidateIndex );
		Assert.IsTrue( SpawnCandidateSelection.TrySelect( candidates, 7, out var later ) );
		Assert.AreEqual( 1, later.CandidateIndex, "Every slot must land on the only namespaced spawn." );
	}

	[TestMethod]
	public void WithoutNamespacedSpawnsEveryPointIsUsedInIdOrder()
	{
		// ID ordering, not scene order, so the same slot resolves to the same point across hosts
		// and across restarts.
		var candidates = new[]
		{
			new SpawnCandidate( Guid.Parse( "00000000-0000-0000-0000-0000000000c0" ), false ),
			new SpawnCandidate( Guid.Parse( "00000000-0000-0000-0000-0000000000a0" ), false ),
			new SpawnCandidate( Guid.Parse( "00000000-0000-0000-0000-0000000000b0" ), false )
		};

		CollectionAssert.AreEqual( new[] { 1, 2, 0 }, SpawnCandidateSelection.Prefer( candidates ) );
		Assert.IsTrue( SpawnCandidateSelection.TrySelect( candidates, 0, out var zero ) );
		Assert.AreEqual( 1, zero.CandidateIndex );
		Assert.IsTrue( SpawnCandidateSelection.TrySelect( candidates, 1, out var one ) );
		Assert.AreEqual( 2, one.CandidateIndex );
		Assert.IsTrue( SpawnCandidateSelection.TrySelect( candidates, 2, out var two ) );
		Assert.AreEqual( 0, two.CandidateIndex );
	}

	[TestMethod]
	public void SlotsBeyondTheSpawnCountOverflowIntoRingsRatherThanStacking()
	{
		var candidates = new[]
		{
			new SpawnCandidate( Guid.Parse( "00000000-0000-0000-0000-0000000000a1" ), true ),
			new SpawnCandidate( Guid.Parse( "00000000-0000-0000-0000-0000000000a2" ), true )
		};

		Assert.IsTrue( SpawnCandidateSelection.TrySelect( candidates, 0, out var first ) );
		Assert.AreEqual( 0, first.Placement.Ring, "The first layer sits on the point itself." );

		// Two points, so slot 2 wraps to the first point on the next layer out.
		Assert.IsTrue( SpawnCandidateSelection.TrySelect( candidates, 2, out var wrapped ) );
		Assert.AreEqual( 0, wrapped.CandidateIndex );
		Assert.IsGreaterThan( 0, wrapped.Placement.Ring );
		Assert.IsGreaterThan( 0.0, wrapped.Placement.Radius, "An overflow slot must be offset, not stacked." );
	}

	[TestMethod]
	public void AnEmptySceneSelectsNothingRatherThanThrowing()
	{
		Assert.IsFalse( SpawnCandidateSelection.TrySelect( Array.Empty<SpawnCandidate>(), 0, out _ ) );
	}
}