perf(physics): reuse reset-complete transition scratch

Mirror retail's ten-deep LIFO transition lifetime, retain all query scratch with complete reset contracts, and remove Tier-0 enum boxing without changing collision decisions. Fresh and retained engines are bit-identical across the expanded oracle, while measured transition profiles now allocate 0 bytes per resolve.
This commit is contained in:
Erik 2026-07-25 14:37:02 +02:00
parent 2effba5127
commit 16d182c2f0
19 changed files with 3037 additions and 1228 deletions

View file

@ -6,7 +6,28 @@ namespace AcDream.Core.Physics;
public readonly record struct TerrainSurfacePolygon(
float Z,
Vector3 Normal,
Vector3[] Vertices);
TerrainTriangleVertices Vertices);
/// <summary>
/// The terrain surface beneath one point is always exactly one triangle.
/// Keeping its three vertices inline avoids allocating two short arrays for
/// every transition substep while retaining the same vertex order and floats.
/// </summary>
public readonly record struct TerrainTriangleVertices(
Vector3 V0,
Vector3 V1,
Vector3 V2)
{
public int Length => 3;
public Vector3 this[int index] => index switch
{
0 => V0,
1 => V1,
2 => V2,
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};
}
/// <summary>
/// Outdoor terrain height resolver for a single landblock. Performs
@ -27,8 +48,8 @@ public sealed class TerrainSurface
public const int CellsPerSide = 8; // 192 / 24
private readonly float[,] _z; // pre-resolved heights [x, y]
private readonly bool[,] _cornerIsWater; // per-VERTEX water flag [x, y] — SurfChar[(type >> 2) & 0x1F]
private readonly byte[,] _cellWaterType; // per-CELL 0=NotWater, 1=Partially, 2=Entirely [cx, cy]
private readonly bool[,] _cornerIsWater; // per-VERTEX water flag [x, y] — SurfChar[(type >> 2) & 0x1F]
private readonly byte[,] _cellWaterType; // per-CELL 0=NotWater, 1=Partially, 2=Entirely [cx, cy]
private readonly uint _landblockX;
private readonly uint _landblockY;
@ -49,8 +70,8 @@ public sealed class TerrainSurface
// Pre-resolve all 81 heights so SampleZ is a pure lookup + lerp.
_z = new float[HeightmapSide, HeightmapSide];
for (int x = 0; x < HeightmapSide; x++)
for (int y = 0; y < HeightmapSide; y++)
_z[x, y] = heightTable[heights[x * HeightmapSide + y]];
for (int y = 0; y < HeightmapSide; y++)
_z[x, y] = heightTable[heights[x * HeightmapSide + y]];
// Per-vertex water flag. TerrainType lives in bits 2-6 of each
// TerrainInfo byte; water is types 0x10-0x14 inclusive (per
@ -62,11 +83,11 @@ public sealed class TerrainSurface
if (terrainTypes is not null && terrainTypes.Length >= 81)
{
for (int x = 0; x < HeightmapSide; x++)
for (int y = 0; y < HeightmapSide; y++)
{
int typeBits = (terrainTypes[x * HeightmapSide + y] >> 2) & 0x1F;
_cornerIsWater[x, y] = typeBits >= 0x10 && typeBits <= 0x14;
}
for (int y = 0; y < HeightmapSide; y++)
{
int typeBits = (terrainTypes[x * HeightmapSide + y] >> 2) & 0x1F;
_cornerIsWater[x, y] = typeBits >= 0x10 && typeBits <= 0x14;
}
}
// Per-cell water classification (mirrors ACE
@ -74,21 +95,21 @@ public sealed class TerrainSurface
// vertex corners; count how many are water type.
_cellWaterType = new byte[CellsPerSide, CellsPerSide];
for (int cx = 0; cx < CellsPerSide; cx++)
for (int cy = 0; cy < CellsPerSide; cy++)
{
int waterCorners = 0;
if (_cornerIsWater[cx, cy ]) waterCorners++;
if (_cornerIsWater[cx + 1, cy ]) waterCorners++;
if (_cornerIsWater[cx + 1, cy + 1]) waterCorners++;
if (_cornerIsWater[cx, cy + 1]) waterCorners++;
_cellWaterType[cx, cy] = waterCorners switch
for (int cy = 0; cy < CellsPerSide; cy++)
{
0 => 0, // NotWater
4 => 2, // EntirelyWater
_ => 1, // PartiallyWater
};
}
int waterCorners = 0;
if (_cornerIsWater[cx, cy]) waterCorners++;
if (_cornerIsWater[cx + 1, cy]) waterCorners++;
if (_cornerIsWater[cx + 1, cy + 1]) waterCorners++;
if (_cornerIsWater[cx, cy + 1]) waterCorners++;
_cellWaterType[cx, cy] = waterCorners switch
{
0 => 0, // NotWater
4 => 2, // EntirelyWater
_ => 1, // PartiallyWater
};
}
}
/// <summary>
@ -134,10 +155,10 @@ public sealed class TerrainSurface
float ty = fy - cy;
// Four corner heights (BL=SW, BR=SE, TR=NE, TL=NW)
float hBL = _z[cx, cy ];
float hBR = _z[cx + 1, cy ];
float hBL = _z[cx, cy];
float hBR = _z[cx + 1, cy];
float hTR = _z[cx + 1, cy + 1];
float hTL = _z[cx, cy + 1];
float hTL = _z[cx, cy + 1];
// Split direction — same formula as TerrainBlending.CalculateSplitDirection
// and ACE's LandblockStruct.ConstructPolygons.
@ -189,10 +210,10 @@ public sealed class TerrainSurface
// x-major heightmap indexing matches TerrainSurface's pre-resolution
// (heights[x * 9 + y]) and ACE LandblockStruct.
float hBL = heightTable[heights[cx * HeightmapSide + cy ]];
float hBR = heightTable[heights[(cx+1) * HeightmapSide + cy ]];
float hTR = heightTable[heights[(cx+1) * HeightmapSide + (cy+1)]];
float hTL = heightTable[heights[cx * HeightmapSide + (cy+1)]];
float hBL = heightTable[heights[cx * HeightmapSide + cy]];
float hBR = heightTable[heights[(cx + 1) * HeightmapSide + cy]];
float hTR = heightTable[heights[(cx + 1) * HeightmapSide + (cy + 1)]];
float hTL = heightTable[heights[cx * HeightmapSide + (cy + 1)]];
bool splitSWtoNE = IsSplitSWtoNE(landblockX, (uint)cx, landblockY, (uint)cy);
return InterpolateZInTriangle(hBL, hBR, hTR, hTL, tx, ty, splitSWtoNE);
@ -227,10 +248,10 @@ public sealed class TerrainSurface
float tx = fx - cx;
float ty = fy - cy;
float hBL = heightTable[heights[cx * HeightmapSide + cy ]];
float hBR = heightTable[heights[(cx+1) * HeightmapSide + cy ]];
float hTR = heightTable[heights[(cx+1) * HeightmapSide + (cy+1)]];
float hTL = heightTable[heights[cx * HeightmapSide + (cy+1)]];
float hBL = heightTable[heights[cx * HeightmapSide + cy]];
float hBR = heightTable[heights[(cx + 1) * HeightmapSide + cy]];
float hTR = heightTable[heights[(cx + 1) * HeightmapSide + (cy + 1)]];
float hTL = heightTable[heights[cx * HeightmapSide + (cy + 1)]];
bool splitSWtoNE = IsSplitSWtoNE(landblockX, (uint)cx, landblockY, (uint)cy);
@ -332,10 +353,10 @@ public sealed class TerrainSurface
float tx = fx - cx;
float ty = fy - cy;
float hBL = _z[cx, cy ];
float hBR = _z[cx + 1, cy ];
float hBL = _z[cx, cy];
float hBR = _z[cx + 1, cy];
float hTR = _z[cx + 1, cy + 1];
float hTL = _z[cx, cy + 1];
float hTL = _z[cx, cy + 1];
bool splitSWtoNE = IsSplitSWtoNE(_landblockX, (uint)cx, _landblockY, (uint)cy);
@ -352,14 +373,14 @@ public sealed class TerrainSurface
if (tx > ty)
{
// {BL,BR,TR}: Z = hBL + (hBR-hBL)·tx + (hTR-hBR)·ty
z = hBL + (hBR - hBL) * tx + (hTR - hBR) * ty;
z = hBL + (hBR - hBL) * tx + (hTR - hBR) * ty;
dzdx = (hBR - hBL) / CellSize;
dzdy = (hTR - hBR) / CellSize;
}
else
{
// {BL,TR,TL}: Z = hBL + (hTR-hTL)·tx + (hTL-hBL)·ty
z = hBL + (hTR - hTL) * tx + (hTL - hBL) * ty;
z = hBL + (hTR - hTL) * tx + (hTL - hBL) * ty;
dzdx = (hTR - hTL) / CellSize;
dzdy = (hTL - hBL) / CellSize;
}
@ -370,7 +391,7 @@ public sealed class TerrainSurface
if (tx + ty <= 1f)
{
// {BL,BR,TL}: Z = hBL + (hBR-hBL)·tx + (hTL-hBL)·ty
z = hBL + (hBR - hBL) * tx + (hTL - hBL) * ty;
z = hBL + (hBR - hBL) * tx + (hTL - hBL) * ty;
dzdx = (hBR - hBL) / CellSize;
dzdy = (hTL - hBL) / CellSize;
}
@ -378,7 +399,7 @@ public sealed class TerrainSurface
{
// {BR,TR,TL}: Z = hTR + (hTL-hTR)(1-tx) + (hBR-hTR)(1-ty)
// Equivalent linear form: Z = [hBR+hTL-hTR] + (hTR-hTL)·tx + (hTR-hBR)·ty
z = hTR + (hTL - hTR) * (1f - tx) + (hBR - hTR) * (1f - ty);
z = hTR + (hTL - hTR) * (1f - tx) + (hBR - hTR) * (1f - ty);
dzdx = (hTR - hTL) / CellSize;
dzdy = (hTR - hBR) / CellSize;
}
@ -405,32 +426,32 @@ public sealed class TerrainSurface
float tx = fx - cx;
float ty = fy - cy;
float hBL = _z[cx, cy ];
float hBR = _z[cx + 1, cy ];
float hBL = _z[cx, cy];
float hBR = _z[cx + 1, cy];
float hTR = _z[cx + 1, cy + 1];
float hTL = _z[cx, cy + 1];
float hTL = _z[cx, cy + 1];
bool splitSWtoNE = IsSplitSWtoNE(_landblockX, (uint)cx, _landblockY, (uint)cy);
Vector3 bl = new(cx * CellSize, cy * CellSize, hBL);
Vector3 br = new((cx + 1) * CellSize, cy * CellSize, hBR);
Vector3 bl = new(cx * CellSize, cy * CellSize, hBL);
Vector3 br = new((cx + 1) * CellSize, cy * CellSize, hBR);
Vector3 tr = new((cx + 1) * CellSize, (cy + 1) * CellSize, hTR);
Vector3 tl = new(cx * CellSize, (cy + 1) * CellSize, hTL);
Vector3 tl = new(cx * CellSize, (cy + 1) * CellSize, hTL);
float z;
Vector3[] vertices;
TerrainTriangleVertices vertices;
if (splitSWtoNE)
{
if (tx > ty)
{
z = hBL + (hBR - hBL) * tx + (hTR - hBR) * ty;
vertices = new[] { bl, br, tr };
vertices = new TerrainTriangleVertices(bl, br, tr);
}
else
{
z = hBL + (hTR - hTL) * tx + (hTL - hBL) * ty;
vertices = new[] { bl, tr, tl };
vertices = new TerrainTriangleVertices(bl, tr, tl);
}
}
else
@ -438,12 +459,12 @@ public sealed class TerrainSurface
if (tx + ty <= 1f)
{
z = hBL + (hBR - hBL) * tx + (hTL - hBL) * ty;
vertices = new[] { bl, br, tl };
vertices = new TerrainTriangleVertices(bl, br, tl);
}
else
{
z = hTR + (hTL - hTR) * (1f - tx) + (hBR - hTR) * (1f - ty);
vertices = new[] { br, tr, tl };
vertices = new TerrainTriangleVertices(br, tr, tl);
}
}