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

View file

@ -105,7 +105,6 @@ internal sealed record LivePresentationResult(
EquippedChildRenderController EquippedChildren,
EntityEffectController EntityEffects,
LiveEntityPresentationController Presentation,
RemoteTeleportController RemoteTeleport,
WbDrawDispatcher? DrawDispatcher,
RetailSelectionScene SelectionScene,
WorldSelectionQuery SelectionQuery,
@ -634,26 +633,6 @@ internal sealed class LivePresentationCompositionPhase
d.EntityObjects.Objects,
liveEntities,
d.PhysicsEngine.ShadowObjects));
var remoteShadowPlacement = new RemoteShadowPlacementSynchronizer(
d.RemotePhysicsUpdater,
d.WorldOrigin);
var remoteTeleportPresentation =
new RemoteTeleportPlacementPresentation(presentationLease.Resource);
var remoteTeleportLease = scope.Acquire(
"remote teleport",
() => new RemoteTeleportController(
d.PhysicsEngine,
liveEntities,
d.MotionBindings.GetSetupCylinder,
d.WorldOrigin.CellLocalForSeed,
remoteShadowPlacement.Sync,
remoteTeleportPresentation.Complete,
remoteTeleportPresentation.Begin,
getMoverPvpState: guid =>
EntityCollisionFlagsExt.ResolveMoverPvpState(
d.EntityObjects.Objects,
guid)),
static value => value.Dispose());
bindings.BindProjectionPoseReady(
equippedLease.Resource,
lightsLease.Resource.OnAttachedPoseReady);
@ -697,7 +676,6 @@ internal sealed class LivePresentationCompositionPhase
equippedLease,
entityEffects,
presentationLease,
remoteTeleportLease,
selectionInteractionSource,
bindings,
scope,
@ -747,7 +725,6 @@ internal sealed class LivePresentationCompositionPhase
CompositionAcquisitionScope.CompositionAcquisitionLease<EquippedChildRenderController> equippedLease,
EntityEffectController entityEffects,
CompositionAcquisitionScope.CompositionAcquisitionLease<LiveEntityPresentationController> presentationLease,
CompositionAcquisitionScope.CompositionAcquisitionLease<RemoteTeleportController> remoteTeleportLease,
DeferredSelectionInteractionSource selectionInteractionSource,
LivePresentationRuntimeBindings bindings,
CompositionAcquisitionScope scope,
@ -1227,7 +1204,6 @@ internal sealed class LivePresentationCompositionPhase
equippedLease.Resource,
entityEffects,
presentationLease.Resource,
remoteTeleportLease.Resource,
dispatcherLease.Resource,
selectionScene,
selectionQuery,
@ -1267,7 +1243,6 @@ internal sealed class LivePresentationCompositionPhase
lightsLease.Transfer();
equippedLease.Transfer();
presentationLease.Transfer();
remoteTeleportLease.Transfer();
dispatcherLease.Transfer();
retainedGameplayLease?.Transfer();
paperdollLease?.Transfer();

View file

@ -480,7 +480,6 @@ internal sealed class SessionPlayerCompositionPhase
live.LiveEntities,
live.Presentation,
live.EntityEffects,
live.RemoteTeleport,
live.SelectionInteractions,
d.Actions.Selection,
d.AnimatedEntities,
@ -645,7 +644,6 @@ internal sealed class SessionPlayerCompositionPhase
live.Lights,
live.EquippedChildren,
live.ProjectileController,
live.RemoteTeleport,
d.AnimatedEntities,
d.RemoteMovementObservations,
d.RemotePhysicsUpdater,
@ -981,7 +979,6 @@ internal sealed class SessionPlayerCompositionPhase
liveness,
networkUpdates,
hydration,
live.RemoteTeleport,
live.EntityEffects,
content.AnimationHookFrames,
live.Presentation,

View file

@ -25,7 +25,6 @@ internal sealed class LiveSessionResetBindings
public required Action<RuntimeGenerationToken> RuntimeGeneration { get; init; }
public required Action<RuntimeGenerationToken> SessionIdentityPresentation
{ get; init; }
public required Action RemoteTeleport { get; init; }
public required Action NetworkEffects { get; init; }
public required Action AnimationHookFrames { get; init; }
public required Action LivePresentation { get; init; }
@ -65,7 +64,6 @@ internal static class LiveSessionResetManifest
new(
"session identity presentation",
bindings.SessionIdentityPresentation),
new("remote teleport", bindings.RemoteTeleport),
// F754/F755 can precede CreateObject, so pending network effects
// must clear even when no LiveEntityRecord was constructed.
new("network effects", bindings.NetworkEffects),

View file

@ -79,7 +79,6 @@ internal sealed record LiveSessionWorldRuntime(
LiveEntityLivenessController Liveness,
LiveEntityNetworkUpdateController NetworkUpdates,
LiveEntityHydrationController Hydration,
RemoteTeleportController RemoteTeleport,
EntityEffectController EntityEffects,
AnimationHookFrameQueue AnimationHookFrames,
LiveEntityPresentationController Presentation,
@ -193,7 +192,6 @@ internal sealed class LiveSessionRuntimeFactory
RuntimeGeneration = generation =>
_domain.Runtime.ResetGeneration(generation, resetHost),
SessionIdentityPresentation = ResetIdentityPresentation,
RemoteTeleport = _world.RemoteTeleport.Clear,
NetworkEffects = _world.EntityEffects.ClearNetworkState,
AnimationHookFrames = _world.AnimationHookFrames.Clear,
LivePresentation = _world.Presentation.Clear,

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);
}

View file

@ -180,7 +180,6 @@ public sealed class GameWindow :
_movementTruthDiagnostics;
private readonly LocalPlayerOutboundController _localPlayerOutbound;
private readonly AcDream.App.Update.UpdateFrameClock _updateFrameClock;
private AcDream.App.Physics.RemoteTeleportController? _remoteTeleportController;
// Step 7 projectile presentation. The controller owns no identity map;
// each runtime component is stored on the canonical LiveEntityRecord.
private AcDream.App.Physics.ProjectileController? _projectileController;
@ -1001,7 +1000,6 @@ public sealed class GameWindow :
|| _equippedChildRenderer is not null
|| _entityEffects is not null
|| _liveEntityPresentation is not null
|| _remoteTeleportController is not null
|| _projectileController is not null
|| _liveEntityProjectionWithdrawal is not null
|| _liveEntityLights is not null
@ -1039,7 +1037,6 @@ public sealed class GameWindow :
_equippedChildRenderer = result.EquippedChildren;
_entityEffects = result.EntityEffects;
_liveEntityPresentation = result.Presentation;
_remoteTeleportController = result.RemoteTeleport;
_wbDrawDispatcher = result.DrawDispatcher;
_retailSelectionScene = result.SelectionScene;
_worldSelectionQuery = result.SelectionQuery;
@ -1638,7 +1635,6 @@ public sealed class GameWindow :
_hookRegistrations,
_liveEntityLights,
_liveEntityPresentation,
_remoteTeleportController,
_animationHookFrames,
_effectPoses,
_audioEngine),

View file

@ -92,7 +92,6 @@ internal sealed record LiveShutdownRoots(
AnimationHookRegistrationSet? HookRegistrations,
LiveEntityLightController? LiveLights,
LiveEntityPresentationController? LivePresentation,
RemoteTeleportController? RemoteTeleport,
AnimationHookFrameQueue? AnimationHookFrames,
EntityEffectPoseRegistry EffectPoses,
OpenAlAudioEngine? Audio);
@ -397,7 +396,6 @@ internal static class GameWindowShutdownManifest
[
Hard("live lights", () => live.LiveLights?.Dispose()),
Hard("live presentation", () => live.LivePresentation?.Dispose()),
Hard("remote teleport", () => live.RemoteTeleport?.Dispose()),
Hard("effect network state", () =>
{
live.EntityEffects?.ClearNetworkState();

View file

@ -31,7 +31,6 @@ public sealed class LiveEntityPresentationController : IDisposable
private readonly LiveEntityPartArrayEnterWorldPort _partArrayEnterWorld;
private readonly HashSet<RuntimeEntityKey> _readyOwners = [];
private readonly HashSet<RuntimeEntityKey> _suspendedShadowOwners = [];
private readonly HashSet<RuntimeEntityKey> _activePlacementOwners = [];
private readonly HashSet<LiveEntityRecord> _drainingRecords = new();
private bool _disposed;
@ -102,73 +101,12 @@ public sealed class LiveEntityPresentationController : IDisposable
return ApplyPendingTransitions(record);
}
/// <summary>
/// Retains ownership of a collision shadow that another retail transition
/// suspended while this incarnation's spatial projection is unavailable.
/// Hidden/UnHide and teleport rollback intentionally converge on this one
/// generation-scoped restoration marker.
/// </summary>
public bool DeferShadowRestore(uint serverGuid, ushort generation)
=> CompleteAuthoritativePlacement(serverGuid, generation, deferShadowRestore: true);
/// <summary>
/// Completes active placement ownership after its final frame/cell is
/// known, either returning a stable deferred restore to presentation or
/// clearing the suspension for an immediate controller-owned restore.
/// </summary>
public bool CompleteAuthoritativePlacement(
uint serverGuid,
ushort generation,
bool deferShadowRestore)
{
if (!_liveEntities.TryGetRecord(serverGuid, out LiveEntityRecord record)
|| record.Generation != generation
|| record.WorldEntity is null)
{
return false;
}
RuntimeEntityKey key = RequireProjectionKey(record);
_activePlacementOwners.Remove(key);
if (deferShadowRestore)
_suspendedShadowOwners.Add(key);
else
_suspendedShadowOwners.Remove(key);
return true;
}
/// <summary>
/// Transfers a deferred restore to a newer authoritative placement before
/// that placement rebuckets and can become visible. The placement hands
/// ownership back after it reaches a stable Hidden destination/rollback;
/// this prevents an intervening UnHide from restoring an unresolved pose.
/// </summary>
public bool BeginAuthoritativePlacement(uint serverGuid, ushort generation)
{
if (!_liveEntities.TryGetRecord(serverGuid, out LiveEntityRecord record)
|| record.Generation != generation)
{
return false;
}
RuntimeEntityKey key = RequireProjectionKey(record);
_activePlacementOwners.Add(key);
_suspendedShadowOwners.Remove(key);
return true;
}
internal bool HasDeferredShadowRestore(uint serverGuid) =>
TryGetCurrentProjectionKey(serverGuid, out RuntimeEntityKey key)
&& _suspendedShadowOwners.Contains(key);
internal bool HasActivePlacement(uint serverGuid) =>
TryGetCurrentProjectionKey(serverGuid, out RuntimeEntityKey key)
&& _activePlacementOwners.Contains(key);
internal int ReadyOwnerCount => _readyOwners.Count;
internal int DeferredShadowRestoreCount => _suspendedShadowOwners.Count;
internal int ActivePlacementCount => _activePlacementOwners.Count;
public void Forget(LiveEntityRecord record)
{
@ -177,7 +115,6 @@ public sealed class LiveEntityPresentationController : IDisposable
{
_readyOwners.Remove(key);
_suspendedShadowOwners.Remove(key);
_activePlacementOwners.Remove(key);
}
_drainingRecords.Remove(record);
}
@ -186,7 +123,6 @@ public sealed class LiveEntityPresentationController : IDisposable
{
_readyOwners.Clear();
_suspendedShadowOwners.Clear();
_activePlacementOwners.Clear();
_drainingRecords.Clear();
}
@ -233,8 +169,7 @@ public sealed class LiveEntityPresentationController : IDisposable
if (!IsCurrent(record, entity))
return false;
_shadows.Suspend(entity.Id);
if (!IsPlacementActive(record))
_suspendedShadowOwners.Add(RequireProjectionKey(record));
_suspendedShadowOwners.Add(RequireProjectionKey(record));
// Retail CPhysicsObj::set_hidden @ 0x00514C60 calls
// CPartArray::HandleEnterWorld after hiding the object
// from its cell. Despite the name, this is the motion
@ -261,8 +196,7 @@ public sealed class LiveEntityPresentationController : IDisposable
_partArrayEnterWorld.HandleEnterWorld(entity.Id);
if (!IsCurrent(record, entity))
return false;
bool restored = !IsPlacementActive(record)
&& RestoreShadow(record, entity);
bool restored = RestoreShadow(record, entity);
if (!IsCurrent(record, entity))
return false;
if (restored)
@ -313,8 +247,12 @@ public sealed class LiveEntityPresentationController : IDisposable
// projection edge that suspends its object clock. Keep the retained
// registration so a stationary object can be restored immediately on
// hydration; it may have no later movement quantum to repair itself.
// Projectiles and active authoritative placements own their matching
// suspend/restore transaction in their dedicated controllers.
// Projectiles own their matching suspend/restore transaction in their
// dedicated controller. C4 route 4b-3 deleted the standalone remote-
// teleport placement controller (and its `_activePlacementOwners`
// suspension gate here) — the canonical Runtime placement owner now
// handles that path, and this class no longer defers to a second
// authority.
if (!visible)
{
SuspendOrdinaryShadowOutsideProjection(record, entity);
@ -322,7 +260,6 @@ public sealed class LiveEntityPresentationController : IDisposable
}
if ((record.FinalPhysicsState & PhysicsStateFlags.Hidden) != 0
|| IsPlacementActive(record)
|| !TryGetProjectionKey(record, out RuntimeEntityKey key)
|| !_readyOwners.Contains(key)
|| !_suspendedShadowOwners.Contains(key))
@ -343,7 +280,6 @@ public sealed class LiveEntityPresentationController : IDisposable
{
if (record.IsSpatiallyVisible
|| record.ProjectileRuntime is not null
|| IsPlacementActive(record)
|| !TryGetProjectionKey(record, out RuntimeEntityKey key)
|| !_readyOwners.Contains(key)
|| !_shadows.Suspend(entity.Id))
@ -362,10 +298,6 @@ public sealed class LiveEntityPresentationController : IDisposable
&& ReferenceEquals(current, record)
&& ReferenceEquals(current.WorldEntity, entity);
private bool IsPlacementActive(LiveEntityRecord record) =>
TryGetProjectionKey(record, out RuntimeEntityKey key)
&& _activePlacementOwners.Contains(key);
private bool TryGetCurrentProjectionKey(
uint serverGuid,
out RuntimeEntityKey key)

View file

@ -2143,6 +2143,22 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
return false;
}
/// <summary>
/// R2 fix round (2026-08-04): the App-layer entry point for retail
/// <c>CPhysicsObj::report_collision_end(this, 1)</c> —
/// <c>teleport_hook</c>'s sixth action (@0x00514F31 → @0x00514620,
/// force-end-all with bidirectional <c>DoCollisionEnd</c> on both this
/// object and every collision-table partner). Forwards to
/// <see cref="RuntimeCollisionReportingState.LeaveWorld"/>, the exact
/// retail leave-world/teleport/Hidden force-end entry point — NOT
/// <c>ShadowObjectRegistry.Suspend</c>, which ports a DIFFERENT retail
/// function (<c>remove_shadows_from_cells</c>) that <c>teleport_hook</c>
/// never calls; that mapping predated this fix and was carried without
/// re-derivation.
/// </summary>
internal void ForceEndCollisionReporting(RuntimeEntityRecord canonical) =>
_physics.CollisionReports.LeaveWorld(canonical);
public bool TryGetRemoteMotionRuntime(
uint serverGuid,
out IRuntimeRemoteMotion runtime)
@ -2406,29 +2422,16 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
System.Numerics.Vector3? currentLocalVelocity,
out PositionTimestampDisposition disposition,
out WorldSession.EntitySpawn accepted,
out AcceptedPhysicsTimestamps timestamps)
{
bool projectionRequiresTeleportHook =
_directory.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical)
&& (canonical.FullCellId == 0u
|| !_projections.TryGet(
canonical,
out LiveEntityRecord? projection)
|| !projection.IsSpatiallyProjected
|| !projection.IsSpatiallyVisible);
return _entityObjects.TryApplyPosition(
out AcceptedPhysicsTimestamps timestamps) =>
_entityObjects.TryApplyPosition(
update,
isLocalPlayer,
forcePositionRotation,
currentLocalVelocity,
projectionRequiresTeleportHook,
acknowledgeProjection: null,
out disposition,
out accepted,
out timestamps);
}
/// <summary>
/// C4 route 4a: borrows the canonical Runtime classification for one

