feat(physics): C4 route 4b-3 — remote teleport + cell-less through the canonical placement

Flips the last remote classification (SetPosition: teleport-advanced and
cell-less) onto 4b-1's RuntimeRemotePlacementDriveController, runs retail's
teleport_hook before the placement, and deletes the legacy remote-teleport
machinery. Contract: docs/research/2026-08-04-c4-route-4b-3-contract.md.

Retail: MoveOrTeleport @0x00516330's branch @0x00516386 -> teleport_hook
@0x005163EF -> SetFlags(0x1012) @0x00516414 -> SetPosition @0x00516420 ->
return 1 @0x00516438. The hook @0x00514ED0 runs BEFORE the placement and
regardless of its outcome. Retail places this branch unconditionally, at any
distance and any contact state (arg4 is read only @0x0051638E, after the
branch) — which is what retires AP-137's cell-less enqueue-vs-place delta.

D1 — the classifier's cell-less input is now the PRE-merge committed cell.
Retail's predicate is `this_1->cell == 0`, the BODY's own cell at
MoveOrTeleport entry (this_1 is assigned from this @0x00516334). acdream fed
the POST-merge canonical.FullCellId, which RefreshSnapshot ->
RefreshDerivedState -> SetFullCell has already stamped with the accepted wire
cell; a zero wire cell fails validation into RejectedData first. The shipped
remote cell-less predicate was therefore dead code, not merely different from
remotePlacementRequired. Threaded via a builder overload; route 1's overload
is untouched. The graphical !IsSpatiallyVisible arm of
projectionRequiresTeleportHook is deleted — a presentation predicate with no
retail analogue that fired the teleport machinery on a routine hot path.

Deleted: RemoteTeleportController (605), RemoteTeleportPlacement (85),
RemoteShadowPlacementSynchronizer (49), their 1,709 lines of tests, the
remotePlacementRequired predicate, the TeleportHookRequired plumbing, the
legacy pre-operation ConstrainTo fallback, and the player arm's legacy
!IsGrounded fallback. Net -2,030 lines.

Structural fix (two independent Opus reviews, round 1 FAIL/FAIL): three of the
four MAJORs were one defect — OnPosition carried two parallel inline copies of
the routing tail (player-guid, NPC-guid) that had drifted. Extracted
RunRemoteArmTail (3 call sites) and ApplyWireAirborneLeftoverBookkeeping (2),
both branches now share one implementation.

  A1  ToConstraintArm mapped AirborneSnap -> AirborneNoOperation, so the NPC
      arm armed ConstrainTo ZERO times for an out-of-contact wire-grounded
      creature — a regression this slice introduced while closing a
      structurally identical hole. Now maps to NearInterpolate; switch made
      total with a throwing default proven unreachable.
  R1  D2's write-nothing shape existed on the player arm only; NPC packets
      fell through and wrote the body. Retail makes no player/NPC distinction.
  R2  report_collision_end(this,1) @0x00514F31 was bound to
      ShadowObjects.Suspend, a port of a DIFFERENT retail function
      (remove_shadows_from_cells) that teleport_hook never calls. Now routes
      to RuntimeCollisionReportingState.LeaveWorld, which wraps the private
      ForceEnd in an admission-blocking transaction so a DoCollisionEnd
      callback cannot recreate the contact table.
  R3/A2 A teleported NPC synthesized ServerVelocity from the teleport distance
      (~1,000+ m/s) and planned a run cycle from it. Both the install and
      RemoteServerControlledVelocityCycle.Apply now gate on !isTeleportRoute.

BISECT HAZARD — A1's fix is correct only BECAUSE R1 landed. AirborneSnap is
reachable wire-airborne on the NPC arm only while D2's shape is missing there.
Reverting R1 alone silently inverts A1 into the opposite divergence: arming
where retail returns 0. Revert both or neither.

Also in the velocity hunk: the NPC block's two !IsPlayerGuid(update.Guid)
guards were dropped when it was wrapped in `if (!isTeleportRoute)`. Safe — all
five exit paths of the enclosing IsPlayerGuid block return, so the predicate is
unconditionally false below it — but it was unremarked by both reviews.

Register: AP-137 REWRITTEN (not deleted) to the surviving acdream-only
divergences — null classification during the login window and Rejected*
through UnroutedCatchUp keep a row. AD-42's RemoteTeleportController citation
retired; AP-136/AP-138 writer lists corrected to the two surviving non-Position
rebucket writers; AP-138 gains the teleport arm as a second producer of the
visible-without-collision residual (retirement path remains #309). AP-135 is
untouched and its two airborne bookkeeping writes are preserved on both arms.
AP-131 does not retire; #276 does not close.

Proof obligation 1: ParkCollisionResidents' overlap throw stays unreachable —
the teleport arm adds packets to the same TryBeginExclusiveAuthoredPlacement
one-operation-per-key machinery the far arm uses, opens no new operation shape,
and every DeferredCell outcome cancels synchronously with
restoreCancelledPark: true. The guarded property remains
HasOldPrefixPlacementDebt's stall, not a throw (4b-1's B2 caveat stands).

Correction to an earlier claim: LiveEntityPresentationController's
_activePlacementOwners was NOT write-never at HEAD —
remotePlacementRequired -> BeginPlacement -> Begin -> BeginAuthoritativePlacement
was a live writer chain. It becomes write-never BECAUSE this slice deletes that
chain, which is why deleting the dead half is behaviour-preserving.

Probe: ACDREAM_PROBE_REMOTE_TELEPORT=1 emits one [remote-teleport] line per
routed arm (guid, cause, hook-ran, placement status). TEMPORARY, strip with the
probe family.

Carried, disclosed not fixed: no dedicated bidirectional collision-partner test
for R2 (the wiring, not LeaveWorld itself, is what lacks coverage); the
stress test's teleport step drives hand-written field assignments rather than
the canonical arm; the per-packet runTeleportHook closure allocation (network
path, not the resolve path Slice I's 0 B discipline governs — file before
route 5 adds a fourth call site). B2: IRuntimeCollisionReportObserver has zero
production implementations, so retail's bidirectional DoCollisionEnd half still
reaches no gameplay consumer — this fix closes the wrong-function binding, not
that nobody listens.

Complete Release suite MEASURED at 11,013 passed / 4 skipped / 0 failed
(baseline 11,027/4/0; net -14 = ~33 deleted test cases against ~19 added).
Neither known flake fired (#302 PortalProjectionTests GC-allocation, #308
NakEmissionTests wall-clock).

STILL OWED: the two-client connected gate, which MUST use an NPC/creature
teleport target. Both round-1 MAJORs lived on the NPC arm and the velocity
cycle early-returns for 0x50xxxxxx guids, so a player target structurally
cannot observe A1, A2, or R3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-04 16:00:10 +02:00
parent 3e002993dd
commit 6dc7ba51ee
53 changed files with 2980 additions and 3372 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,49 +0,0 @@
using AcDream.App.World;
using AcDream.Core.Physics;
using AcDream.Core.World;
namespace AcDream.App.Physics;
/// <summary>Synchronizes a resolved remote placement in the current live origin.</summary>
internal sealed class RemoteShadowPlacementSynchronizer
{
private readonly RemotePhysicsUpdater _remotePhysics;
private readonly LiveWorldOriginState _origin;
public RemoteShadowPlacementSynchronizer(
RemotePhysicsUpdater remotePhysics,
LiveWorldOriginState origin)
{
_remotePhysics = remotePhysics
?? throw new ArgumentNullException(nameof(remotePhysics));
_origin = origin ?? throw new ArgumentNullException(nameof(origin));
}
public void Sync(WorldEntity entity, PhysicsBody body, uint cellId) =>
_remotePhysics.SyncRemoteShadowToBody(
entity.Id,
body,
_origin.CenterX,
_origin.CenterY,
cellId);
}
/// <summary>Bridges remote placement ownership to live presentation state.</summary>
internal sealed class RemoteTeleportPlacementPresentation
{
private readonly LiveEntityPresentationController _presentation;
public RemoteTeleportPlacementPresentation(
LiveEntityPresentationController presentation) =>
_presentation = presentation
?? throw new ArgumentNullException(nameof(presentation));
public void Complete(uint serverGuid, ushort generation, bool deferShadowRestore) =>
_presentation.CompleteAuthoritativePlacement(
serverGuid,
generation,
deferShadowRestore);
public void Begin(uint serverGuid, ushort generation) =>
_presentation.BeginAuthoritativePlacement(serverGuid, generation);
}

View file

@ -1,605 +0,0 @@
using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.World;
using AcDream.Core.Physics;
using AcDream.Core.World;
using AcDream.Runtime.Entities;
namespace AcDream.App.Physics;
/// <summary>
/// Owns the SetPosition half of retail remote
/// <c>CPhysicsObj::MoveOrTeleport</c> (0x00516330). It resolves the
/// destination through the placement transition, commits that transition to
/// the existing body, and synchronizes the remote movement/contact state.
/// Logical identity, target-hook actions, rebucketing callbacks, and render
/// presentation remain with their existing owners.
/// </summary>
internal sealed class RemoteTeleportController : IDisposable
{
internal delegate ResolveResult PlacementResolver(
Vector3 position,
uint cellId,
float radius,
float height,
ObjectInfoState moverFlags,
uint movingEntityId);
private readonly PhysicsEngine _physics;
private readonly LiveEntityRuntime _liveEntities;
private readonly Func<uint, WorldEntity, (float Radius, float Height)> _getSetupCylinder;
private readonly Func<Vector3, uint, Vector3> _cellLocalForSeed;
private readonly Action<WorldEntity, PhysicsBody, uint> _syncResolvedShadow;
private readonly Action<uint, ushort, bool> _completeAuthoritativePlacement;
private readonly Action<uint, ushort> _beginAuthoritativePlacement;
private readonly PlacementResolver _resolvePlacement;
private readonly Func<uint, ObjectInfoState> _getMoverPvpState;
private readonly Dictionary<RuntimeEntityKey, PendingPlacement> _pending = new();
internal RemoteTeleportController(
PhysicsEngine physics,
LiveEntityRuntime liveEntities,
Func<uint, WorldEntity, (float Radius, float Height)> getSetupCylinder,
Func<Vector3, uint, Vector3> cellLocalForSeed,
Action<WorldEntity, PhysicsBody, uint> syncResolvedShadow,
Action<uint, ushort, bool> completeAuthoritativePlacement,
Action<uint, ushort> beginAuthoritativePlacement,
PlacementResolver? resolvePlacement = null,
// TS-23 (Campaign P Slice P3, 2026-07-30): see
// RemotePhysicsUpdater's identical parameter.
Func<uint, ObjectInfoState>? getMoverPvpState = null)
{
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
_getSetupCylinder = getSetupCylinder
?? throw new ArgumentNullException(nameof(getSetupCylinder));
_cellLocalForSeed = cellLocalForSeed
?? throw new ArgumentNullException(nameof(cellLocalForSeed));
_syncResolvedShadow = syncResolvedShadow
?? throw new ArgumentNullException(nameof(syncResolvedShadow));
_completeAuthoritativePlacement = completeAuthoritativePlacement
?? throw new ArgumentNullException(nameof(completeAuthoritativePlacement));
_beginAuthoritativePlacement = beginAuthoritativePlacement
?? throw new ArgumentNullException(nameof(beginAuthoritativePlacement));
_resolvePlacement = resolvePlacement ?? ResolvePlacement;
_getMoverPvpState = getMoverPvpState ?? (static _ => ObjectInfoState.None);
_liveEntities.ProjectionVisibilityChanged += OnProjectionVisibilityChanged;
}
internal readonly record struct Result(
bool Applied,
bool ContactResolved,
Vector3 Position,
uint CellId,
Quaternion Orientation,
bool Superseded = false);
private readonly record struct PendingPlacement(
LiveEntityRecord Record,
ulong PositionAuthorityVersion,
ulong VelocityAuthorityVersion,
IRuntimeRemotePlacement Remote,
PhysicsBody Body,
WorldEntity Entity,
Vector3 RequestedWorldPosition,
uint RequestedCellId,
Quaternion RequestedOrientation,
double GameTime,
ushort Generation,
ushort PositionSequence,
bool WasInContact,
bool WasOnWalkable,
RollbackPlacement Rollback);
private readonly record struct RollbackPlacement(
Vector3 Position,
uint CellId,
Vector3 CellLocalPosition,
Quaternion Orientation,
TransientStateFlags TransientState,
Plane ContactPlane,
bool ContactPlaneValid,
uint ContactPlaneCellId,
bool ContactPlaneIsWater,
double LastUpdateTime,
bool Airborne,
Vector3 LastServerPosition,
double LastServerPositionTime,
Vector3 LastShadowSyncPosition,
Quaternion LastShadowSyncOrientation);
/// <summary>
/// Applies a placement to whichever exact incarnation is current at this
/// call boundary. Packet handlers should use the token-bearing overload;
/// this form serves non-wire orchestration and retains the same callback
/// revalidation once admitted.
/// </summary>
internal Result TryApply(
IRuntimeRemotePlacement remote,
WorldEntity entity,
Vector3 requestedWorldPosition,
uint requestedCellId,
Vector3 requestedCellLocalPosition,
Quaternion requestedOrientation,
double gameTime,
bool destinationProjectionVisible,
ushort generation,
ushort positionSequence)
{
if (!_liveEntities.TryGetRecord(entity.ServerGuid, out LiveEntityRecord record))
{
throw new InvalidOperationException(
$"Remote placement for 0x{entity.ServerGuid:X8} requires a live incarnation.");
}
return TryApply(
record,
record.PositionAuthorityVersion,
record.VelocityAuthorityVersion,
remote,
entity,
requestedWorldPosition,
requestedCellId,
requestedCellLocalPosition,
requestedOrientation,
gameTime,
destinationProjectionVisible,
generation,
positionSequence);
}
internal Result TryApply(
LiveEntityRecord expectedRecord,
ulong expectedPositionAuthorityVersion,
ulong expectedVelocityAuthorityVersion,
IRuntimeRemotePlacement remote,
WorldEntity entity,
Vector3 requestedWorldPosition,
uint requestedCellId,
Vector3 requestedCellLocalPosition,
Quaternion requestedOrientation,
double gameTime,
bool destinationProjectionVisible,
ushort generation,
ushort positionSequence)
{
ArgumentNullException.ThrowIfNull(remote);
ArgumentNullException.ThrowIfNull(entity);
ArgumentNullException.ThrowIfNull(expectedRecord);
if (!_liveEntities.TryGetRecord(entity.ServerGuid, out LiveEntityRecord liveRecord)
|| !ReferenceEquals(liveRecord, expectedRecord)
|| !_liveEntities.IsCurrentPositionAuthority(
expectedRecord,
expectedPositionAuthorityVersion)
|| liveRecord.Generation != generation
|| !ReferenceEquals(liveRecord.WorldEntity, entity)
|| !ReferenceEquals(liveRecord.RemoteMotionRuntime, remote)
|| liveRecord.PhysicsBody is null
|| !ReferenceEquals(liveRecord.PhysicsBody, remote.Body))
{
throw new InvalidOperationException(
$"Remote placement for 0x{entity.ServerGuid:X8} must use its incarnation's canonical physics body.");
}
bool wasInContact = liveRecord.PhysicsBody.InContact;
bool wasOnWalkable = liveRecord.PhysicsBody.OnWalkable;
RollbackPlacement rollback = CaptureRollback(remote, liveRecord.PhysicsBody);
RuntimeEntityKey key = RequireProjectionKey(liveRecord);
if (_pending.TryGetValue(key, out PendingPlacement prior)
&& ReferenceEquals(prior.Record, liveRecord))
{
wasInContact = prior.WasInContact;
wasOnWalkable = prior.WasOnWalkable;
rollback = prior.Rollback;
}
PendingPlacement request = new(
liveRecord,
expectedPositionAuthorityVersion,
expectedVelocityAuthorityVersion,
remote,
liveRecord.PhysicsBody,
entity,
requestedWorldPosition,
requestedCellId,
requestedOrientation,
gameTime,
generation,
positionSequence,
wasInContact,
wasOnWalkable,
rollback);
if (!destinationProjectionVisible)
{
if (!ParkPending(request, requestedCellLocalPosition))
return Superseded(request);
return new Result(
true,
false,
requestedWorldPosition,
requestedCellId,
requestedOrientation);
}
ResolveResult placement = Resolve(request);
if (!IsCurrent(request))
return Superseded(request);
if (!placement.Ok)
{
_pending.Remove(key);
if (!RollBackDeferredPlacement(request))
return Superseded(request);
return new Result(
false,
false,
request.Body.Position,
remote.CellId,
request.Body.Orientation);
}
_pending.Remove(key);
return CommitResolved(request, placement);
}
internal void Forget(LiveEntityRecord record)
{
ArgumentNullException.ThrowIfNull(record);
if (record.ProjectionKey is { } key
&& _pending.TryGetValue(key, out PendingPlacement pending)
&& ReferenceEquals(pending.Record, record))
{
_pending.Remove(key);
}
}
internal void Clear()
{
_pending.Clear();
}
internal bool HasPending(uint serverGuid) =>
_liveEntities.TryGetRecord(serverGuid, out LiveEntityRecord record)
&& record.ProjectionKey is { } key
&& _pending.ContainsKey(key);
internal int PendingPlacementCount => _pending.Count;
/// <summary>
/// Transfers any older deferred shadow restoration before the accepted
/// destination is rebucketed and can publish a visibility edge.
/// </summary>
internal void BeginPlacement(uint serverGuid, ushort generation) =>
_beginAuthoritativePlacement(serverGuid, generation);
public void Dispose()
{
_liveEntities.ProjectionVisibilityChanged -= OnProjectionVisibilityChanged;
Clear();
}
private ResolveResult Resolve(PendingPlacement request)
{
var (radius, height) = _getSetupCylinder(
request.Entity.ServerGuid,
request.Entity);
if (radius < 0.05f)
{
radius = 0.48f;
height = 1.835f;
}
return _resolvePlacement(
request.RequestedWorldPosition,
request.RequestedCellId,
radius,
height,
// TS-23: moverPvpState is a no-op OR (None) for every non-PK
// remote.
(IsPlayerGuid(request.Entity.ServerGuid)
? ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide
: ObjectInfoState.EdgeSlide)
| _getMoverPvpState(request.Entity.ServerGuid),
request.Entity.Id);
}
private ResolveResult ResolvePlacement(
Vector3 position,
uint cellId,
float radius,
float height,
ObjectInfoState moverFlags,
uint movingEntityId) =>
_physics.ResolvePlacement(
position,
cellId,
radius,
height,
stepUpHeight: 0.4f,
stepDownHeight: 0.4f,
moverFlags: moverFlags,
movingEntityId: movingEntityId);
private Result CommitResolved(PendingPlacement request, ResolveResult placement)
{
if (!IsCurrent(request))
return Superseded(request);
if (!RemoteTeleportPlacement.Apply(
request.Remote,
request.Body,
placement,
_cellLocalForSeed(placement.Position, placement.CellId),
request.RequestedOrientation,
request.GameTime,
request.WasInContact,
request.WasOnWalkable,
() => IsCurrent(request),
() => IsVelocityCurrent(request)))
{
return Superseded(request);
}
if (!IsCurrent(request))
return Superseded(request);
if (request.Remote.CellId != placement.CellId)
request.Remote.CellId = placement.CellId;
if (!IsCurrent(request))
return Superseded(request);
request.Remote.LastServerPosition = placement.Position;
request.Remote.LastServerPositionTime = request.GameTime;
request.Remote.LastShadowSyncPosition = Vector3.Zero;
request.Remote.LastShadowSyncOrientation = Quaternion.Zero;
request.Entity.SetPosition(request.Body.Position);
request.Entity.ParentCellId = placement.CellId;
request.Entity.Rotation = request.Body.Orientation;
if (!IsCurrent(request))
return Superseded(request);
if (_liveEntities.TryGetRecord(request.Entity.ServerGuid, out LiveEntityRecord record)
&& ReferenceEquals(record, request.Record))
{
bool deferShadowRestore =
record.FinalPhysicsState.HasFlag(PhysicsStateFlags.Hidden)
|| !record.IsSpatiallyVisible;
_completeAuthoritativePlacement(
request.Entity.ServerGuid,
request.Generation,
deferShadowRestore);
if (!IsCurrent(request))
return Superseded(request);
if (!deferShadowRestore)
{
request.Remote.LastShadowSyncPosition = request.Body.Position;
request.Remote.LastShadowSyncOrientation = request.Body.Orientation;
_syncResolvedShadow(request.Entity, request.Body, placement.CellId);
if (!IsCurrent(request))
return Superseded(request);
}
}
return new Result(
true,
true,
request.Body.Position,
placement.CellId,
request.Body.Orientation);
}
private bool ParkPending(PendingPlacement request, Vector3 requestedCellLocalPosition)
{
if (!IsCurrent(request))
return false;
request.Body.Orientation = request.RequestedOrientation;
request.Body.SnapToCell(
request.RequestedCellId,
request.RequestedWorldPosition,
requestedCellLocalPosition);
request.Body.LastUpdateTime = request.GameTime;
request.Body.ContactPlaneValid = false;
request.Body.ContactPlaneCellId = 0u;
request.Body.ContactPlaneIsWater = false;
PhysicsObjUpdate.ApplySetPositionContact(
request.Body,
inContact: false,
onWalkable: false);
request.Remote.Airborne = true;
if (request.Remote.CellId != request.RequestedCellId)
request.Remote.CellId = request.RequestedCellId;
if (!IsCurrent(request))
return false;
request.Remote.LastServerPosition = request.RequestedWorldPosition;
request.Remote.LastServerPositionTime = request.GameTime;
request.Remote.LastShadowSyncPosition = Vector3.Zero;
request.Remote.LastShadowSyncOrientation = Quaternion.Zero;
request.Entity.SetPosition(request.RequestedWorldPosition);
request.Entity.ParentCellId = request.RequestedCellId;
request.Entity.Rotation = request.RequestedOrientation;
if (!IsCurrent(request))
return false;
_pending[RequireProjectionKey(request.Record)] = request;
return true;
}
private void OnProjectionVisibilityChanged(LiveEntityRecord record, bool visible)
{
if (!visible)
return;
RuntimeEntityKey key = RequireProjectionKey(record);
if (_pending.TryGetValue(key, out PendingPlacement pending))
{
if (record.WorldEntity is null
|| !ReferenceEquals(record, pending.Record)
|| !ReferenceEquals(record.WorldEntity, pending.Entity)
|| record.Generation != pending.Generation)
{
_pending.Remove(key);
return;
}
if (record.RemoteMotionRuntime is not IRuntimeRemotePlacement currentRemote
|| record.PhysicsBody is null
|| !ReferenceEquals(record.PhysicsBody, pending.Body)
|| !ReferenceEquals(currentRemote.Body, record.PhysicsBody))
{
_pending.Remove(key);
if (record.RemoteMotionRuntime is not null
&& (record.PhysicsBody is null
|| !ReferenceEquals(record.RemoteMotionRuntime.Body, record.PhysicsBody)))
{
_liveEntities.ClearRemoteMotionRuntime(record.ServerGuid);
}
RollBackDeferredPlacement(pending);
return;
}
if (!ReferenceEquals(currentRemote, pending.Remote))
{
pending = pending with { Remote = currentRemote };
_pending[key] = pending;
}
if (!_liveEntities.IsCurrentPositionAuthority(
pending.Record,
pending.PositionAuthorityVersion)
|| record.Snapshot.PositionSequence != pending.PositionSequence)
{
// A newer accepted UpdatePosition rebucketed the projection
// before its TryApply call could replace this request. Keep
// the original rollback alive for that synchronous call.
return;
}
_pending.Remove(key);
ResolveResult placement = Resolve(pending);
if (!IsCurrent(pending))
return;
if (!placement.Ok)
RollBackDeferredPlacement(pending);
else
CommitResolved(pending, placement);
return;
}
}
private static RollbackPlacement CaptureRollback(
IRuntimeRemotePlacement remote,
PhysicsBody body)
{
return new RollbackPlacement(
body.Position,
body.CellPosition.ObjCellId,
body.CellPosition.Frame.Origin,
body.Orientation,
body.TransientState,
body.ContactPlane,
body.ContactPlaneValid,
body.ContactPlaneCellId,
body.ContactPlaneIsWater,
body.LastUpdateTime,
remote.Airborne,
remote.LastServerPosition,
remote.LastServerPositionTime,
remote.LastShadowSyncPosition,
remote.LastShadowSyncOrientation);
}
private bool RollBackDeferredPlacement(PendingPlacement pending)
{
if (!IsCurrentAuthority(pending))
return false;
RollbackPlacement rollback = pending.Rollback;
PhysicsBody body = pending.Body;
body.Orientation = rollback.Orientation;
body.SnapToCell(
rollback.CellId,
rollback.Position,
rollback.CellLocalPosition);
body.TransientState = rollback.TransientState;
body.ContactPlane = rollback.ContactPlane;
body.ContactPlaneValid = rollback.ContactPlaneValid;
body.ContactPlaneCellId = rollback.ContactPlaneCellId;
body.ContactPlaneIsWater = rollback.ContactPlaneIsWater;
body.LastUpdateTime = rollback.LastUpdateTime;
body.calc_acceleration();
pending.Remote.Airborne = rollback.Airborne;
pending.Remote.CellId = rollback.CellId;
pending.Remote.LastServerPosition = rollback.LastServerPosition;
pending.Remote.LastServerPositionTime = rollback.LastServerPositionTime;
pending.Remote.LastShadowSyncPosition = rollback.LastShadowSyncPosition;
pending.Remote.LastShadowSyncOrientation = rollback.LastShadowSyncOrientation;
pending.Entity.SetPosition(rollback.Position);
pending.Entity.ParentCellId = rollback.CellId;
pending.Entity.Rotation = rollback.Orientation;
if (!IsCurrentAuthority(pending))
return false;
if (rollback.CellId == 0)
_liveEntities.WithdrawLiveEntityProjectionToCellless(pending.Entity.ServerGuid);
else
_liveEntities.RebucketLiveEntity(pending.Entity.ServerGuid, rollback.CellId);
if (!IsCurrentAuthority(pending)
|| !_liveEntities.TryGetRecord(
pending.Entity.ServerGuid,
out LiveEntityRecord restored)
|| !ReferenceEquals(restored, pending.Record)
|| restored.Generation != pending.Generation)
{
return false;
}
if (rollback.CellId == 0)
{
_completeAuthoritativePlacement(
pending.Entity.ServerGuid,
pending.Generation,
false);
return IsCurrentAuthority(pending);
}
bool deferShadowRestore =
restored.FinalPhysicsState.HasFlag(PhysicsStateFlags.Hidden)
|| !restored.IsSpatiallyVisible;
_completeAuthoritativePlacement(
pending.Entity.ServerGuid,
pending.Generation,
deferShadowRestore);
if (!IsCurrentAuthority(pending))
return false;
if (deferShadowRestore)
return true;
pending.Remote.LastShadowSyncPosition = Vector3.Zero;
pending.Remote.LastShadowSyncOrientation = Quaternion.Zero;
_syncResolvedShadow(pending.Entity, pending.Body, rollback.CellId);
return IsCurrentAuthority(pending);
}
private bool IsCurrentAuthority(PendingPlacement request) =>
_liveEntities.IsCurrentPositionAuthority(
request.Record,
request.PositionAuthorityVersion)
&& request.Record.Generation == request.Generation
&& ReferenceEquals(request.Record.WorldEntity, request.Entity)
&& ReferenceEquals(request.Record.PhysicsBody, request.Body);
private bool IsCurrent(PendingPlacement request) =>
IsCurrentAuthority(request)
&& ReferenceEquals(request.Record.RemoteMotionRuntime, request.Remote);
private bool IsVelocityCurrent(PendingPlacement request) =>
_liveEntities.IsCurrentVelocityAuthority(
request.Record,
request.VelocityAuthorityVersion);
private static Result Superseded(PendingPlacement request) => new(
Applied: false,
ContactResolved: false,
Position: request.Body.Position,
CellId: request.Remote.CellId,
Orientation: request.Body.Orientation,
Superseded: true);
private static bool IsPlayerGuid(uint guid) =>
(guid & 0xFF000000u) == 0x50000000u;
private static RuntimeEntityKey RequireProjectionKey(
LiveEntityRecord record) =>
record.ProjectionKey
?? throw new InvalidOperationException(
$"Live entity 0x{record.ServerGuid:X8}/{record.Generation} " +
"has no exact projection key.");
}

