acdream/tests/AcDream.App.Tests/Input/PlayerMovementPlacementTransactionTests.cs
Erik eeb0f6b45c refactor(runtime): extract local teleport and player mode
Move local teleport, player-mode, animation, shadow, and sealed-dungeon lifetimes out of GameWindow. Make player-mode entry transactional and isolate approach completions by controller lifetime and approach generation so stale callbacks cannot affect replacements.

Co-authored-by: Codex <noreply@openai.com>
2026-07-22 02:50:15 +02:00

93 lines
3.3 KiB
C#

using System.Numerics;
using AcDream.App.Input;
using AcDream.App.Physics;
using AcDream.Core.Physics;
using AcDream.Core.Physics.Motion;
using DatReaderWriter.Types;
using DatEnvCell = DatReaderWriter.DBObjs.EnvCell;
namespace AcDream.App.Tests.Input;
public sealed class PlayerMovementPlacementTransactionTests
{
private const uint PlayerGuid = 0x5000_0001u;
private const uint TargetGuid = 0x7000_0001u;
private const uint PriorCell = 0x0101_0101u;
private const uint DestinationCell = 0x0101_0102u;
[Fact]
public void PreparedPosition_DoesNotPublishRenderRootOrUnstickUntilCommit()
{
PhysicsEngine physics = PhysicsWithCells(PriorCell, DestinationCell);
physics.UpdatePlayerCurrCell(PriorCell);
var controller = new PlayerMovementController(physics);
var hosts = new Dictionary<uint, IPhysicsObjHost>();
IPhysicsObjHost? Resolve(uint id) => hosts.GetValueOrDefault(id);
EntityPhysicsHost player = Host(PlayerGuid, Resolve);
EntityPhysicsHost target = Host(TargetGuid, Resolve);
hosts.Add(PlayerGuid, player);
hosts.Add(TargetGuid, target);
controller.PositionManager = player.PositionManager;
player.PositionManager.StickTo(TargetGuid, 0.5f, 1.8f);
controller.PreparePositionForCommit(
new Vector3(2f, 3f, 4f),
DestinationCell,
new Vector3(2f, 3f, 4f));
Assert.Equal(DestinationCell, controller.CellId);
Assert.Equal(PriorCell, physics.DataCache!.CellGraph.CurrCell!.Id);
Assert.Equal(TargetGuid, player.PositionManager.GetStickyObjectId());
controller.CommitPreparedPosition();
Assert.Equal(DestinationCell, physics.DataCache.CellGraph.CurrCell!.Id);
Assert.Equal(0u, player.PositionManager.GetStickyObjectId());
}
private static PhysicsEngine PhysicsWithCells(params uint[] cellIds)
{
var physics = new PhysicsEngine { DataCache = new PhysicsDataCache() };
foreach (uint cellId in cellIds)
{
var cellStruct = new CellStruct
{
VertexArray = new VertexArray
{
Vertices = new Dictionary<ushort, SWVertex>(),
},
Polygons = new Dictionary<ushort, Polygon>(),
};
var envCell = new DatEnvCell
{
CellPortals = [],
VisibleCells = [],
};
physics.DataCache.CacheCellStruct(
cellId,
envCell,
cellStruct,
Matrix4x4.Identity);
}
return physics;
}
private static EntityPhysicsHost Host(
uint guid,
Func<uint, IPhysicsObjHost?> resolve) =>
new(
guid,
getPosition: () => new AcDream.Core.Physics.Position(
PriorCell,
Vector3.Zero,
Quaternion.Identity),
getVelocity: () => Vector3.Zero,
getRadius: () => 0.5f,
inContact: () => true,
minterpMaxSpeed: () => 1f,
curTime: () => 0d,
physicsTimerTime: () => 0d,
getObjectA: resolve,
handleUpdateTarget: _ => { },
interruptCurrentMovement: () => { });
}