View file

@ -28,7 +28,6 @@ internal sealed class LiveEntityRuntimeTeardownController
private readonly LiveEntityRuntime? _runtime;
private readonly LiveEntityPresentationController? _presentation;
private readonly EntityEffectController? _effects;
private readonly RemoteTeleportController? _remoteTeleport;
private readonly SelectionInteractionController? _interactions;
private readonly SelectionState? _selection;
private readonly LiveEntityAnimationRuntimeView<LiveEntityAnimationState>? _animations;
@ -47,7 +46,6 @@ internal sealed class LiveEntityRuntimeTeardownController
LiveEntityRuntime runtime,
LiveEntityPresentationController presentation,
EntityEffectController effects,
RemoteTeleportController remoteTeleport,
SelectionInteractionController? interactions,
SelectionState selection,
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> animations,
@ -63,7 +61,6 @@ internal sealed class LiveEntityRuntimeTeardownController
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_presentation = presentation ?? throw new ArgumentNullException(nameof(presentation));
_effects = effects ?? throw new ArgumentNullException(nameof(effects));
_remoteTeleport = remoteTeleport ?? throw new ArgumentNullException(nameof(remoteTeleport));
_interactions = interactions;
_selection = selection ?? throw new ArgumentNullException(nameof(selection));
_animations = animations ?? throw new ArgumentNullException(nameof(animations));
@ -110,7 +107,6 @@ internal sealed class LiveEntityRuntimeTeardownController
{
() => _presentation!.Forget(record),
() => _effects!.OnLiveEntityUnregistered(record),
() => _remoteTeleport!.Forget(record),
() =>
{
bool replacementExists =

View file

@ -140,8 +140,9 @@ public static class EntityCollisionFlagsExt
/// <c>ClientObjectTable</c>-backed mover-flags lookup every physics
/// call site (local player world-entry, remote DR sweep + teleport,
/// ordinary movers) uses — was inlined three times (App
/// <c>GameWindow</c>/<c>LivePresentationComposition</c>/
/// <c>RemoteTeleportController</c>) before being consolidated here.
/// <c>GameWindow</c>/<c>LivePresentationComposition</c>/the since-deleted
/// <c>RemoteTeleportController</c>, C4 route 4b-3) before being
/// consolidated here.
/// An entity with no row, or a row with no wire bitfield yet, resolves
/// to <see cref="ObjectInfoState.None"/> — a no-op OR into moverFlags,
/// bit-identical to every pre-P3 caller.

View file

@ -218,6 +218,39 @@ public static class PhysicsDiagnostics
public static bool ProbeRemoteLandingEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_REMOTE_LANDING") == "1";
/// <summary>
/// C4 route 4b-3 (2026-08-04) live-execution proof (process rule 5): one
/// <c>[remote-teleport]</c> line per routed teleport arm
/// (<c>LiveEntityNetworkUpdateController.ApplyRemoteContactRouting</c>'s
/// teleport dispatch), so a connected test can confirm the new arm
/// actually executed rather than inferring it from a clean-looking
/// session (#309's lesson — a session with zero probe lines is a
/// not-run). Initial state from <c>ACDREAM_PROBE_REMOTE_TELEPORT=1</c>.
/// TEMPORARY — strip with the rest of the probe family once the
/// two-client connected teleport gate has landed.
/// </summary>
public static bool ProbeRemoteTeleportEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_REMOTE_TELEPORT") == "1";
/// <summary>
/// Emit one <c>[remote-teleport]</c> line for one routed teleport arm.
/// Self-guards on <see cref="ProbeRemoteTeleportEnabled"/>, so callers
/// need not pre-check. <paramref name="cause"/> is <c>"teleport-ts"</c>
/// or <c>"cellless"</c> (D1's two classifier predicates); the guid, the
/// hook's own currency result, and the placement status are the fields
/// the connected gate recipe reads back.
/// </summary>
public static void LogRemoteTeleport(
uint guid,
string cause,
bool hookRan,
string placementStatus)
{
if (!ProbeRemoteTeleportEnabled) return;
Console.WriteLine(System.FormattableString.Invariant(
$"[remote-teleport] guid=0x{guid:X8} cause={cause} hookRan={hookRan} placement={placementStatus}"));
}
/// <summary>
/// Emit one <c>[remote-landing]</c> line for a remote landing-detection
/// edge. Caller MUST guard with
@ -1253,6 +1286,7 @@ public static class PhysicsDiagnostics
ProbeSweptEnabled = false;
ProbeStepWalkEnabled = false;
ProbeTeleportEnabled = false;
ProbeRemoteTeleportEnabled = false;
ProbeRemoteLandingEnabled = false;
ProbeRemoteSlideEnabled = false;
ProbeRemoteSlideGuids = new System.Collections.Generic.HashSet<uint>();

View file

@ -755,9 +755,8 @@ public sealed class InboundPhysicsStateController
/// immediate-apply path did.
///
/// A <see cref="PositionTimestampDisposition.Rejected"/> retained
/// continuation only ever exists because
/// <see cref="AcceptedPhysicsTimestamps.TeleportHookRequired"/>-adjacent
/// bookkeeping mutated (see <see cref="PhysicsTimestampGate.TryAcceptPositionEvent"/>:
/// continuation only ever exists because timestamp-gate bookkeeping
/// mutated (see <see cref="PhysicsTimestampGate.TryAcceptPositionEvent"/>:
/// a Rejected outcome always leaves POSITION_TS and TELEPORT_TS net
/// unchanged, so the ONLY dimension that can differ is FORCE_POSITION_TS
/// from the local-player force-position fallthrough branch) — apply the
@ -1142,8 +1141,7 @@ public sealed class InboundPhysicsStateController
gate.TeleportTimestamp,
gate.ForcePositionTimestamp,
teleportAdvanced,
TeleportHookRequired: false,
previousTeleport ?? gate.TeleportTimestamp);
PreviousTeleport: previousTeleport ?? gate.TeleportTimestamp);
private static WorldSession.EntitySpawn MergeUntimestampedCreate(
WorldSession.EntitySpawn retained,
@ -1375,13 +1373,27 @@ public sealed class InboundPhysicsStateController
}
}
/// <summary>
/// <paramref name="PreMergeCommittedCellId"/> — C4 route 4b-3 (D1): the
/// entity's canonical <c>FullCellId</c> measured by
/// <see cref="AcDream.Runtime.Entities.RuntimeEntityObjectLifetime.TryApplyPosition"/>
/// BEFORE this packet's merge, for a record that already existed
/// (<see langword="null"/> when there was no prior canonical record — never a
/// fabricated 0). This is retail's <c>this_1-&gt;cell == 0</c> predicate
/// (<c>CPhysicsObj::MoveOrTeleport</c> @0x00516330, read at entry, before any
/// placement) — "this object has no resolved cell right now" — which is a
/// DIFFERENT question from the post-merge <c>canonical.FullCellId</c> the
/// classifier's <c>CommittedCellId</c> request field reads from every other
/// caller. 0 means the record genuinely had no committed cell (the
/// unwield-to-3D shape); it is never invented.
/// </summary>
public readonly record struct AcceptedPhysicsTimestamps(
ushort Instance,
ushort ServerControlledMove,
ushort Teleport,
ushort ForcePosition,
bool TeleportAdvanced = false,
bool TeleportHookRequired = false,
uint? PreMergeCommittedCellId = null,
ushort PreviousTeleport = 0);
public readonly record struct CreateParentUpdate(

View file

@ -622,8 +622,19 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
float? playerDistance)
{
ArgumentNullException.ThrowIfNull(canonical);
if (_generation is not { } generation)
if (_generation is not { } generation
// C4 route 4b-3 (D1): a remote PositionEvent feeds the
// classifier's cell-less predicate the PRE-merge committed cell
// — the same value TryApplyPosition measured for THIS packet,
// never re-read from the (already merged) canonical record. A
// null here means the merge never observed a prior canonical
// record for this entity, which "Runtime has no opinion" — the
// same policy every other missing-input case in this method
// already uses — covers honestly rather than fabricating 0.
|| timestamps.PreMergeCommittedCellId is not { } preMergeCommittedCellId)
{
return null;
}
if (!RuntimeAcceptedPositionRouteRequests.TryBuild(
generation(),
canonical,
@ -637,6 +648,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
// Retail's UsePositionFromServer is consumed by the local
// player branch only; the Remote branch never reads it.
usePositionFromServer: false,
preMergeCommittedCellId,
out RuntimeAcceptedPositionRouteRequest request))
{
return null;
@ -1641,7 +1653,6 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
bool isLocalPlayer,
System.Numerics.Quaternion? forcePositionRotation,
System.Numerics.Vector3? currentLocalVelocity,
bool projectionRequiresTeleportHook,
Action<RuntimeEntityRecord>? acknowledgeProjection,
out PositionTimestampDisposition disposition,
out WorldSession.EntitySpawn accepted,
@ -1713,7 +1724,6 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
update.Guid,
out RuntimeEntityRecord beforeCanonical);
uint beforeCell = beforeCanonical?.FullCellId ?? 0u;
bool wasCellless = hadCanonical && beforeCell == 0u;
bool known = Entities.TryApplyPosition(
update,
isLocalPlayer,
@ -1743,16 +1753,20 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
bool acceptedPosition =
disposition is not PositionTimestampDisposition.Rejected;
if (disposition is PositionTimestampDisposition.Apply)
// C4 route 4b-3 (D1): the classifier's remote cell-less predicate
// needs the PRE-merge committed cell — the value this method just
// measured as `beforeCell`, before `RefreshSnapshot`
// below stamps the accepted wire cell onto the canonical record via
// `RefreshDerivedState` -> `SetFullCell`. Reading `canonical.FullCellId`
// AFTER that merge (as the classifier's default builder overload
// does for every other caller) always sees the wire cell, which is
// why the predicate as fed to a remote PositionEvent was dead before
// this fix. `hadCanonical` is what makes this an honest value rather
// than a fabricated 0 — see the field's own doc.
timestamps = timestamps with
{
timestamps = timestamps with
{
TeleportHookRequired =
timestamps.TeleportAdvanced
|| wasCellless
|| projectionRequiresTeleportHook,
};
}
PreMergeCommittedCellId = hadCanonical ? beforeCell : null,
};
RuntimePlacementCancellationReceipt cancellation = default;
if (acceptedPosition)
{

View file

@ -38,7 +38,46 @@ internal static class RuntimeAcceptedPositionRouteRequests
ushort previousTeleportSequence,
ushort acceptedTeleportSequence,
float playerDistance,
bool usePositionFromServer)
bool usePositionFromServer) =>
Build(
generation,
canonical,
key,
update,
entityKind,
source,
disposition,
previousTeleportSequence,
acceptedTeleportSequence,
playerDistance,
usePositionFromServer,
// Route 1's own semantics, unchanged (contract invariant 11):
// the post-merge FullCellId is exactly what this overload has
// always read.
committedCellId: canonical.FullCellId);
/// <summary>
/// C4 route 4b-3 (D1): the overload for a caller that holds the PRE-merge
/// committed cell explicitly, rather than reading the (already merged)
/// canonical record's <c>FullCellId</c>. The only caller today is the
/// remote PositionEvent path
/// (<see cref="AcDream.Runtime.Entities.RuntimeEntityObjectLifetime.ClassifyRemoteAcceptedPosition"/>);
/// every other field still comes from <paramref name="canonical"/> exactly
/// as the route-1 overload above reads it.
/// </summary>
internal static RuntimeAcceptedPositionRouteRequest Build(
RuntimeGenerationToken generation,
RuntimeEntityRecord canonical,
RuntimeEntityKey key,
in WorldSession.EntityPositionUpdate update,
RuntimePositionEntityKind entityKind,
RuntimeAcceptedPositionSource source,
PositionTimestampDisposition disposition,
ushort previousTeleportSequence,
ushort acceptedTeleportSequence,
float playerDistance,
bool usePositionFromServer,
uint? committedCellId)
{
ArgumentNullException.ThrowIfNull(canonical);
var authority = new RuntimeAuthoritativePositionAuthority(
@ -68,7 +107,7 @@ internal static class RuntimeAcceptedPositionRouteRequests
update.Position,
update.PlacementId,
update.Velocity,
canonical.FullCellId,
committedCellId,
update.IsGrounded,
playerDistance,
usePositionFromServer,
@ -124,4 +163,53 @@ internal static class RuntimeAcceptedPositionRouteRequests
usePositionFromServer);
return true;
}
/// <summary>
/// C4 route 4b-3 (D1): the overload for a caller that holds the PRE-merge
/// committed cell explicitly (see the sibling <c>Build</c> overload's
/// doc) instead of letting this method read the already-merged
/// <paramref name="canonical"/> record. Refuses (returns
/// <see langword="false"/>) exactly like the overload above, PLUS when
/// <paramref name="committedCellId"/> itself is <see langword="null"/> —
/// an unknown pre-merge cell is "Runtime has no opinion", never a
/// fabricated 0.
/// </summary>
internal static bool TryBuild(
RuntimeGenerationToken generation,
RuntimeEntityRecord canonical,
in WorldSession.EntityPositionUpdate update,
RuntimePositionEntityKind entityKind,
RuntimeAcceptedPositionSource source,
PositionTimestampDisposition disposition,
ushort previousTeleportSequence,
ushort acceptedTeleportSequence,
float? playerDistance,
bool usePositionFromServer,
uint? committedCellId,
out RuntimeAcceptedPositionRouteRequest request)
{
ArgumentNullException.ThrowIfNull(canonical);
if (canonical.Key is not { } key
|| playerDistance is not { } distance
|| committedCellId is null)
{
request = default;
return false;
}
request = Build(
generation,
canonical,
key,
update,
entityKind,
source,
disposition,
previousTeleportSequence,
acceptedTeleportSequence,
distance,
usePositionFromServer,
committedCellId);
return true;
}
}

View file

@ -390,10 +390,15 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
reporting);
}
// Accepted wire position is intentionally distinct from committed
// residence. A target frame with a nonzero cell does not make a
// cellless canonical body resident; only a later Runtime SetPosition
// or simulation commit may change FullCellId.
// C4 route 4b-3 (D1): CommittedCellId is the caller's own committed
// cell, not derived from the accepted wire frame here — the LOCAL
// player's caller reads its own record's FullCellId (unaffected by
// this packet), and a remote's caller (D1) supplies the PRE-merge
// committed cell measured before the wire frame was merged onto the
// canonical record, never the (already merged) post-merge value.
// This is retail's `this_1->cell == 0` predicate — "this object has
// no resolved cell right now" — read at MoveOrTeleport's entry,
// before any placement.
bool cellless = !request.CommittedCellId.HasValue
|| request.CommittedCellId.Value == 0u;
if (request.Authority.TeleportAdvanced || cellless)

View file

@ -21,19 +21,29 @@ internal enum RuntimeRemoteAcceptedPositionArm : byte
/// <summary>Retail's <c>player_distance &gt;= 96f</c>
/// <c>StopInterpolating</c> @0x005163CB + <c>SetPositionSimple</c>
/// @0x005163D9. Route 4b-2 — this slice.</summary>
/// @0x005163D9. Route 4b-2.</summary>
FarSnapPlacement,
/// <summary>
/// C4 route 4b-3: retail's teleport/cell-less branch
/// (<c>TELEPORT_TS</c> advanced OR the body's cell is 0) —
/// <c>teleport_hook</c> @0x005163EF then <c>SetPosition</c>
/// @0x00516420, <c>return 1</c> @0x00516438. Decided AHEAD of the
/// contact test (D5), so this arm never competes with
/// <see cref="AirborneNoOperation"/>/<see cref="NearInterpolate"/>/
/// <see cref="FarSnapPlacement"/> for the same packet.
/// </summary>
TeleportPlacement,
/// <summary>
/// <b>acdream-only.</b> No retail arm corresponds to it, because retail
/// cannot reach the states that produce it: a classification of
/// <c>RejectedAuthority</c>/<c>RejectedData</c> (retail validates no wire
/// frame this way), <see langword="null"/> (retail always has a player, so
/// <c>player_distance</c> always exists — acdream returns null for every
/// remote packet during the login window before the local movement
/// controller exists), or the cell-less <c>SetPosition</c> half that route
/// 4b-3 will own. See <see cref="ResolveArm"/>'s remarks for the stated
/// policy.
/// frame this way), or <see langword="null"/> (retail always has a
/// player, so <c>player_distance</c> always exists — acdream returns
/// null for every remote packet during the login window before the local
/// movement controller exists). See <see cref="ResolveArm"/>'s remarks
/// for the stated policy.
/// </summary>
UnroutedCatchUp,
}
@ -80,106 +90,36 @@ internal static class RuntimeRemoteFarSnapPosition
&& (route.Value.SetPositionFlags & PhysicsSetPositionFlags.Teleport)
!= 0;
/// <summary>
/// The ONE predicate deciding whether retail's single
/// <c>SmartBox::HandleReceivedPosition</c> <c>ConstrainTo</c> site
/// (@0x00454272, inside <c>if (MoveOrTeleport(...) != 0)</c> @0x00454254)
/// is armed AFTER the position operation for this classification, rather
/// than by the legacy pre-operation App call.
///
/// <para>
/// Route 4a moved its two dispositions onto the post-operation arm; this
/// slice adds the far snap. Every remaining classification still arms
/// through the legacy pre-operation call site, unchanged. Both the legacy
/// gate and
/// <see cref="RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation"/>
/// read THIS predicate, so a classification can never be armed twice or
/// not at all.
/// </para>
/// </summary>
internal static bool OwnsAfterOperationConstraint(
RuntimeAuthoritativePositionRoute? route) =>
RuntimeRemoteSteadyStatePosition.OwnsSteadyState(route)
|| OwnsFarSnap(route);
/// <summary>
/// Selects the arm for one already-classified remote accepted Position.
/// <b>Does NOT decide the teleport arm</b> — C4 route 4b-3 (D5) routes a
/// teleport/cell-less classification (<c>RuntimeRemoteTeleportPosition.
/// OwnsTeleportPlacement</c>) AHEAD of this method entirely, from inside
/// <c>LiveEntityNetworkUpdateController.ApplyRemoteContactRouting</c>,
/// because retail decides that branch before reading the wire contact bit
/// this method's callers have already tested. This method is reached only
/// for a body already known to be in contact and NOT teleport-classified.
///
/// <para>
/// <b>The <see cref="RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp"/>
/// policy, stated.</b> Before this slice, every classification route 4a
/// did not own fell into a duplicated App-side near/far block that
/// re-derived retail's <c>player_distance &gt;= 96 m</c> test from
/// <c>_playerController?.Position ?? Vector3.Zero</c>. That test is
/// deleted here rather than preserved, because in every residual case it
/// was already meaningless or wrong:
/// policy, stated.</b> What remains here after 4b-3's teleport/cell-less
/// classification moved out is exactly the two acdream-only divergences
/// retail has no state for at all:
/// </para>
/// <list type="bullet">
/// <item><description><see langword="null"/> — the classifier returned no
/// route precisely BECAUSE there is no local-player position
/// route precisely BECAUSE there is no local-player position yet
/// (<c>RuntimeAcceptedPositionRouteRequests.TryBuild</c> refuses to
/// fabricate one; <c>GameRuntime</c> states the rule). The legacy block
/// then measured the distance from a fabricated <c>Vector3.Zero</c>.
/// <b>Review fix — what that actually computed:</b> <c>worldPos</c> is
/// streaming-origin-relative
/// (<c>LiveEntityNetworkUpdateController.cs</c>: local position plus
/// <c>(landblock _origin.Center) * 192 m</c>), and the streaming origin
/// recentres on the local player's landblock, so the fabricated distance
/// was the remote's distance from the ORIGIN LANDBLOCK'S CORNER — a
/// biased but genuinely correlated proxy for <c>player_distance</c>,
/// its error bounded by the player's own offset inside that landblock
/// (0-192 m per axis). The earlier claim that it had "no relationship to
/// <c>player_distance</c>" was false. It is deleted anyway because a
/// silently-biased proxy for retail's exact 96 m threshold is not a
/// threshold: the bias is up to ~2.8x the threshold itself, so the arm it
/// selects is not the arm retail would select, and there is no honest way
/// to correct for it without the player position the classifier already
/// declined to fabricate.</description></item>
/// <item><description>Cell-less <c>SetPosition</c> — the classifier
/// returns at <c>:399-417</c>, BEFORE it ever evaluates the distance.
/// Retail's cell-less body takes the <c>this_1-&gt;cell == 0</c> branch
/// @0x00516386 (<c>SetPosition</c>, flags <c>0x1012</c>), never the far
/// branch, so applying a far test to it was a divergence in its own
/// right. <b>Review fix — the residual delta, stated:</b> retail's
/// cell-less arm is an UNCONDITIONAL placement sitting ahead of the
/// contact test — <c>teleport_hook</c> @0x005163EF then <c>SetPosition</c>
/// @0x00516420, <c>return 1</c> @0x00516438 — while acdream now routes
/// that classification here, where <see cref="ApplyInterpolate"/> ENQUEUES
/// whenever <c>!firstUp &amp;&amp; willBeDrTicked &amp;&amp;
/// bodyToTarget &lt;= 4 m</c>, at any distance. <b>It is deliberately not
/// changed to place here.</b> Retail's arm is not a pose write: it is
/// <c>teleport_hook</c> @0x00514ED0 — the COMPLETE call list, read from
/// the decomp (delta review N6 added the last of these, which the earlier
/// enumeration dropped): <c>MovementManager::CancelMoveTo</c> @0x00514EDF,
/// <c>PositionManager::UnStick</c> @0x00514EEE,
/// <c>PositionManager::StopInterpolating</c> @0x00514EFD,
/// <c>PositionManager::UnConstrain</c> @0x00514F0C,
/// <c>TargetManager::ClearTarget</c> @0x00514F1B +
/// <c>NotifyVoyeurOfEvent(Teleported_TargetStatus)</c> @0x00514F28, and
/// <c>CPhysicsObj::report_collision_end(this, 1)</c> @0x00514F31 — followed
/// by the canonical flags-<c>0x1012</c>
/// <c>SetPosition</c>. Writing only the pose here would leave a live
/// moveto, a live stick, a live constraint leash pointing at the old
/// cell, and every collision partner still believing it is in contact,
/// which is strictly worse than the recorded queue. Porting the
/// whole arm is route 4b-3's entire scope; claiming half of it here is
/// route 4a's "'not Interpolate' is not 'far'" mistake one level up.
/// Recorded as AP-137.</description></item>
/// fabricate one; <c>GameRuntime</c> states the rule), or because the
/// merge observed no PRIOR canonical record for this entity (D1 — an
/// honest "no opinion" rather than a fabricated pre-merge
/// cell).</description></item>
/// <item><description><c>RejectedAuthority</c>/<c>RejectedData</c> — the
/// packet's authority or payload was refused; retail, which validates
/// neither, has no state here at all. <b>Review fix — stated plainly:</b>
/// this arm therefore APPLIES a payload the classifier just marked
/// invalid. <c>ClassifyAcceptedPosition</c> emits <c>RejectedData</c> from
/// two sites a remote can reach — an accepted wire position that fails
/// <c>ValidPosition</c> (non-finite origin/orientation or a frame
/// <c>PositionFrameValidation</c> refuses), and a non-finite or negative
/// derived <c>player_distance</c> — and both then take this arm. That is
/// not a regression — the deleted legacy block fed the same payload to the
/// same snap/enqueue pair — but this slice's stated purpose was an
/// EXPLICIT handler, so it is named rather than left implied. Retiring it
/// means giving the two rejections their own no-operation arm, which is a
/// behaviour change route 4b-3 must decide with the cell-less half, not a
/// tidy-up. Recorded as AP-137.</description></item>
/// neither, has no state here at all. This arm therefore APPLIES a
/// payload the classifier just marked invalid — not a regression (the
/// deleted legacy block fed the same payload to the same snap/enqueue
/// pair), but named rather than left implied.</description></item>
/// </list>
/// <para>
/// What remains is exactly AP-87 — the shared
@ -188,10 +128,8 @@ internal static class RuntimeRemoteFarSnapPosition
/// <c>firstUp || !willBeDrTicked || bodyToTarget &gt; 4 m</c> still PLACES
/// an unplaced or badly-lagging body. So a remote keeps tracking the
/// server through the login window and through a rejected packet; it is
/// never silently frozen, which is what deleting the legacy block without
/// a replacement would have done. The single 4 m constant now lives only
/// in <c>RuntimeRemoteSteadyStatePosition</c>; the App's two duplicated
/// copies are gone.
/// never silently frozen. The single 4 m constant lives only in
/// <c>RuntimeRemoteSteadyStatePosition</c>.
/// </para>
/// </summary>
internal static RuntimeRemoteAcceptedPositionArm ResolveArm(

View file

@ -516,10 +516,12 @@ internal sealed class RuntimeRemotePhysicsUpdater
// callbacks between the contact prefix and
// handle_all_collisions are HitGround/LeaveGround and the
// ownership re-check. The packet-driven placement paths are
// the ones that need it: `RemoteTeleportPlacement.Apply` is
// the only caller that passes the delegate, and
// `RuntimeSetPositionState`'s canonical commit makes the same
// check inline around its own `HandleAllCollisions`.
// the ones that need it: `RuntimeSetPositionState`'s
// canonical commit (which now backs every remote packet-
// driven placement, teleport included — C4 route 4b-3
// deleted the `RemoteTeleportPlacement.Apply` caller this
// note used to name) makes the same check inline around its
// own `HandleAllCollisions`.
//
// It is spelled out through its own public sub-steps
// (CommitSetPositionContactPrefix / the ground edge /
@ -663,11 +665,14 @@ internal sealed class RuntimeRemotePhysicsUpdater
// Bug B (2026-08-04): Airborne is DERIVED from the committed
// ON_WALKABLE transient, never latched by a landing test.
// This is the project's ONE definition of the flag — every
// writer spells `!Body.OnWalkable`, and there are FIVE of
// writer spells `!Body.OnWalkable`, and there are FOUR of
// them: `SettleSpawnedRemoteContact` (the spawn-settle
// tail) and `RemoteTeleportPlacement.Apply` in App,
// `RuntimeSetPositionState`'s canonical placement commit,
// and this file's two (here and the `TickHidden` resolve).
// tail), `RuntimeSetPositionState`'s canonical placement
// commit (C4 route 4b-3 retired the fifth writer this
// note used to name, `RemoteTeleportPlacement.Apply` in
// App — the teleport path's `Airborne` derivation is now
// this same canonical commit's), and this file's two
// (here and the `TickHidden` resolve).
// `PlayerMovementController.IsAirborne` computes the same
// predicate for the local player. It stays unchanged here;
// only the fact it is derived FROM has moved, from a

View file

@ -196,7 +196,7 @@ internal static class RuntimeRemoteSteadyStatePosition
}
/// <summary>
/// D2: retail arms <c>CPhysicsObj::ConstrainTo</c> strictly AFTER
/// D2/D4: retail arms <c>CPhysicsObj::ConstrainTo</c> strictly AFTER
/// <c>MoveOrTeleport</c> returns nonzero, anchored to the object's own
/// CURRENT (i.e. post-move) position — <c>SmartBox::HandleReceivedPosition</c>
/// 0x00453FD0 reads <c>&amp;arg2-&gt;m_position</c> at 0x00454272, inside
@ -204,38 +204,55 @@ internal static class RuntimeRemoteSteadyStatePosition
/// does NOT run on the airborne no-op.
///
/// <para>
/// Route-gated by
/// <see cref="RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint"/>
/// — route 4a's two dispositions plus (C4 route 4b-2) the far snap. Every
/// other classification still arms the leash through the legacy
/// pre-operation call site, unchanged, until 4b-3 moves them too. The
/// legacy site reads the SAME predicate, so no classification can be
/// armed twice or left unarmed.
/// </para>
///
/// <para>
/// This deliberately does not consult the outcome of the operation it
/// follows. Retail's far branch returns 1 @0x005163E8 unconditionally —
/// <c>MoveOrTeleport</c> discards <c>SetPositionSimple</c>'s
/// <c>enum SetPositionError</c> return entirely — so
/// <c>HandleReceivedPosition</c> arms the leash even when the placement
/// FAILED. "Arm only on commit" is the natural misreading and is the same
/// shape as the already-recorded unarmed-leash bug.
/// C4 route 4b-3 (D4): this is now the ONLY arming site — the legacy
/// pre-operation call is deleted, matching retail's single
/// @0x00454272. <paramref name="arm"/> is the routing outcome (which arm
/// actually claimed the packet), NOT the raw classification, because
/// that is the only input that correctly distinguishes a GROUNDED
/// <see cref="RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp"/> (arms —
/// retail has no state here, but the analogue of "MoveOrTeleport returned
/// nonzero" is true) from the wire-airborne leftover shape (D2's
/// return-0 replacement, which never reaches this call at all — see the
/// caller). The complete partition:
/// </para>
/// <list type="bullet">
/// <item><description><see cref="RuntimeRemoteAcceptedPositionArm.TeleportPlacement"/>
/// — arms on EVERY placement outcome (retail discards
/// <c>SetPosition</c>'s error and returns 1 unconditionally
/// @0x00516438).</description></item>
/// <item><description><see cref="RuntimeRemoteAcceptedPositionArm.FarSnapPlacement"/>
/// — arms unconditionally, same reason
/// (@0x005163E8).</description></item>
/// <item><description><see cref="RuntimeRemoteAcceptedPositionArm.NearInterpolate"/>
/// — arms; retail's InterpolateTo branch returns 1
/// (@0x005163BE).</description></item>
/// <item><description><see cref="RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp"/>
/// — arms; only reachable here when the body is already known to be in
/// contact (the caller's free-flight carve-out already
/// returned).</description></item>
/// <item><description><see cref="RuntimeRemoteAcceptedPositionArm.AirborneNoOperation"/>
/// — never arms; retail's <c>arg4 == 0</c> branch returns 0
/// (@0x0051636D). Both production callers already early-return on this
/// classification before reaching any arming call, so this case is
/// defensive.</description></item>
/// </list>
/// </summary>
internal static bool TryArmConstraintAfterOperation(
RuntimeAuthoritativePositionRoute? route,
RuntimeRemoteAcceptedPositionArm arm,
RemoteMotion remote)
{
ArgumentNullException.ThrowIfNull(remote);
if (route is not { } selected
|| !RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
selected)
|| !selected.ConstrainAfterRouting
|| remote.Host is not { } host)
bool arms = arm switch
{
RuntimeRemoteAcceptedPositionArm.TeleportPlacement => true,
RuntimeRemoteAcceptedPositionArm.FarSnapPlacement => true,
RuntimeRemoteAcceptedPositionArm.NearInterpolate => true,
RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp => true,
RuntimeRemoteAcceptedPositionArm.AirborneNoOperation => false,
_ => false,
};
if (!arms || remote.Host is not { } host)
return false;
}
ArmConstraintAfterOperation(host);
return true;

View file

@ -0,0 +1,47 @@
using AcDream.Core.Physics;
namespace AcDream.Runtime.Physics;
/// <summary>
/// C4 route 4b-3 (2026-08-04): the remote teleport/cell-less arm selection —
/// retail's <c>CPhysicsObj::MoveOrTeleport</c> (0x00516330) branch decided at
/// entry, BEFORE the contact test: <c>this_1-&gt;cell == 0 || newer_event(TELEPORT_TS)</c>
/// @0x00516375-@0x00516386. Sibling to <see cref="RuntimeRemoteFarSnapPosition"/>
/// (the &gt;=96 m far snap) and <see cref="RuntimeRemoteSteadyStatePosition"/>
/// (airborne no-op / near interpolate); together the three cover every arm a
/// remote's accepted Position can take.
/// </summary>
internal static class RuntimeRemoteTeleportPosition
{
/// <summary>
/// True when route 4b-3 owns this classification: retail's teleport/
/// cell-less branch, @0x00516386-@0x00516438.
///
/// <para>
/// The disposition alone is NOT sufficient — the classifier emits
/// <c>SetPosition</c> for a remote top-level Create too
/// (<c>RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate</c>,
/// flags <c>Placement|Slide</c>). The <c>Teleport</c> flag bit is the
/// exact discriminator — an accepted remote Position's teleport/cell-less
/// branch carries retail's <c>Teleport|Slide|SendPositionEvent</c> =
/// <c>0x1012</c>, exactly the flags a Create never sets — the same
/// discriminator <see cref="RuntimeRemoteFarSnapPosition.OwnsFarSnap"/>
/// uses for its own disposition. This predicate is therefore a strict
/// narrowing of
/// <see cref="AcDream.Runtime.Session.RuntimeRemotePlacementDriveController.OwnsPlacement"/>
/// to its teleport half, exactly as <c>OwnsFarSnap</c> narrows to its far
/// half — the two partition <c>OwnsPlacement</c>'s remote scope
/// completely and disjointly (a route can never carry both
/// <c>SetPosition</c> and <c>SetPositionSimple</c> at once).
/// </para>
/// </summary>
internal static bool OwnsTeleportPlacement(
RuntimeAuthoritativePositionRoute? route) =>
route is
{
Disposition: RuntimeAuthoritativePositionDisposition.SetPosition,
OperationKind: RuntimeSetPositionOperationKind.RemoteAuthoritative,
}
&& (route.Value.SetPositionFlags & PhysicsSetPositionFlags.Teleport)
!= 0;
}

