fix(physics): hold retail cell across inner retries

This commit is contained in:
Erik 2026-07-31 12:40:50 +02:00
parent 67d1e9b331
commit 4ca7230b36
4 changed files with 168 additions and 83 deletions

View file

@ -385,33 +385,97 @@ public sealed class PhysicsEngine
float localX = worldX - lb.WorldOffsetX;
float localY = worldY - lb.WorldOffsetY;
if (localX >= 0f && localX < 192f && localY >= 0f && localY < 192f)
{
var sample = lb.Terrain.SampleSurfacePolygon(localX, localY);
var vertices = new TerrainTriangleVertices(
OffsetTerrainVertex(sample.Vertices.V0, lb),
OffsetTerrainVertex(sample.Vertices.V1, lb),
OffsetTerrainVertex(sample.Vertices.V2, lb));
var normal = sample.Normal;
float d = -Vector3.Dot(normal, vertices[0]);
var plane = new System.Numerics.Plane(normal, d);
float waterDepth = lb.Terrain.SampleWaterDepth(localX, localY);
bool isWater = waterDepth >= 0.45f;
uint lowCellId = lb.Terrain.ComputeOutdoorCellId(localX, localY);
uint fullCellId = (kvp.Key & 0xFFFF0000u) | lowCellId;
return new TerrainWalkableSample(
plane,
vertices,
waterDepth,
isWater,
fullCellId);
}
return BuildTerrainWalkableSample(kvp.Key, lb, localX, localY);
}
return null;
}
/// <summary>
/// Samples only the fixed outdoor cell supplied to retail
/// <c>CTransition::insert_into_cell</c>. The target point may move into a
/// neighboring cell during a retry, but retail continues dispatching the
/// captured <c>CObjCell*</c> until that inner call returns.
/// </summary>
internal TerrainWalkableSample? SampleTerrainWalkableInCell(
uint cellId,
float worldX,
float worldY)
{
uint lowCellId = cellId & 0xFFFFu;
if (lowCellId is < 1u or > 0x40u)
return null;
foreach (var kvp in _landblocks)
{
uint requestedPrefix = cellId & 0xFFFF0000u;
if (requestedPrefix != 0u &&
(kvp.Key & 0xFFFF0000u) != requestedPrefix)
continue;
LandblockPhysics lb = kvp.Value;
float localX = worldX - lb.WorldOffsetX;
float localY = worldY - lb.WorldOffsetY;
if (requestedPrefix == 0u &&
(localX < 0f || localX >= 192f ||
localY < 0f || localY >= 192f))
{
continue;
}
int cellIndex = (int)lowCellId - 1;
int cellX = cellIndex / TerrainSurface.CellsPerSide;
int cellY = cellIndex % TerrainSurface.CellsPerSide;
float minX = cellX * TerrainSurface.CellSize;
float minY = cellY * TerrainSurface.CellSize;
float maxX = minX + TerrainSurface.CellSize;
float maxY = minY + TerrainSurface.CellSize;
if (localX < minX || localX >= maxX ||
localY < minY || localY >= maxY)
{
return null;
}
return BuildTerrainWalkableSample(
kvp.Key,
lb,
localX,
localY);
}
return null;
}
private static TerrainWalkableSample BuildTerrainWalkableSample(
uint landblockId,
LandblockPhysics landblock,
float localX,
float localY)
{
TerrainSurfacePolygon sample = landblock.Terrain.SampleSurfacePolygon(
localX,
localY);
var vertices = new TerrainTriangleVertices(
OffsetTerrainVertex(sample.Vertices.V0, landblock),
OffsetTerrainVertex(sample.Vertices.V1, landblock),
OffsetTerrainVertex(sample.Vertices.V2, landblock));
Vector3 normal = sample.Normal;
float d = -Vector3.Dot(normal, vertices[0]);
var plane = new System.Numerics.Plane(normal, d);
float waterDepth = landblock.Terrain.SampleWaterDepth(localX, localY);
bool isWater = waterDepth >= 0.45f;
uint lowCellId = landblock.Terrain.ComputeOutdoorCellId(localX, localY);
uint fullCellId = (landblockId & 0xFFFF0000u) | lowCellId;
return new TerrainWalkableSample(
plane,
vertices,
waterDepth,
isWater,
fullCellId);
}
private static Vector3 OffsetTerrainVertex(Vector3 vertex, LandblockPhysics landblock)
=> new(
vertex.X + landblock.WorldOffsetX,
@ -466,11 +530,11 @@ public sealed class PhysicsEngine
}
/// <summary>
/// TEST-ONLY outdoor cell re-derive. The single caller is
/// <c>Transition.FindEnvCollisions</c>'s cache-null fallback
/// TEST-ONLY outdoor cell re-derive. The sole caller is
/// <c>Transition.RunCheckOtherCellsAndAdvance</c>'s cache-null fallback
/// (PhysicsEngineTests run engines without a <see cref="DataCache"/>,
/// so <see cref="CellTransit.FindCellSet"/> is unavailable). Production
/// membership flows exclusively through the collide-then-pick advance
/// so <see cref="CellTransit.FindCellSet"/> is unavailable). Normal
/// production membership flows exclusively through the collide-then-pick advance
/// (<c>RunCheckOtherCellsAndAdvance</c> → <c>FindCellSet</c>).
///
/// <para>