fix(physics): keep remote placement and targeting in one world frame

CreateObject positions are landblock-local, but Runtime first-entry previously submitted remotes with a zero world offset. Runtime now owns the accepted local-player world-frame center and converts remote placements before SetPosition. The local physics host also publishes body.Position rather than CellPosition's landblock-local origin, so TargetManager no longer directs monsters toward a phantom player position. User gate: monster/static placement, chase, and attacks accepted outside Tusker Barracks.
This commit is contained in:
Erik 2026-08-03 08:59:31 +02:00
parent 01f4791e95
commit 670f307c84
7 changed files with 140 additions and 12 deletions

View file

@ -556,6 +556,18 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Func<RuntimeEntityRecord, Exception?>? retirePriorProjection)
{
EnsureNotDisposed();
if (beginInitialResidence
&& isLocalPlayer
&& (incoming.Physics?.Position ?? incoming.Position)
is { LandblockId: not 0u } initialPlayerPosition)
{
// The accepted local Create establishes the shared world frame
// before any remote first-entry conductor converts its authored
// landblock-local coordinates.
Physics.ObserveLocalWorldFrame(
initialPlayerPosition.LandblockId,
teleportAdvanced: false);
}
if (_sessionClearInProgress)
{
throw new InvalidOperationException(
@ -1508,6 +1520,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
if (!deferredKnown)
return false;
if (isLocalPlayer
&& disposition is not PositionTimestampDisposition.Rejected)
{
Physics.ObserveLocalWorldFrame(
update.Position.LandblockId,
timestamps.TeleportAdvanced);
}
if (disposition is PositionTimestampDisposition.Rejected
&& !timestampMutation)
{
@ -1556,6 +1576,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
return known;
}
if (isLocalPlayer
&& disposition is not PositionTimestampDisposition.Rejected)
{
Physics.ObserveLocalWorldFrame(
update.Position.LandblockId,
timestamps.TeleportAdvanced);
}
bool acceptedPosition =
disposition is not PositionTimestampDisposition.Rejected;
if (disposition is PositionTimestampDisposition.Apply)