View file

@ -4536,9 +4536,12 @@ internal sealed class RuntimeSetPositionState : IDisposable
/// read from that same record field. It is NOT invariant for a RETAINED
/// operation: both drives re-submit from their own cadence pump with no
/// fresh merge in between (<c>SubmitAndResolve</c> re-reads the record as
/// it then stands), and <c>RemoteTeleportController</c>'s rollback is a
/// shipped writer that rebuckets <c>FullCellId</c> back to the
/// PRE-teleport landblock. <c>RuntimeRemotePlacementDriveController</c>'s
/// it then stands). The surviving non-Position rebucket writers (C4
/// route 4b-3 deleted the third, <c>RemoteTeleportController</c>'s
/// rollback) are the projection materializer
/// (<c>DatLiveEntityProjectionMaterializer</c>) and the equipped-child
/// renderer (<c>EquippedChildRenderController.TickChild</c>).
/// <c>RuntimeRemotePlacementDriveController</c>'s
/// <c>CanAttemptDestination</c> doc states this correctly; treat the arm
/// as live, not as dead code.
/// </para>

View file

@ -204,7 +204,6 @@ public sealed class RuntimeLiveEntitySessionController
isLocal,
forcePositionRotation: localController?.BodyOrientation,
currentLocalVelocity: localController?.BodyVelocity,
projectionRequiresTeleportHook: false,
acknowledgeProjection: null,
out PositionTimestampDisposition disposition,
out _,