View file

@ -1,85 +0,0 @@
using System.Numerics;
using AcDream.App.World;
using AcDream.Core.Physics;
namespace AcDream.App.Physics;
/// <summary>
/// Commits the placement half of retail <c>CPhysicsObj::MoveOrTeleport</c>
/// Branch A (<c>0x00516330</c>). The caller runs
/// <see cref="RemoteTeleportHook"/> first, then this exact frame/cell snap,
/// before considering contact-driven interpolation.
/// </summary>
internal static class RemoteTeleportPlacement
{
internal static bool Apply(
IRuntimeRemotePlacement remote,
PhysicsBody body,
ResolveResult placement,
Vector3 cellLocalPosition,
Quaternion orientation,
double gameTime,
bool previousContact,
bool previousOnWalkable,
Func<bool>? isCurrent = null,
Func<bool>? isVelocityCurrent = null)
{
ArgumentNullException.ThrowIfNull(remote);
ArgumentNullException.ThrowIfNull(body);
if (!placement.Ok
|| !IsFinite(placement.Position)
|| !IsFinite(cellLocalPosition)
|| !PositionFrameValidation.IsValid(
placement.CellId,
cellLocalPosition,
orientation)
|| !double.IsFinite(gameTime))
{
throw new ArgumentOutOfRangeException(
nameof(placement),
"A remote teleport placement requires an accepted finite frame, clock, and nonzero cell.");
}
body.Orientation = orientation;
body.SnapToCell(placement.CellId, placement.Position, cellLocalPosition);
body.LastUpdateTime = gameTime;
body.ContactPlaneValid = placement.InContact;
if (placement.InContact)
{
body.ContactPlane = placement.ContactPlane;
body.ContactPlaneCellId = placement.ContactPlaneCellId;
body.ContactPlaneIsWater = placement.ContactPlaneIsWater;
}
else
{
body.ContactPlaneCellId = 0u;
body.ContactPlaneIsWater = false;
}
if (!PhysicsObjUpdate.CommitSetPositionTransition(
body,
placement.InContact,
placement.OnWalkable,
placement.CollisionNormalValid,
placement.CollisionNormal,
previousContact,
previousOnWalkable,
remote.HitGround,
remote.LeaveGround,
isCurrent,
isVelocityCurrent))
{
return false;
}
if (isVelocityCurrent?.Invoke() != false)
remote.Airborne = !body.OnWalkable;
return isCurrent?.Invoke() ?? true;
}
private static bool IsFinite(Vector3 value) =>
float.IsFinite(value.X)
&& float.IsFinite(value.Y)
&& float.IsFinite(value.Z);
}