Removes all [PLAYER], [PLAYER-INIT], [PLAYER-ANIM] diagnostic dump lines now that walking, camera, and animation are verified working. Updates PhysicsEngineTests.Resolve_EnterIndoorCell to match the new behavior (outdoor→indoor transition disabled in the B.2 MVP): the test now asserts the player stays outdoor at terrain Z instead of transitioning to the indoor cell. 265 tests green. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
170 lines
6 KiB
C#
170 lines
6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using AcDream.Core.Physics;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Tests.Physics;
|
|
|
|
public class PhysicsEngineTests
|
|
{
|
|
private static float[] LinearHeightTable()
|
|
{
|
|
var table = new float[256];
|
|
for (int i = 0; i < 256; i++) table[i] = i * 1.0f;
|
|
return table;
|
|
}
|
|
|
|
private static byte[] FlatHeightmap(byte value = 50)
|
|
{
|
|
var heights = new byte[81];
|
|
Array.Fill(heights, value);
|
|
return heights;
|
|
}
|
|
|
|
private PhysicsEngine MakeFlatEngine(float terrainZ = 50f)
|
|
{
|
|
var engine = new PhysicsEngine();
|
|
var terrain = new TerrainSurface(FlatHeightmap((byte)terrainZ), LinearHeightTable());
|
|
engine.AddLandblock(0xA9B4FFFFu, terrain, Array.Empty<CellSurface>(),
|
|
worldOffsetX: 0f, worldOffsetY: 0f);
|
|
return engine;
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_FlatTerrain_ZMatchesTerrain()
|
|
{
|
|
var engine = MakeFlatEngine(terrainZ: 50f);
|
|
|
|
var result = engine.Resolve(
|
|
new Vector3(96f, 96f, 50f), cellId: 0x0001, delta: new Vector3(1f, 0f, 0f),
|
|
stepUpHeight: 2f);
|
|
|
|
Assert.Equal(50f, result.Position.Z, precision: 1);
|
|
Assert.True(result.IsOnGround);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_WalkUpSmallSlope_Accepted()
|
|
{
|
|
// Heights slope from 50 to 52 across X — small enough for step height.
|
|
var heights = new byte[81];
|
|
for (int x = 0; x < 9; x++)
|
|
for (int y = 0; y < 9; y++)
|
|
heights[x * 9 + y] = (byte)(50 + x / 4); // gentle slope
|
|
|
|
var engine = new PhysicsEngine();
|
|
var terrain = new TerrainSurface(heights, LinearHeightTable());
|
|
engine.AddLandblock(0xA9B4FFFFu, terrain, Array.Empty<CellSurface>(),
|
|
worldOffsetX: 0f, worldOffsetY: 0f);
|
|
|
|
var result = engine.Resolve(
|
|
new Vector3(48f, 96f, 50f), cellId: 0x0001, delta: new Vector3(48f, 0f, 0f),
|
|
stepUpHeight: 5f);
|
|
|
|
Assert.True(result.IsOnGround);
|
|
Assert.True(result.Position.Z >= 50f); // moved uphill
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_StepUpExceedsHeight_MovementBlocked()
|
|
{
|
|
// Heights jump sharply: left half = 50, right half = 100.
|
|
var heights = new byte[81];
|
|
for (int x = 0; x < 9; x++)
|
|
for (int y = 0; y < 9; y++)
|
|
heights[x * 9 + y] = (byte)(x < 5 ? 50 : 100);
|
|
|
|
var engine = new PhysicsEngine();
|
|
var terrain = new TerrainSurface(heights, LinearHeightTable());
|
|
engine.AddLandblock(0xA9B4FFFFu, terrain, Array.Empty<CellSurface>(),
|
|
worldOffsetX: 0f, worldOffsetY: 0f);
|
|
|
|
// Try to walk from the low side to the high side.
|
|
var result = engine.Resolve(
|
|
new Vector3(96f, 96f, 50f), cellId: 0x0001, delta: new Vector3(48f, 0f, 0f),
|
|
stepUpHeight: 2f);
|
|
|
|
// Movement should be blocked — Z delta (50→100) exceeds step height (2).
|
|
Assert.Equal(96f, result.Position.X, precision: 1); // didn't move
|
|
Assert.True(result.IsOnGround);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_EnterIndoorCell_StaysOutdoor_BecauseTransitionDisabled()
|
|
{
|
|
// Phase B.2 MVP: outdoor→indoor transitions are disabled because
|
|
// CellSurface floor polygons are too aggressive (building
|
|
// footprints/roofs capture the player). Walking over a cell's XY
|
|
// area stays on the outdoor terrain Z. Indoor transitions will be
|
|
// re-enabled when portal-based detection lands in Phase E.
|
|
var engine = new PhysicsEngine();
|
|
var terrain = new TerrainSurface(FlatHeightmap(50), LinearHeightTable());
|
|
|
|
var cellVerts = new Dictionary<ushort, Vector3>
|
|
{
|
|
[0] = new(40f, 40f, 55f),
|
|
[1] = new(60f, 40f, 55f),
|
|
[2] = new(60f, 60f, 55f),
|
|
[3] = new(40f, 60f, 55f),
|
|
};
|
|
var cellPolys = new List<List<short>> { new() { 0, 1, 2, 3 } };
|
|
var cell = new CellSurface(0x0100, cellVerts, cellPolys);
|
|
|
|
engine.AddLandblock(0xA9B4FFFFu, terrain, new[] { cell },
|
|
worldOffsetX: 0f, worldOffsetY: 0f);
|
|
|
|
// Walk from outdoor (30, 50) into the cell's floor area (50, 50).
|
|
var result = engine.Resolve(
|
|
new Vector3(30f, 50f, 50f), cellId: 0x0001, delta: new Vector3(20f, 0f, 0f),
|
|
stepUpHeight: 10f);
|
|
|
|
// Should stay outdoor (transition disabled) at terrain Z = 50.
|
|
Assert.True(result.CellId < 0x0100u);
|
|
Assert.Equal(50f, result.Position.Z, precision: 1);
|
|
Assert.True(result.IsOnGround);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_LeaveIndoorCell_TransitionsToOutdoor()
|
|
{
|
|
var engine = new PhysicsEngine();
|
|
var terrain = new TerrainSurface(FlatHeightmap(50), LinearHeightTable());
|
|
|
|
var cellVerts = new Dictionary<ushort, Vector3>
|
|
{
|
|
[0] = new(40f, 40f, 55f),
|
|
[1] = new(60f, 40f, 55f),
|
|
[2] = new(60f, 60f, 55f),
|
|
[3] = new(40f, 60f, 55f),
|
|
};
|
|
var cellPolys = new List<List<short>> { new() { 0, 1, 2, 3 } };
|
|
var cell = new CellSurface(0x0100, cellVerts, cellPolys);
|
|
|
|
engine.AddLandblock(0xA9B4FFFFu, terrain, new[] { cell },
|
|
worldOffsetX: 0f, worldOffsetY: 0f);
|
|
|
|
// Start inside the cell, walk out.
|
|
var result = engine.Resolve(
|
|
new Vector3(50f, 50f, 55f), cellId: 0x0100, delta: new Vector3(-20f, 0f, 0f),
|
|
stepUpHeight: 10f);
|
|
|
|
// Should transition back to outdoor.
|
|
Assert.True(result.CellId < 0x0100u);
|
|
Assert.Equal(50f, result.Position.Z, precision: 1);
|
|
Assert.True(result.IsOnGround);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_NoSurfaceUnderEntity_NotOnGround()
|
|
{
|
|
var engine = new PhysicsEngine();
|
|
// No landblocks loaded — entity is floating in void.
|
|
|
|
var result = engine.Resolve(
|
|
new Vector3(0f, 0f, 100f), cellId: 0x0001, delta: Vector3.Zero,
|
|
stepUpHeight: 2f);
|
|
|
|
Assert.False(result.IsOnGround);
|
|
}
|
|
}
|