View file

@ -783,6 +783,65 @@ internal sealed class RuntimeRemotePlacementDriveController
return status;
}
/// <summary>
/// C4 route 4b-3: retail's teleport/cell-less branch, end to end, for one
/// remote whose accepted Position already classified to
/// <see cref="RuntimeRemoteTeleportPosition.OwnsTeleportPlacement"/>.
///
/// <code>
/// 00516386 if (newer_event(TELEPORT_TS) || this_1-&gt;cell == 0)
/// 005163ef CPhysicsObj::teleport_hook(this_1, edx_2);
/// 00516414 SetPositionStruct::SetFlags(&amp;var_64, 0x1012);
/// 00516420 CPhysicsObj::SetPosition(this_1, &amp;var_64);
/// 00516438 return 1;
/// </code>
///
/// <para>
/// The teleport hook is the CALLER's responsibility (D3) — it must run
/// BEFORE this method, regardless of what the placement then yields,
/// exactly like retail's ordering. This method does not clear the
/// interpolation queue itself: unlike the far arm, the classifier's
/// teleport branch carries <c>StopInterpolating: false</c> on purpose —
/// retail's clear for THIS branch lives inside <c>teleport_hook</c>'s
/// <c>PositionManager::StopInterpolating</c> @0x00514EFD, not in
/// <c>MoveOrTeleport</c> itself.
/// </para>
///
/// <para>
/// The <c>store_position</c> fallback (invariant 1) is identical to the
/// far arm's: every outcome for which the canonical placement never
/// reached the engine
/// (<see cref="RuntimeRemotePlacementExecutionStatusExtensions.StoresAcceptedDestination"/>)
/// still advances the body to the accepted destination pose. Retail
/// discards <c>SetPosition</c>'s error and returns 1 unconditionally
/// @0x00516438 — the placement's outcome never changes whether the
/// packet "succeeded".
/// </para>
/// </summary>
internal RuntimeRemotePlacementExecutionStatus ApplyAcceptedRemoteTeleport(
RuntimeEntityRecord record,
RemoteMotion remote,
in RuntimeAuthoritativePositionRoute route)
{
ArgumentNullException.ThrowIfNull(record);
ArgumentNullException.ThrowIfNull(remote);
if (!RuntimeRemoteTeleportPosition.OwnsTeleportPlacement(route))
{
throw new ArgumentException(
"Only a remote teleport/cell-less classification (SetPosition, "
+ "RemoteAuthoritative, Teleport-flagged) may be applied "
+ "through the teleport arm; the caller must select the arm "
+ "with RuntimeRemoteTeleportPosition.OwnsTeleportPlacement.",
nameof(route));
}
RuntimeRemotePlacementExecutionStatus status =
TryExecuteAcceptedRemotePosition(record, route);
if (status.StoresAcceptedDestination())
StoreAcceptedDestinationPose(record);
return status;
}
/// <summary>
/// Retail <c>CPhysicsObj::store_position</c> @0x00515CE2, reached from
/// <c>SetPositionInternal</c>'s no-resolvable-cell branch @0x00515C1D.
@ -1248,10 +1307,13 @@ internal sealed class RuntimeRemotePlacementDriveController
/// </list>
/// <para>
/// <see cref="Advance"/> re-reads this predicate and is subject to the
/// same two gaps, plus a third: a non-Position rebucket
/// (<c>RemoteTeleportController</c>, the projection materializer, the
/// equipped-child renderer) can move <c>record.FullCellId</c> to a THIRD
/// landblock between the retained submit and the retry. All three are
/// same two gaps, plus a third: a non-Position rebucket (the projection
/// materializer <c>DatLiveEntityProjectionMaterializer</c>, the
/// equipped-child renderer <c>EquippedChildRenderController.TickChild</c>
/// — C4 route 4b-3 deleted the third shipped writer,
/// <c>RemoteTeleportController</c>'s rollback) can move
/// <c>record.FullCellId</c> to a THIRD landblock between the retained
/// submit and the retry. Both remaining writers are
/// harmless for the same reason (delta review N3). That reason is the
/// paragraph below — NOT, as the round-2 text claimed, that re-reading
/// <c>record.CurrentCellId</c> here would "re-derive a private Core