fix(physics): preserve refreshed cell retry state
This commit is contained in:
parent
e5f855ac40
commit
67d1e9b331
3 changed files with 183 additions and 11 deletions
|
|
@ -70,6 +70,7 @@ public sealed class PhysicsEngine
|
||||||
/// response coupling.
|
/// response coupling.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Func<
|
internal Func<
|
||||||
|
Transition,
|
||||||
TransitionCellCollisionPhase,
|
TransitionCellCollisionPhase,
|
||||||
uint,
|
uint,
|
||||||
TransitionState,
|
TransitionState,
|
||||||
|
|
|
||||||
|
|
@ -1728,6 +1728,11 @@ public sealed class Transition
|
||||||
/// The former flat per-landblock object query is gone.
|
/// The former flat per-landblock object query is gone.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
internal TransitionState TransitionalInsertForTest(
|
||||||
|
int numAttempts,
|
||||||
|
PhysicsEngine engine)
|
||||||
|
=> TransitionalInsert(numAttempts, engine);
|
||||||
|
|
||||||
private TransitionState TransitionalInsert(int numAttempts, PhysicsEngine engine)
|
private TransitionState TransitionalInsert(int numAttempts, PhysicsEngine engine)
|
||||||
{
|
{
|
||||||
if (SpherePath.CheckCellId == 0) return TransitionState.OK;
|
if (SpherePath.CheckCellId == 0) return TransitionState.OK;
|
||||||
|
|
@ -2133,7 +2138,7 @@ public sealed class Transition
|
||||||
TransitionState state = TransitionState.OK;
|
TransitionState state = TransitionState.OK;
|
||||||
for (int attempt = 0; attempt < numAttempts; attempt++)
|
for (int attempt = 0; attempt < numAttempts; attempt++)
|
||||||
{
|
{
|
||||||
state = FindPrimaryCellCollisions(engine, cellId, attempt);
|
state = FindPrimaryCellCollisions(engine, attempt);
|
||||||
if (state is TransitionState.OK or TransitionState.Collided)
|
if (state is TransitionState.OK or TransitionState.Collided)
|
||||||
return state;
|
return state;
|
||||||
|
|
||||||
|
|
@ -2154,41 +2159,42 @@ public sealed class Transition
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private TransitionState FindPrimaryCellCollisions(
|
private TransitionState FindPrimaryCellCollisions(
|
||||||
PhysicsEngine engine,
|
PhysicsEngine engine,
|
||||||
uint cellId,
|
|
||||||
int innerAttempt)
|
int innerAttempt)
|
||||||
{
|
{
|
||||||
|
TransitionState actualEnvironment = FindEnvCollisions(engine);
|
||||||
|
uint currentCellId = SpherePath.CheckCellId;
|
||||||
TransitionState environment = ObservePrimaryCellPhase(
|
TransitionState environment = ObservePrimaryCellPhase(
|
||||||
engine,
|
engine,
|
||||||
TransitionCellCollisionPhase.Environment,
|
TransitionCellCollisionPhase.Environment,
|
||||||
cellId,
|
currentCellId,
|
||||||
FindEnvCollisions(engine));
|
actualEnvironment);
|
||||||
if (environment != TransitionState.OK)
|
if (environment != TransitionState.OK)
|
||||||
return environment;
|
return environment;
|
||||||
|
|
||||||
TransitionState building = ObservePrimaryCellPhase(
|
TransitionState building = ObservePrimaryCellPhase(
|
||||||
engine,
|
engine,
|
||||||
TransitionCellCollisionPhase.Building,
|
TransitionCellCollisionPhase.Building,
|
||||||
cellId,
|
currentCellId,
|
||||||
FindBuildingCollisions(engine, cellId));
|
FindBuildingCollisions(engine, currentCellId));
|
||||||
if (building != TransitionState.OK)
|
if (building != TransitionState.OK)
|
||||||
return building;
|
return building;
|
||||||
|
|
||||||
TransitionState objects = ObservePrimaryCellPhase(
|
TransitionState objects = ObservePrimaryCellPhase(
|
||||||
engine,
|
engine,
|
||||||
TransitionCellCollisionPhase.Objects,
|
TransitionCellCollisionPhase.Objects,
|
||||||
cellId,
|
currentCellId,
|
||||||
FindObjCollisionsInCell(engine, cellId));
|
FindObjCollisionsInCell(engine, currentCellId));
|
||||||
DumpPhase2(innerAttempt, environment, objects);
|
DumpPhase2(innerAttempt, environment, objects);
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TransitionState ObservePrimaryCellPhase(
|
private TransitionState ObservePrimaryCellPhase(
|
||||||
PhysicsEngine engine,
|
PhysicsEngine engine,
|
||||||
TransitionCellCollisionPhase phase,
|
TransitionCellCollisionPhase phase,
|
||||||
uint cellId,
|
uint cellId,
|
||||||
TransitionState actual)
|
TransitionState actual)
|
||||||
=> engine.TransitionCellCollisionTestHook is { } hook
|
=> engine.TransitionCellCollisionTestHook is { } hook
|
||||||
? hook(phase, cellId, actual)
|
? hook(this, phase, cellId, actual)
|
||||||
: actual;
|
: actual;
|
||||||
|
|
||||||
private TransitionState EdgeSlideAfterStepDownFailed(
|
private TransitionState EdgeSlideAfterStepDownFailed(
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public sealed class TransitionInsertIntoCellRetryTests
|
||||||
int buildingCalls = 0;
|
int buildingCalls = 0;
|
||||||
int objectCalls = 0;
|
int objectCalls = 0;
|
||||||
|
|
||||||
engine.TransitionCellCollisionTestHook = (phase, cellId, actual) =>
|
engine.TransitionCellCollisionTestHook = (_, phase, cellId, actual) =>
|
||||||
{
|
{
|
||||||
Assert.Equal(Cell, cellId);
|
Assert.Equal(Cell, cellId);
|
||||||
Assert.Equal(TransitionState.OK, actual);
|
Assert.Equal(TransitionState.OK, actual);
|
||||||
|
|
@ -109,6 +109,148 @@ public sealed class TransitionInsertIntoCellRetryTests
|
||||||
+ "outer N=3 budget while remaining inside retail's N×N budget.");
|
+ "outer N=3 budget while remaining inside retail's N×N budget.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NestedRetry_AlwaysAdjusted_ExhaustsExactNByNBudget()
|
||||||
|
{
|
||||||
|
PhysicsEngine engine = BuildEngine(preparedFlat: false);
|
||||||
|
int calls = 0;
|
||||||
|
engine.TransitionCellCollisionTestHook =
|
||||||
|
(_, phase, cellId, actual) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(TransitionCellCollisionPhase.Environment, phase);
|
||||||
|
Assert.Equal(Cell, cellId);
|
||||||
|
Assert.Equal(TransitionState.OK, actual);
|
||||||
|
calls++;
|
||||||
|
return TransitionState.Adjusted;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector3 current = new(10f, 10f, 5f);
|
||||||
|
Vector3 target = current + new Vector3(0.05f, 0f, 0f);
|
||||||
|
Transition transition = BSPStepUpFixtures.MakeAirborneTransition(
|
||||||
|
current,
|
||||||
|
target,
|
||||||
|
Cell);
|
||||||
|
transition.SpherePath.SetCheckPos(target, Cell);
|
||||||
|
TransitionState result = transition.TransitionalInsertForTest(3, engine);
|
||||||
|
|
||||||
|
Assert.Equal(TransitionState.Adjusted, result);
|
||||||
|
Assert.Equal(target, transition.SpherePath.CheckPos);
|
||||||
|
Assert.Equal(9, calls);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NestedRetry_SlidClearsInnerContact_ThenOuterNegPoly()
|
||||||
|
{
|
||||||
|
PhysicsEngine engine = BuildEngine(preparedFlat: false);
|
||||||
|
int environmentCalls = 0;
|
||||||
|
var contactPlane = new Plane(Vector3.UnitZ, -5f);
|
||||||
|
|
||||||
|
engine.TransitionCellCollisionTestHook =
|
||||||
|
(transition, phase, _, actual) =>
|
||||||
|
{
|
||||||
|
if (phase != TransitionCellCollisionPhase.Environment)
|
||||||
|
return actual;
|
||||||
|
|
||||||
|
environmentCalls++;
|
||||||
|
if (environmentCalls <= 3)
|
||||||
|
{
|
||||||
|
if (environmentCalls > 1)
|
||||||
|
{
|
||||||
|
Assert.False(transition.CollisionInfo.ContactPlaneValid);
|
||||||
|
Assert.False(transition.CollisionInfo.ContactPlaneIsWater);
|
||||||
|
Assert.True(transition.SpherePath.NegPolyHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.CollisionInfo.SetContactPlane(
|
||||||
|
contactPlane,
|
||||||
|
Cell,
|
||||||
|
isWater: true);
|
||||||
|
transition.SpherePath.NegPolyHit = true;
|
||||||
|
return TransitionState.Slid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Three Slid responses exhaust the first inner N=3 budget.
|
||||||
|
// Its final Slid clear removes contact/water; the outer
|
||||||
|
// transitional_insert boundary additionally removes neg-poly.
|
||||||
|
Assert.False(transition.CollisionInfo.ContactPlaneValid);
|
||||||
|
Assert.False(transition.CollisionInfo.ContactPlaneIsWater);
|
||||||
|
Assert.False(transition.SpherePath.NegPolyHit);
|
||||||
|
return actual;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector3 current = new(10f, 10f, 5f);
|
||||||
|
ResolveResult result = ResolveAirborne(
|
||||||
|
engine,
|
||||||
|
current,
|
||||||
|
current + new Vector3(0.05f, 0f, 0f),
|
||||||
|
Cell);
|
||||||
|
|
||||||
|
Assert.True(result.Ok);
|
||||||
|
Assert.Equal(4, environmentCalls);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NoDataCache_CrossLandblockRepick_UsesRefreshedPrimaryCell()
|
||||||
|
{
|
||||||
|
PhysicsEngine engine = BuildCrossLandblockEngine();
|
||||||
|
var cells = new List<uint>();
|
||||||
|
var phases = new List<TransitionCellCollisionPhase>();
|
||||||
|
engine.TransitionCellCollisionTestHook =
|
||||||
|
(_, phase, cellId, actual) =>
|
||||||
|
{
|
||||||
|
phases.Add(phase);
|
||||||
|
cells.Add(cellId);
|
||||||
|
return actual;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector3 current = new(191.99f, 10f, 5f);
|
||||||
|
Vector3 target = new(192.01f, 10f, 5f);
|
||||||
|
ResolveResult result = ResolveAirborne(
|
||||||
|
engine,
|
||||||
|
current,
|
||||||
|
target,
|
||||||
|
0xA9B40039u);
|
||||||
|
|
||||||
|
Assert.True(result.Ok);
|
||||||
|
Assert.Equal(target, result.Position);
|
||||||
|
Assert.Equal(0xAAB40001u, result.CellId);
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
TransitionCellCollisionPhase.Environment,
|
||||||
|
TransitionCellCollisionPhase.Building,
|
||||||
|
TransitionCellCollisionPhase.Objects,
|
||||||
|
},
|
||||||
|
phases);
|
||||||
|
Assert.All(cells, cellId => Assert.Equal(0xAAB40001u, cellId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResolveResult ResolveAirborne(
|
||||||
|
PhysicsEngine engine,
|
||||||
|
Vector3 current,
|
||||||
|
Vector3 target,
|
||||||
|
uint cellId)
|
||||||
|
{
|
||||||
|
var body = new PhysicsBody
|
||||||
|
{
|
||||||
|
Position = current,
|
||||||
|
Orientation = Quaternion.Identity,
|
||||||
|
TransientState = TransientStateFlags.Active,
|
||||||
|
};
|
||||||
|
return engine.ResolveWithTransition(
|
||||||
|
current,
|
||||||
|
target,
|
||||||
|
cellId,
|
||||||
|
sphereRadius: 0.48f,
|
||||||
|
sphereHeight: 1.835f,
|
||||||
|
stepUpHeight: 0.6f,
|
||||||
|
stepDownHeight: 1.5f,
|
||||||
|
isOnGround: false,
|
||||||
|
body,
|
||||||
|
moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
|
||||||
|
movingEntityId: 0x000F4243u);
|
||||||
|
}
|
||||||
|
|
||||||
private static PhysicsEngine BuildEngine(bool preparedFlat)
|
private static PhysicsEngine BuildEngine(bool preparedFlat)
|
||||||
{
|
{
|
||||||
var (root, resolved) = BSPStepUpFixtures.FlatRoof();
|
var (root, resolved) = BSPStepUpFixtures.FlatRoof();
|
||||||
|
|
@ -166,4 +308,27 @@ public sealed class TransitionInsertIntoCellRetryTests
|
||||||
0f);
|
0f);
|
||||||
return engine;
|
return engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PhysicsEngine BuildCrossLandblockEngine()
|
||||||
|
{
|
||||||
|
var engine = new PhysicsEngine { DataCache = null };
|
||||||
|
var heights = new byte[81];
|
||||||
|
var heightTable = new float[256];
|
||||||
|
Array.Fill(heightTable, -1000f);
|
||||||
|
engine.AddLandblock(
|
||||||
|
0xA9B4FFFFu,
|
||||||
|
new TerrainSurface(heights, heightTable),
|
||||||
|
Array.Empty<CellSurface>(),
|
||||||
|
Array.Empty<PortalPlane>(),
|
||||||
|
0f,
|
||||||
|
0f);
|
||||||
|
engine.AddLandblock(
|
||||||
|
0xAAB4FFFFu,
|
||||||
|
new TerrainSurface(heights, heightTable),
|
||||||
|
Array.Empty<CellSurface>(),
|
||||||
|
Array.Empty<PortalPlane>(),
|
||||||
|
192f,
|
||||||
|
0f);
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue