docs(physics): pin Slice I collision oracle
This commit is contained in:
parent
f1a8d36682
commit
624e1119ca
3 changed files with 827 additions and 0 deletions
|
|
@ -0,0 +1,206 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter.Types;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Slice I0 evidence harness for the four production
|
||||
/// <see cref="PhysicsEngine.ResolveWithTransition"/> call shapes. This is not a
|
||||
/// budget assertion: I1 deliberately changes the expected allocation from the
|
||||
/// recorded graph-path baseline to zero steady transition/query-scratch bytes.
|
||||
/// The harness stays in-tree so the before/after figure is reproducible.
|
||||
/// </summary>
|
||||
public sealed class TransitionAllocationBaselineTests
|
||||
{
|
||||
private const int WarmupIterations = 256;
|
||||
private const int MeasuredIterations = 4_096;
|
||||
private const uint CellId = 0xA9B40001u;
|
||||
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public TransitionAllocationBaselineTests(ITestOutputHelper output)
|
||||
=> _output = output;
|
||||
|
||||
[Fact]
|
||||
public void GraphPath_RecordsPerResolveAllocationForEveryMoverFamily()
|
||||
{
|
||||
var (root, resolved) = BSPStepUpFixtures.TallWall();
|
||||
var engine = BuildEngine(root, resolved);
|
||||
|
||||
long player = Measure(() => ResolvePlayer(engine));
|
||||
long remote = Measure(() => ResolveRemote(engine));
|
||||
long projectile = Measure(() => ResolveProjectile(engine));
|
||||
long camera = Measure(() => ResolveCamera(engine));
|
||||
|
||||
_output.WriteLine(
|
||||
$"Slice I0 graph-path allocation baseline ({MeasuredIterations:N0} resolves/profile):");
|
||||
_output.WriteLine($" player: {player,8:N0} B/resolve");
|
||||
_output.WriteLine($" remote: {remote,8:N0} B/resolve");
|
||||
_output.WriteLine($" projectile: {projectile,8:N0} B/resolve");
|
||||
_output.WriteLine($" camera: {camera,8:N0} B/resolve");
|
||||
|
||||
// The current graph path constructs a Transition object graph and
|
||||
// CollisionSphere helpers on every resolve. I1 replaces that lifetime
|
||||
// shape; this lower bound merely proves the baseline capture actually
|
||||
// observed those allocations instead of an optimized-away call.
|
||||
Assert.All(
|
||||
new[] { player, remote, projectile, camera },
|
||||
bytes => Assert.True(
|
||||
bytes > 0,
|
||||
"The I0 baseline must observe the current per-resolve allocation."));
|
||||
}
|
||||
|
||||
private static long Measure(Func<ResolveResult> resolve)
|
||||
{
|
||||
ResolveResult sink = default;
|
||||
for (int i = 0; i < WarmupIterations; i++)
|
||||
sink = resolve();
|
||||
|
||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
||||
for (int i = 0; i < MeasuredIterations; i++)
|
||||
sink = resolve();
|
||||
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
||||
|
||||
GC.KeepAlive(sink);
|
||||
return allocated / MeasuredIterations;
|
||||
}
|
||||
|
||||
private static ResolveResult ResolvePlayer(PhysicsEngine engine)
|
||||
{
|
||||
var body = Bodies.Player;
|
||||
ResetBody(body, PhysicsStateFlags.Gravity);
|
||||
return engine.ResolveWithTransition(
|
||||
currentPos: new Vector3(0.10f, 0f, 0.20f),
|
||||
targetPos: new Vector3(0.36f, 0f, 0.20f),
|
||||
cellId: CellId,
|
||||
sphereRadius: BSPStepUpFixtures.SphereRadius,
|
||||
sphereHeight: 1.20f,
|
||||
stepUpHeight: 0.60f,
|
||||
stepDownHeight: 1.50f,
|
||||
isOnGround: true,
|
||||
body: body,
|
||||
moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
|
||||
movingEntityId: 0x5000000Au);
|
||||
}
|
||||
|
||||
private static ResolveResult ResolveRemote(PhysicsEngine engine)
|
||||
{
|
||||
var body = Bodies.Remote;
|
||||
ResetBody(body, PhysicsStateFlags.Gravity);
|
||||
return engine.ResolveWithTransition(
|
||||
currentPos: new Vector3(0.10f, 0f, 0.20f),
|
||||
targetPos: new Vector3(0.36f, 0f, 0.20f),
|
||||
cellId: CellId,
|
||||
sphereRadius: BSPStepUpFixtures.SphereRadius,
|
||||
sphereHeight: 1.20f,
|
||||
stepUpHeight: 0.60f,
|
||||
stepDownHeight: 1.50f,
|
||||
isOnGround: true,
|
||||
body: body,
|
||||
moverFlags: ObjectInfoState.EdgeSlide,
|
||||
movingEntityId: 0x80000001u);
|
||||
}
|
||||
|
||||
private static ResolveResult ResolveProjectile(PhysicsEngine engine)
|
||||
{
|
||||
var body = Bodies.Projectile;
|
||||
ResetBody(body, PhysicsStateFlags.Missile);
|
||||
return engine.ResolveWithTransition(
|
||||
currentPos: new Vector3(0.10f, 0f, 2.00f),
|
||||
targetPos: new Vector3(0.36f, 0f, 2.00f),
|
||||
cellId: CellId,
|
||||
sphereRadius: 0.05f,
|
||||
sphereHeight: 0f,
|
||||
stepUpHeight: 0f,
|
||||
stepDownHeight: 0f,
|
||||
isOnGround: false,
|
||||
body: body,
|
||||
movingEntityId: 0x80000002u);
|
||||
}
|
||||
|
||||
private static ResolveResult ResolveCamera(PhysicsEngine engine)
|
||||
=> engine.ResolveWithTransition(
|
||||
currentPos: new Vector3(0.10f, 0f, 2.00f),
|
||||
targetPos: new Vector3(0.36f, 0f, 2.00f),
|
||||
cellId: CellId,
|
||||
sphereRadius: 0.30f,
|
||||
sphereHeight: 0f,
|
||||
stepUpHeight: 0f,
|
||||
stepDownHeight: 0f,
|
||||
isOnGround: false,
|
||||
body: null,
|
||||
moverFlags: ObjectInfoState.IsViewer
|
||||
| ObjectInfoState.PathClipped
|
||||
| ObjectInfoState.FreeRotate
|
||||
| ObjectInfoState.PerfectClip);
|
||||
|
||||
private static void ResetBody(PhysicsBody body, PhysicsStateFlags state)
|
||||
{
|
||||
body.State = state;
|
||||
body.TransientState = TransientStateFlags.Active;
|
||||
body.ContactPlaneValid = false;
|
||||
body.WalkablePolygonValid = false;
|
||||
body.WalkableVertices = null;
|
||||
body.SlidingNormal = Vector3.Zero;
|
||||
body.FramesStationaryFall = 0;
|
||||
}
|
||||
|
||||
private static PhysicsEngine BuildEngine(
|
||||
PhysicsBSPNode root,
|
||||
Dictionary<ushort, ResolvedPolygon> resolved)
|
||||
{
|
||||
var heights = new byte[81];
|
||||
var heightTable = new float[256];
|
||||
Array.Fill(heightTable, -50f);
|
||||
|
||||
var engine = new PhysicsEngine();
|
||||
engine.AddLandblock(
|
||||
0xA9B4FFFFu,
|
||||
new TerrainSurface(heights, heightTable),
|
||||
Array.Empty<CellSurface>(),
|
||||
Array.Empty<PortalPlane>(),
|
||||
0f,
|
||||
0f);
|
||||
|
||||
const uint gfxObjId = 0x0100F100u;
|
||||
var cache = new PhysicsDataCache();
|
||||
cache.RegisterGfxObjForTest(gfxObjId, new GfxObjPhysics
|
||||
{
|
||||
BSP = new PhysicsBSPTree { Root = root },
|
||||
PhysicsPolygons = new Dictionary<ushort, Polygon>(),
|
||||
Vertices = new VertexArray(),
|
||||
Resolved = resolved,
|
||||
BoundingSphere = new Sphere
|
||||
{
|
||||
Origin = new Vector3(0f, 0f, 2.5f),
|
||||
Radius = 10f,
|
||||
},
|
||||
});
|
||||
engine.DataCache = cache;
|
||||
engine.ShadowObjects.Register(
|
||||
entityId: gfxObjId,
|
||||
gfxObjId: gfxObjId,
|
||||
worldPos: Vector3.Zero,
|
||||
rotation: Quaternion.Identity,
|
||||
radius: 10f,
|
||||
worldOffsetX: 0f,
|
||||
worldOffsetY: 0f,
|
||||
landblockId: 0xA9B4FFFFu,
|
||||
collisionType: ShadowCollisionType.BSP,
|
||||
scale: 1f);
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
private static class Bodies
|
||||
{
|
||||
internal static readonly PhysicsBody Player = new();
|
||||
internal static readonly PhysicsBody Remote = new();
|
||||
internal static readonly PhysicsBody Projectile = new();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue