fix(portal): commit destination projection before reveal
Retail installs the destination CObjCell before hidden world simulation resumes. Rebucket the retained local-player projection inside the Place transaction so Hidden/UnHide scripts and particles settle behind the portal viewport instead of firing after reveal. Preserve the existing unloaded-cell scheduler gate and pin the ordering with a cross-landblock placement test. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
1a14812c44
commit
7aa9618136
4 changed files with 173 additions and 1 deletions
|
|
@ -464,6 +464,94 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
spatial.Snapshot.ControllerRotation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConcretePlacement_CommitsDestinationSpatialBucketBeforeReconcile()
|
||||
{
|
||||
const uint guid = 0x50000001u;
|
||||
const uint sourceCell = 0x20210001u;
|
||||
const uint destinationCell = 0x30310001u;
|
||||
var world = new GpuWorldState();
|
||||
world.AddLandblock(new LoadedLandblock(
|
||||
sourceCell & 0xFFFF0000u | 0xFFFFu,
|
||||
new DatReaderWriter.DBObjs.LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
world.AddLandblock(new LoadedLandblock(
|
||||
destinationCell & 0xFFFF0000u | 0xFFFFu,
|
||||
new DatReaderWriter.DBObjs.LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
var runtime = new LiveEntityRuntime(world, new NullResources());
|
||||
runtime.RegisterLiveEntity(Spawn(guid, sourceCell));
|
||||
WorldEntity entity = runtime.MaterializeLiveEntity(
|
||||
guid,
|
||||
sourceCell,
|
||||
id => new WorldEntity
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = guid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
})!;
|
||||
var identity = new LocalPlayerIdentityState { ServerGuid = guid };
|
||||
var controllerSlot = new LocalPlayerControllerSlot
|
||||
{
|
||||
Controller = new PlayerMovementController(new PhysicsEngine()),
|
||||
};
|
||||
controllerSlot.Controller.SetPosition(
|
||||
Vector3.Zero,
|
||||
sourceCell,
|
||||
Vector3.Zero);
|
||||
var cameras = new ChaseCameraInputState
|
||||
{
|
||||
Legacy = new ChaseCamera(),
|
||||
Retail = new RetailChaseCamera(),
|
||||
};
|
||||
var origin = new LiveWorldOriginState();
|
||||
origin.SetPlaceholder(0x30, 0x31);
|
||||
LiveEntityRecord? recordAtReconcile = null;
|
||||
var spatial = new FakeSpatialReconcile(() =>
|
||||
{
|
||||
Assert.True(runtime.TryGetRecord(guid, out recordAtReconcile));
|
||||
return new PlacementSnapshot(
|
||||
entity.Position,
|
||||
entity.ParentCellId ?? 0u,
|
||||
entity.Rotation,
|
||||
controllerSlot.Controller.Position,
|
||||
controllerSlot.Controller.CellId,
|
||||
controllerSlot.Controller.BodyOrientation);
|
||||
});
|
||||
var placement = new LocalPlayerTeleportPlacement(
|
||||
new PhysicsEngine(),
|
||||
runtime,
|
||||
identity,
|
||||
controllerSlot,
|
||||
new LocalPlayerPhysicsHostSlot(),
|
||||
cameras,
|
||||
origin,
|
||||
spatial);
|
||||
|
||||
placement.Place(
|
||||
new Vector3(12f, 24f, 6f),
|
||||
destinationCell,
|
||||
Quaternion.Identity);
|
||||
|
||||
Assert.NotNull(recordAtReconcile);
|
||||
uint resolvedDestinationCell = controllerSlot.Controller.CellId;
|
||||
Assert.Equal(
|
||||
destinationCell & 0xFFFF0000u,
|
||||
resolvedDestinationCell & 0xFFFF0000u);
|
||||
Assert.Equal(resolvedDestinationCell, recordAtReconcile.FullCellId);
|
||||
Assert.Equal(
|
||||
destinationCell & 0xFFFF0000u | 0xFFFFu,
|
||||
recordAtReconcile.CanonicalLandblockId);
|
||||
Assert.True(recordAtReconcile.IsSpatiallyProjected);
|
||||
Assert.True(recordAtReconcile.IsSpatiallyVisible);
|
||||
Assert.Contains(entity, world.Entities);
|
||||
Assert.Equal(resolvedDestinationCell, entity.ParentCellId);
|
||||
Assert.Equal(1, spatial.Count);
|
||||
}
|
||||
|
||||
private static int Index(IReadOnlyList<string> events, string prefix)
|
||||
{
|
||||
for (int i = 0; i < events.Count; i++)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue