fix(physics): C4 route 3 — portal placement authority (local player)

Removes a duplicate placement authority for local-player portal arrival.
Portalling worked before this change and works after it — this is not a
bug fix, EXCEPT that it found and fixed one dead-code production bug.

THE PRODUCTION BUG: TryExecuteCanonicalPortalPlacement re-read the
accepted destination at Place time, but TryBeginPortalReveal already
consumes that slot at Aim time — so the arm was 100% dead code and every
real portal Place refused with host-token-unavailable. Found only
because we refused to accept 7 skipped tests instead of chasing the
count to zero.

RETAIL IS THE GENERIC PATH FOR THE THIRD ROUTE RUNNING:
SmartBox::TeleportPlayer @0x00453910 = SetPositionSimple(dest, 1) with
flags 0x1012, followed by PlayerPositionUpdated.

BOTH INVERSIONS, WITH THEIR ANCHORS: unlike route 2, the leash IS armed
here (ConstrainTo @0x0045418A) and velocity is zeroed
(set_velocity @0x004541B4); unlike route 4b-3, the local teleport_hook
runs AFTER placement (@0x004538AE).

THE THREE-ROUND DEFECT CHAIN, HONESTLY:
- Round 1 released the player at the pre-teleport position while the
  anim stream marched on — the contract wrongly assumed Place re-fires
  (process rule 1's third occurrence this campaign).
- Round 2's fix inferred commit from a global PendingCount, which three
  non-committing paths also clear — making the SAME bug complete
  cleanly and silently. Strictly worse than round 1: round 1 at least
  tripped portal-complete-before-materialized.
- Round 3 latches the commit where it actually happens
  (ReconcileAndAcknowledgePortal), keyed on reveal generation and
  teleport sequence, via TryConsumePortalCommit. Two of the three
  required regression tests landed and are sabotage-verified on both
  hosts (ParkedPlace_ForgottenByOrdinaryMergeDoesNotLatchAsCommitted /
  HeadlessPortalPrepareDestinationForgottenByOrdinaryMergeDoesNotLatchAsCommitted).
  The third (force-arm-takes-the-slot) was judged unnecessary on review:
  with the inference gone, PendingCount is only a "don't ask yet" guard
  at both gates, so a force operation occupying or vacating the slot no
  longer changes an input the commit decision reads — the case collapses
  into what the landed test already discriminates.

THE B2/P3 RESOLUTION: both round-2 reviews were right about different
branches of the same synchronous call. RuntimePlacementProjectionSubscription
.OnPlacement acknowledges the FIFO head only when TryApply returns true;
a Place whose portal authority went stale (transit ended/superseded
while parked) used to return false, wedging every later entity's
placement receipt behind it forever. Both sinks
(RuntimePlacementPresentationSink, HeadlessRuntimePlacementProjectionSink)
now acknowledge-and-ignore a stale-authority Place instead of refusing
it. The regression test (RuntimePlacementPresentationSinkTests
.PortalPlace_StaleTransitHostOrSequenceIsAcknowledgedAndIgnored) had
been asserting the old, wrong `false` behaviour; it now asserts and
sabotage-verifies the fix.

Also lands: AP-144 (register discipline — the portal movement-event
send reuses the stricter UsePositionFromServer gate where retail's
SendMovementEvent is the looser autonomy_level != 0 test, diverging
only at level 1, currently unreachable), AP-145 + issue #318 (the
local-player collision-shadow presentation write bypasses its own
publisher's ShadowObjects write via a direct cache .Set(), self-healing
only once dedup diverges — filed, not fixed, pending a composition
test), AD-42 deleted (its last citation retired by the canonical portal
arm), AD-2 updated (the wait-cue's trigger predicate now covers a
second cause), and two documentation corrections: the enter_world
misattribution (both call sites are in SmartBox::HandleCreateObject,
only one in the player branch — portal arrival is TeleportPlayer, not
enter_world) and the stale "local player never reaches this path"
comment on the generic-remote-render-pose write.

Suite: 11,090 passed / 4 skipped / 0 failed. No new skips, nothing
weakened.

