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

@ -1522,6 +1522,32 @@ internal sealed class RuntimeSetPositionState : IDisposable
.RetrySetupUnavailable;
}
RuntimeSetPositionMoverPreparation effectivePreparation = preparation;
if (preparation.ResolveWorldOffsetFromRuntimeFrame)
{
// CreateObject/Position origins are local to their authored
// landblock. Resolve them through Runtime's accepted world frame,
// which is initialized by the local-player Create and advanced
// only by authoritative teleport transitions. This is available
// before streaming publishes the target collision generation, so
// remote admission cannot starve world loading while still using
// the exact same coordinate frame that generation will publish.
if (!_physics.TryGetWorldFrameOffset(
authority.AcceptedPosition.LandblockId,
out float worldOffsetX,
out float worldOffsetY))
{
return RuntimeSetPositionMoverPreparationStatus
.RetrySetupUnavailable;
}
effectivePreparation = preparation with
{
ShadowWorldOffsetX = worldOffsetX,
ShadowWorldOffsetY = worldOffsetY,
};
}
if (!RuntimeSetPositionMoverPreparer.TryBuild(
operation.Record,
authority.AcceptedPosition,
@ -1529,7 +1555,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
operation.Kind,
operation.Portal,
authority.VelocityAuthorityVersion,
preparation,
effectivePreparation,
out command)
|| !IsStructurallyValid(command.Physics))
{
@ -1586,7 +1612,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
float scatterRadiusY = 0f,
uint scatterAttempts = 0u,
float shadowWorldOffsetX = 0f,
float shadowWorldOffsetY = 0f)
float shadowWorldOffsetY = 0f,
bool resolveWorldOffsetFromRuntimeFrame = false)
{
outcome = default;
@ -1606,7 +1633,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
scatterRadiusY,
scatterAttempts,
shadowWorldOffsetX,
shadowWorldOffsetY);
shadowWorldOffsetY,
resolveWorldOffsetFromRuntimeFrame);
if (status != RuntimeSetPositionMoverPreparationStatus.Prepared)
return status;
@ -1644,7 +1672,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
float scatterRadiusY = 0f,
uint scatterAttempts = 0u,
float shadowWorldOffsetX = 0f,
float shadowWorldOffsetY = 0f)
float shadowWorldOffsetY = 0f,
bool resolveWorldOffsetFromRuntimeFrame = false)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(record);
@ -1683,7 +1712,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
scatterAttempts,
shadowWorldOffsetX,
shadowWorldOffsetY,
portal);
portal,
resolveWorldOffsetFromRuntimeFrame);
return PrepareMover(token, preparation, out command);
}