STILL OWED: the connected two-client gate, with
ACDREAM_PROBE_LOCAL_TELEPORT=1, scored only if [local-tp] lines
actually appear in the capture — and explicitly NOT scored as covering
issue #318 (no composition test yet asserts PhysicsEngine.ShadowObjects
directly).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-05 03:57:37 +02:00
parent cd3129e9d6
commit e0f96a55bf
24 changed files with 5261 additions and 243 deletions

View file

@ -10,6 +10,8 @@ using AcDream.Core.Physics;
using AcDream.Core.Rendering;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.Physics;
using AcDream.Runtime.Session;
using AcDream.Runtime.World;
namespace AcDream.App.Streaming;
@ -173,68 +175,67 @@ internal sealed class LocalPlayerTeleportStreamingOperations
internal interface ILocalPlayerTeleportPlacement
{
void Place(Vector3 position, uint cellId, Quaternion rotation);
void Place(Quaternion rotation);
}
/// <summary>
/// Commits the local player's deferred portal arrival. It owns the exact
/// Place -&gt; root/controller/camera mutation -&gt; spatial reconcile edge.
/// C4 route 3: acknowledges the local player's deferred portal arrival —
/// the canonical placement itself now runs through
/// <see cref="RuntimeAcceptedPositionDriveController.TryExecuteAcceptedPortalArrival"/>
/// (a portal arm sharing route 2's Runtime SetPosition owner), retiring the
/// duplicate Resolve/SetPosition authority this class used to own (D1;
/// docs/research/2026-08-04-c4-route-3-contract.md D-T4). This class runs
/// AFTER that commit succeeds. A10 review fix (2026-08-05): the render
/// <see cref="WorldEntity"/> pose write and rebucket this method performs
/// are REDUNDANT repeats of a mutation the canonical Place receipt already
/// made — <c>RuntimePlacementPresentationSink.TryApply</c> →
/// <c>LiveEntityRuntime.TryApplyRuntimePlacementPlace</c> already calls
/// <c>entity.SetPosition</c>/sets <c>Rotation</c>/<c>ParentCellId</c> and
/// rebuckets, synchronously, before <c>TryPublishPlace</c>'s OWN snapshot
/// even runs (proof obligation P2's ordering). This method's writes are
/// therefore harmless-but-duplicate, not the render entity's ONLY mover as
/// an earlier revision of this comment claimed; kept because they cost
/// nothing extra and this is also where the retail teleport_hook tail's
/// remaining local-player-visible actions run (target-watcher
/// notification, camera reset, spatial reconcile).
/// <paramref name="rotation"/> is the retained accepted destination's wire
/// orientation — the resolved body orientation was already committed
/// identically by <c>CommitCanonical</c> (retail's teleport branch does not
/// independently reorient the mover), so re-deriving it here would only add
/// a second copy of the same source of truth.
/// </summary>
internal sealed class LocalPlayerTeleportPlacement : ILocalPlayerTeleportPlacement
{
private readonly PhysicsEngine _physics;
private readonly LiveEntityRuntime _liveEntities;
private readonly ILocalPlayerIdentitySource _identity;
private readonly IRuntimeLocalPlayerControllerSource _controller;
private readonly ILocalPlayerPhysicsHostSource _host;
private readonly ChaseCameraInputState _cameras;
private readonly LiveWorldOriginState _origin;
private readonly ILiveSpatialReconcilePhase _spatial;
public LocalPlayerTeleportPlacement(
PhysicsEngine physics,
LiveEntityRuntime liveEntities,
ILocalPlayerIdentitySource identity,
IRuntimeLocalPlayerControllerSource controller,
ILocalPlayerPhysicsHostSource host,
ChaseCameraInputState cameras,
LiveWorldOriginState origin,
ILiveSpatialReconcilePhase spatial)
{
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
_controller = controller ?? throw new ArgumentNullException(nameof(controller));
_host = host ?? throw new ArgumentNullException(nameof(host));
_cameras = cameras ?? throw new ArgumentNullException(nameof(cameras));
_origin = origin ?? throw new ArgumentNullException(nameof(origin));
_spatial = spatial ?? throw new ArgumentNullException(nameof(spatial));
}
public void Place(Vector3 position, uint cellId, Quaternion rotation)
public void Place(Quaternion rotation)
{
PlayerMovementController controller = _controller.Controller
?? throw new InvalidOperationException(
"Teleport Place ran without the local player controller.");
var resolved = _physics.Resolve(
position,
cellId,
Vector3.Zero,
controller.StepUpHeight);
var snapped = new Vector3(
resolved.Position.X,
resolved.Position.Y,
resolved.Position.Z);
uint playerGuid = _identity.ServerGuid;
controller.SetPosition(
snapped,
resolved.CellId,
CellLocalForSeed(snapped, resolved.CellId));
// SnapToCell owns the retail Position frame and may normalize an
// outdoor land-cell index from the cell-local origin. Publish that
// canonical result, not the pre-snap resolver hint, to rendering.
if (_liveEntities.TryGetWorldEntity(
playerGuid,
out WorldEntity? entity))
@ -245,12 +246,12 @@ internal sealed class LocalPlayerTeleportPlacement : ILocalPlayerTeleportPlaceme
// Retail CPhysicsObj::enter_world installs the object in its
// destination CObjCell before hidden scripts/particles resume.
// The accepted Position packet has already advanced FullCellId,
// but that wire fact alone does not move acdream's retained
// projection out of its source/pending GPU bucket. Commit both
// halves of the spatial move here, while portal space still owns
// the viewport, so CPhysicsObj::update_object's cell-gated tail
// can advance the Hidden/UnHide PES chain at retail's boundary.
// The canonical commit has already advanced FullCellId, but that
// fact alone does not move acdream's retained projection out of
// its source/pending GPU bucket. Commit both halves of the
// spatial move here, while portal space still owns the
// viewport, so CPhysicsObj::update_object's cell-gated tail can
// advance the Hidden/UnHide PES chain at retail's boundary.
if (!_liveEntities.RebucketLiveEntity(playerGuid, controller.CellId))
{
throw new InvalidOperationException(
@ -260,9 +261,13 @@ internal sealed class LocalPlayerTeleportPlacement : ILocalPlayerTeleportPlaceme
}
// Retail teleport_hook tail @ 0x00514ED0 clears the local target and
// notifies every watcher that this object teleported.
// notifies every watcher that this object teleported. The body's
// constraint leash re-arm and orientation are already the canonical
// commit's job (RuntimeAcceptedPositionDriveController
// .ReconcileAndAcknowledgePortal -> PlayerMovementController
// .CommitCanonicalTeleportFrame), so this suffix only acknowledges
// the result into presentation.
_host.Host?.NotifyTeleported();
controller.SetBodyOrientation(rotation);
_cameras.Legacy?.Update(controller.Position, controller.Yaw);
_cameras.Retail?.ResetViewerToPlayer(controller.Position, controller.Yaw);
@ -276,17 +281,6 @@ internal sealed class LocalPlayerTeleportPlacement : ILocalPlayerTeleportPlaceme
$"live: teleport materialized - snapped to {controller.Position} "
+ $"cell=0x{controller.CellId:X8}");
}
private Vector3 CellLocalForSeed(Vector3 worldPosition, uint cellId)
{
int landblockX = (int)((cellId >> 24) & 0xFFu);
int landblockY = (int)((cellId >> 16) & 0xFFu);
var origin = new Vector3(
(landblockX - _origin.CenterX) * 192f,
(landblockY - _origin.CenterY) * 192f,
0f);
return worldPosition - origin;
}
}
internal interface ILocalPlayerTeleportSession
@ -397,11 +391,28 @@ internal sealed class LocalPlayerTeleportController
private readonly ILocalPlayerTeleportPlacement _placement;
private readonly ILocalPlayerTeleportSession _session;
private readonly ILocalPlayerTeleportPresentation _presentation;
private readonly RuntimeAcceptedPositionDriveController _acceptedPositionDrive;
private Vector3 _pendingPosition;
private uint _pendingCell;
private Quaternion _pendingRotation = Quaternion.Identity;
private long _pendingRevealGeneration;
private RuntimeTeleportDestination _pendingDestination;
private bool _hasPendingDestination;
/// <summary>
/// A1 review fix (2026-08-05): true once the canonical Runtime commit
/// for THIS teleport lifetime has actually happened. See the class doc
/// on <see cref="TryAdvancePortalCommit"/> for why this exists.
/// </summary>
private bool _placementCommitted;
/// <summary>
/// A1 review fix: true while a <c>DeferredCell</c> park is outstanding
/// for the local player's one possible pending operation. See
/// <see cref="TryAdvancePortalCommit"/>.
/// </summary>
private bool _awaitingDeferredWake;
private float _holdSeconds;
private long _lifetimeGeneration;
private bool _disposed;
@ -415,7 +426,8 @@ internal sealed class LocalPlayerTeleportController
WorldRevealCoordinator worldReveal,
ILocalPlayerTeleportPlacement placement,
ILocalPlayerTeleportSession session,
ILocalPlayerTeleportPresentation presentation)
ILocalPlayerTeleportPresentation presentation,
RuntimeAcceptedPositionDriveController acceptedPositionDrive)
{
_authority = authority ?? throw new ArgumentNullException(nameof(authority));
_input = input ?? throw new ArgumentNullException(nameof(input));
@ -426,6 +438,8 @@ internal sealed class LocalPlayerTeleportController
_placement = placement ?? throw new ArgumentNullException(nameof(placement));
_session = session ?? throw new ArgumentNullException(nameof(session));
_presentation = presentation ?? throw new ArgumentNullException(nameof(presentation));
_acceptedPositionDrive = acceptedPositionDrive
?? throw new ArgumentNullException(nameof(acceptedPositionDrive));
}
public bool IsActive => _transit.IsTeleportActive;
@ -488,21 +502,41 @@ internal sealed class LocalPlayerTeleportController
bool haveDestination = _pendingCell != 0u;
bool originReady = !_streaming.IsRecenterPending;
bool ready = haveDestination
bool dataReady = haveDestination
&& originReady
&& _worldReveal.Evaluate(_pendingCell).IsReady;
if (!IsCurrentLifetime(generation, sequence))
return;
if (haveDestination && !ready)
// A1 review fix (2026-08-05, retail/architecture review): the
// sequencer's Tunnel -> TunnelContinue transition
// (TeleportAnimSequencer.cs:134-141) is unconditional and
// irreversible the instant it observes `worldReady` true; by the
// time a failed placement is discovered the stream has already left
// Tunnel with no path back, and TeleportAnimSequencer itself is
// untouched (stop condition 2 forbids sequencer timing changes). So
// the boolean fed into the sequencer must never mean "the data is
// ready" alone - it must mean "the canonical Runtime commit has
// ALREADY happened", checked/attempted fresh every tick via
// TryAdvancePortalCommit. This makes D-T5 row 2's "the NEXT Tick
// re-attempts the Place edge" real: the sequencer simply never
// leaves Tunnel while the commit keeps refusing, and by the time it
// finally does leave Tunnel and fire Place, TryAdvancePortalCommit
// has already made the Runtime side succeed - the Place-event
// handler below only ever runs the presentation suffix.
bool placementReady = dataReady && TryAdvancePortalCommit(sequence);
if (!IsCurrentLifetime(generation, sequence))
return;
if (haveDestination && !placementReady)
_holdSeconds += deltaSeconds;
_presentation.SetWaitCue(
haveDestination
&& !ready
&& !placementReady
&& _worldReveal.ObserveWait(
TimeSpan.FromSeconds(_holdSeconds)));
var (_, events) = _presentation.Tick(deltaSeconds, ready);
var (_, events) = _presentation.Tick(deltaSeconds, placementReady);
if (!IsCurrentLifetime(generation, sequence))
return;
@ -511,17 +545,32 @@ internal sealed class LocalPlayerTeleportController
switch (teleportEvent)
{
case TeleportAnimEvent.Place:
// TryAdvancePortalCommit above is the only path that
// makes `placementReady` (and, with the REAL sequencer,
// this event) true, so the canonical Runtime commit has
// ALREADY succeeded by construction on that path - this
// only runs the presentation suffix (D-T4). The
// _placementCommitted re-check stays defensive: it is
// the exact same shape of guard IsCurrentLifetime below
// already applies to every other step of this case, for
// a transit that goes stale between the gate above and
// this line being reached.
if (!_placementCommitted)
return;
// B7 review fix (2026-08-05): re-derived, not assumed -
// if the reveal was cancelled/superseded in the window
// between the commit above and this event being
// processed, ObserveMaterialized below would refuse but
// Place/the presentation suffix would already have run
// against a reveal that is no longer current. Same
// check TryExecuteCanonicalPortalPlacementCore itself
// gates on; idempotent to repeat here.
if (!_worldReveal.CanPlacePortalDestination(
_pendingRevealGeneration,
sequence,
_pendingCell))
_pendingRevealGeneration, sequence, _pendingCell))
{
return;
}
_placement.Place(
_pendingPosition,
_pendingCell,
_pendingRotation);
_placement.Place(_pendingRotation);
if (!IsCurrentLifetime(generation, sequence))
return;
_worldReveal.ObserveMaterialized(
@ -569,6 +618,193 @@ internal sealed class LocalPlayerTeleportController
_presentation.TickTunnel(deltaSeconds);
}
/// <summary>
/// A1 review fix (2026-08-05): the one gate that decides whether the
/// anim sequencer is allowed to see <c>worldReady=true</c>. Returns
/// <see langword="true"/> ONLY once the canonical Runtime commit for
/// THIS teleport lifetime has actually happened — never speculatively,
/// never optimistically. Three states:
///
/// <list type="bullet">
/// <item><description>Already committed
/// (<see cref="_placementCommitted"/>) — returns
/// <see langword="true"/> immediately, every subsequent Tick.</description></item>
/// <item><description>A <c>DeferredCell</c> park is outstanding
/// (<see cref="_awaitingDeferredWake"/>) — polls
/// <see cref="RuntimeAcceptedPositionDriveController.PendingCount"/>
/// only to decide whether to re-attempt the Runtime call: Runtime's own
/// Begin would just refuse a second overlapping attempt with
/// <c>Contention</c> while a park is outstanding (the drive tracks at
/// most one pending operation for the local player), so retrying blind
/// would only add noise. B1 review fix (2026-08-05): once
/// <c>PendingCount</c> returns to 0 the park is DONE, but "done" is not
/// "committed" — the drive's own doc names a merge-time <c>Forget</c>
/// (an ordinary ACE broadcast arriving mid-park) as the EXPECTED way a
/// park resolves without committing, and A2's own abandon branches are
/// a second way. The OLD code inferred commit from the empty slot
/// alone; this now asks
/// <see cref="RuntimeAcceptedPositionDriveController.TryConsumePortalCommit"/>,
/// which the drive latches ONLY inside a REAL
/// <c>ReconcileAndAcknowledgePortal</c> call, keyed to this exact
/// reveal generation/sequence. A "no" here is NOT a failure — it just
/// means the park ended without placing, so <see cref="_awaitingDeferredWake"/>
/// clears and the method falls through to a fresh attempt below,
/// safely (nothing is pending anymore).</description></item>
/// <item><description>Neither — attempt the canonical placement fresh
/// this tick. <c>Committed</c> latches
/// <see cref="_placementCommitted"/>; <c>DeferredCell</c> latches
/// <see cref="_awaitingDeferredWake"/>; every other status (Contention,
/// Rejected, NotApplicable, or the transit no longer owning this
/// reveal) is the D-T5 refusal shape — nothing mutates, and the SAME
/// predicate retries automatically on the NEXT Tick, which is what
/// makes D-T5 row 2's "the next Tick re-attempts the Place edge"
/// mechanism real without ever touching
/// <see cref="AcDream.Core.World.TeleportAnimSequencer"/>.</description></item>
/// </list>
/// </summary>
private bool TryAdvancePortalCommit(ushort sequence)
{
if (_placementCommitted)
return true;
if (_awaitingDeferredWake)
{
if (_acceptedPositionDrive.PendingCount != 0)
return false;
_awaitingDeferredWake = false;
if (_acceptedPositionDrive.TryConsumePortalCommit(
_pendingRevealGeneration, sequence))
{
_placementCommitted = true;
return true;
}
// The park ended without placing (Forgotten, or abandoned by
// A2's re-validation). Fall through to the fresh-attempt path
// below in this SAME call — nothing is pending, so it is safe.
}
if (!_worldReveal.CanPlacePortalDestination(
_pendingRevealGeneration,
sequence,
_pendingCell))
{
PhysicsDiagnostics.LogTeleport(
"REFUSED", _pendingCell, "cause=stale-reveal");
// R8 residual fix (2026-08-05): this refusal previously only
// logged through PhysicsDiagnostics.LogTeleport, gated by the
// DIFFERENT ACDREAM_PROBE_TELEPORT flag — invisible under
// ACDREAM_PROBE_LOCAL_TELEPORT, the gate the rest of this
// route's arrival/commit lines use. No placement was attempted,
// so there is no resolved cell/leash/autorun fact to report.
PhysicsDiagnostics.LogLocalTeleportArrival(
cause: "stale-reveal",
placementStatus: "Refused",
portalGeneration: _pendingRevealGeneration,
teleportSequence: sequence,
destinationCell: _pendingCell,
resolvedCell: 0u,
hookTailRan: false,
leashArmed: false,
autorunCancelled: false);
return false;
}
RuntimeAcceptedPositionExecutionStatus status =
TryExecuteCanonicalPortalPlacementCore(sequence);
switch (status)
{
case RuntimeAcceptedPositionExecutionStatus.Committed:
_placementCommitted = true;
return true;
case RuntimeAcceptedPositionExecutionStatus.DeferredCell:
_awaitingDeferredWake = true;
return false;
default:
return false;
}
}
/// <summary>
/// C4 route 3 (D-T1/D-T2): builds the producer's
/// <see cref="RuntimePortalPlacementAuthority"/> from live transit facts
/// and drives the canonical Runtime portal arm. No new
/// <see cref="WorldRevealCoordinator"/> exposure is needed — the host
/// token is RE-DERIVED through the transit owner's idempotent
/// <c>TryRegisterHostProjection</c> (the same generation+cell returns
/// the token <see cref="WorldRevealCoordinator.TryBeginPortal"/> already
/// registered at Aim time; a stale generation, wrong cell, cancelled, or
/// completed reveal refuses), which makes a superseded token unobtainable
/// by construction.
///
/// <para>
/// The destination itself is <see cref="_pendingDestination"/> — the
/// value <see cref="AimDestination"/> captured at Aim time — and NOT a
/// fresh <c>_transit.TryGetAcceptedTeleportDestination</c> read.
/// <see cref="RuntimeWorldTransitState.TryBeginPortalReveal"/> atomically
/// CONSUMES the transit's one accepted-destination slot the instant Aim
/// claims the reveal generation (it clears
/// <c>_hasAcceptedDestination</c> so a stale destination can never be
/// re-claimed by a later portal) — by Place time that slot is already
/// empty, so re-querying it here always fails. This mirrors why
/// <see cref="_pendingCell"/>/<see cref="_pendingRotation"/>/
/// <see cref="_pendingRevealGeneration"/> are themselves Aim-time
/// snapshots rather than live transit reads.
/// </para>
///
/// <para>
/// A9 review fix (2026-08-05): <paramref name="sequence"/> is
/// <see cref="_pendingDestination"/>'s OWN
/// <see cref="RuntimeTeleportDestination.TeleportSequence"/>, not the
/// transit's separately-tracked <c>ActiveTeleportSequence</c> the caller
/// otherwise threads through — one source for the fact this method's
/// authority carries, asserted equal to the caller's copy so the two
/// can never silently diverge.
/// </para>
/// </summary>
private RuntimeAcceptedPositionExecutionStatus
TryExecuteCanonicalPortalPlacementCore(ushort sequence)
{
System.Diagnostics.Debug.Assert(
!_hasPendingDestination
|| _pendingDestination.TeleportSequence == sequence,
"The transit's active sequence and the Aim-time destination's "
+ "own sequence must never diverge (A9).");
if (!_hasPendingDestination
|| !_transit.TryRegisterHostProjection(
_pendingRevealGeneration,
_pendingCell,
out RuntimeWorldHostProjectionToken hostToken))
{
PhysicsDiagnostics.LogTeleport(
"REFUSED", _pendingCell, "cause=host-token-unavailable");
// R8 residual fix (2026-08-05): same rationale as the
// stale-reveal refusal above — route through
// LogLocalTeleportArrival too, so ACDREAM_PROBE_LOCAL_TELEPORT
// alone is enough to see every App-side refusal cause.
PhysicsDiagnostics.LogLocalTeleportArrival(
cause: "host-token-unavailable",
placementStatus: "Refused",
portalGeneration: _pendingRevealGeneration,
teleportSequence: sequence,
destinationCell: _pendingCell,
resolvedCell: 0u,
hookTailRan: false,
leashArmed: false,
autorunCancelled: false);
return RuntimeAcceptedPositionExecutionStatus.Rejected;
}
RuntimeTeleportDestination destination = _pendingDestination;
var portal = new RuntimePortalPlacementAuthority(
Present: true,
RevealGeneration: _pendingRevealGeneration,
TeleportSequence: destination.TeleportSequence,
Projection: hostToken);
return _acceptedPositionDrive.TryExecuteAcceptedPortalArrival(
destination,
portal);
}
public void ResetSession()
{
ThrowIfDisposed();
@ -695,7 +931,6 @@ internal sealed class LocalPlayerTeleportController
if (!IsCurrentLifetime(generation, sequence))
return false;
Vector3 worldPosition;
if (transition.ChangesStreamingCenter)
{
bool isSealedDungeon = _streaming.IsSealedDungeon(
@ -709,19 +944,24 @@ internal sealed class LocalPlayerTeleportController
isSealedDungeon);
if (!IsCurrentLifetime(generation, sequence))
return false;
worldPosition = new Vector3(
position.Frame.Origin.X,
position.Frame.Origin.Y,
position.Frame.Origin.Z);
}
else
{
worldPosition = translated;
}
// C4 route 3: the App-frame-translated `translated`/`worldPosition`
// vector is no longer carried past this point — the canonical
// portal arm resolves the placement through Runtime's OWN world
// frame (resolveWorldOffsetFromRuntimeFrame: true), using the
// cell-local `destination` Position captured HERE rather than an
// App-translated snapshot (trap T4). It must be captured here and
// NOT re-read from the transit at the Place edge:
// _worldReveal.TryBeginPortal (above) drives
// RuntimeWorldTransitState.TryBeginPortalReveal, which atomically
// CONSUMES the transit's one accepted-destination slot the instant
// it claims this reveal generation — a later
// TryGetAcceptedTeleportDestination call always finds it empty.
_pendingRotation = position.Frame.Orientation;
_pendingPosition = worldPosition;
_pendingCell = position.ObjCellId;
_pendingDestination = destination;
_hasPendingDestination = true;
_holdSeconds = 0f;
PhysicsDiagnostics.LogTeleport(
"AIM",
@ -739,10 +979,13 @@ internal sealed class LocalPlayerTeleportController
{
long generation = checked(++_lifetimeGeneration);
_pendingPosition = default;
_pendingCell = 0u;
_pendingRotation = Quaternion.Identity;
_pendingRevealGeneration = 0;
_pendingDestination = default;
_hasPendingDestination = false;
_placementCommitted = false;
_awaitingDeferredWake = false;
_holdSeconds = 0f;
_streaming.ResetRecenter(clearSession);