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

@ -130,6 +130,11 @@ internal enum RuntimeAcceptedPositionExecutionStatus : byte
/// early return (@0x0045409D) never reaches; the deleted
/// <c>PlayerMovementController.BlipPosition</c>'s re-arm was an unbacked
/// deviation this route retires (docs/research/2026-08-03-c4-route-2-implementation-plan.md §1b).
/// This no-re-arm rule is scoped to FORCE_POSITION only — C4 route 3's
/// portal arm (<see cref="TryExecuteAcceptedPortalArrival"/>) DOES re-arm,
/// because retail's local TELEPORT branch of the same function reaches
/// <c>ConstrainTo</c> @0x0045418A; see
/// docs/research/2026-08-04-c4-route-3-contract.md §2 Inversion A.
///
/// One instance per host session route (graphical/headless), constructed
/// once per host process and reused across reconnects exactly like
@ -170,8 +175,26 @@ public sealed class RuntimeAcceptedPositionDriveController
/// begin: that marker stands for a packet whose placement was never
/// begun, so its ack is owed by the eventual re-issue's own terminal
/// outcome, not by the marker.
///
/// C4 route 3: always <c>false</c> for a portal pending — the
/// portal route's <see cref="RuntimeAuthoritativePositionRoute.SendPositionImmediately"/>
/// is always <c>false</c> (retail's teleport branch never sends
/// <c>AutonomousPosition</c>), so there is never an owed position
/// event to carry.
/// </summary>
internal required bool PositionEventOwed { get; init; }
/// <summary>
/// C4 route 3: <see cref="RuntimePortalPlacementAuthority.Present"/>
/// when this descriptor is the trap T7 (2026-08-04 contract) portal
/// arm's own DeferredCell park — a SIBLING use of this same
/// retained-operation machinery, not a repurposed force pending.
/// <see cref="Advance"/> and <see cref="SubmitAndResolvePortal"/> are
/// the only writers/readers that branch on it; the force funnel
/// (<see cref="SettlePending"/>, <see cref="_newestForce"/>) never
/// sees or produces a portal pending.
/// </summary>
internal RuntimePortalPlacementAuthority Portal { get; init; }
}
/// <summary>
@ -218,6 +241,40 @@ public sealed class RuntimeAcceptedPositionDriveController
private readonly Func<bool> _usePositionFromServer;
private readonly Func<WorldSession?> _session;
/// <summary>
/// C4 route 3: the D-T3 <c>PlayerTeleported</c> port
/// (<c>CommandInterpreter::PlayerTeleported</c> @0x006B32B0 =
/// <c>SetAutoRun(0,1)</c> + <c>SendMovementEvent</c>) needs the J5.4
/// autorun latch owner, which lives one level above
/// <see cref="PlayerMovementController"/> and is not reachable from
/// <see cref="_localController"/>. Late-bound like every other
/// dependency here so this controller does not need to outlive a
/// specific movement-owner instance across a reconnect.
/// </summary>
private readonly Func<RuntimeLocalPlayerMovementState?> _localMovementState;
/// <summary>
/// A2 review fix (2026-08-05, D-T2.4): re-validates a retained portal
/// authority against the transit owner's CURRENT reveal before either
/// wake site in <see cref="Advance"/> acts on it. A <c>DeferredCell</c>
/// park commits asynchronously (<c>RuntimeSetPositionState.RetryDeferred</c>,
/// driven entirely by an unrelated collision-generation wake) — nothing
/// in this class can prevent that body-level commit once it starts. What
/// this predicate CAN prevent is running the reconcile/ack suffix (or a
/// stale resubmission) against a reveal that ended or was superseded
/// while the park was outstanding — exactly the D-T2.4 requirement this
/// slice's first pass never implemented (architecture review A2). Wired
/// by each host composition to
/// <c>RuntimeWorldTransitState.CanPlacePortalDestination</c> (the SAME
/// idempotent query <see cref="TryExecuteAcceptedPortalArrival"/>'s
/// caller already uses at the Place edge); left <see langword="null"/>
/// by fixtures that do not exercise the DeferredCell wake, in which case
/// every retained portal pending is treated as still current (today's
/// unconditional behaviour, preserved for callers that never park).
/// </summary>
private readonly Func<RuntimePortalPlacementAuthority, bool>?
_isPortalAuthorityCurrent;
/// <summary>
/// The drive's at-most-one in-flight placement for the local player.
/// Round 2 unified mechanism (2026-08-03): exactly THREE members write
@ -244,7 +301,9 @@ public sealed class RuntimeAcceptedPositionDriveController
Func<uint> localPlayerServerGuid,
Func<PlayerMovementController?> localController,
Func<bool> usePositionFromServer,
Func<WorldSession?> session)
Func<WorldSession?> session,
Func<RuntimeLocalPlayerMovementState?>? localMovementState = null,
Func<RuntimePortalPlacementAuthority, bool>? isPortalAuthorityCurrent = null)
{
_entityObjects = entityObjects
?? throw new ArgumentNullException(nameof(entityObjects));
@ -262,12 +321,53 @@ public sealed class RuntimeAcceptedPositionDriveController
_usePositionFromServer = usePositionFromServer
?? throw new ArgumentNullException(nameof(usePositionFromServer));
_session = session ?? throw new ArgumentNullException(nameof(session));
_localMovementState = localMovementState ?? (static () => null);
_isPortalAuthorityCurrent = isPortalAuthorityCurrent;
_entityObjects.RegisterAcceptedPositionDriveOwnership(
() => _pending is null ? 0 : 1);
}
internal int PendingCount => _pending is null ? 0 : 1;
/// <summary>
/// B1 review fix (2026-08-05): the drive's own record of the LAST portal
/// authority <see cref="ReconcileAndAcknowledgePortal"/> actually
/// committed — set only there, so this is never an inference. Both host
/// gates were latching "committed" from <see cref="PendingCount"/>
/// reaching zero, but that global (force-arm-shared) slot ALSO clears on
/// three non-committing paths (a merge-time <c>Forget</c> — the drive's
/// own doc names this the EXPECTED outcome of a park surviving one ACE
/// broadcast interval — and both of A2's new abandon branches), so
/// "not pending" never implied "this specific reveal placed". Consumed
/// exactly once per commit via <see cref="TryConsumePortalCommit"/>.
/// </summary>
private (long RevealGeneration, ushort TeleportSequence)? _lastCommittedPortal;
/// <summary>
/// B1 review fix: the host gate's ONLY correct way to learn "did MY
/// specific reveal commit" — never infer it from
/// <see cref="PendingCount"/>. Returns <see langword="true"/> and
/// consumes the fact exactly once when the drive's last portal commit
/// matches the caller's own (revealGeneration, teleportSequence); a
/// mismatch (nothing committed yet, a DIFFERENT reveal committed, or
/// this generation's park was abandoned/forgotten instead) returns
/// <see langword="false"/> without side effects, so the caller keeps
/// retrying or falls through to a fresh attempt.
/// </summary>
internal bool TryConsumePortalCommit(
long revealGeneration,
ushort teleportSequence)
{
if (_lastCommittedPortal is not { } committed
|| committed.RevealGeneration != revealGeneration
|| committed.TeleportSequence != teleportSequence)
{
return false;
}
_lastCommittedPortal = null;
return true;
}
/// <summary>
/// C3c-R1-style one-route-at-a-time latch (mirrors
/// <see cref="RuntimeFirstEntryDriveController.AttachRoute"/>): this
@ -308,10 +408,22 @@ public sealed class RuntimeAcceptedPositionDriveController
/// force observation dies with it: a reconnect re-merges its own
/// positions, and a stale observation must never survive to authorize a
/// re-issue against a later session's record.
/// <para>
/// Coordinator hygiene fix (round-3 closeout, 2026-08-05): also clears
/// <see cref="_lastCommittedPortal"/>. An unconsumed latch surviving a
/// session reset was harmless only because the transit's own
/// generation counter is monotonic across resets within one
/// <c>GameRuntime</c> lifetime, so a stale entry could never match a
/// later reveal's generation/sequence pair by construction — a
/// correctness argument resting on an invariant this method never
/// declared. Clearing it here lets the ledger converge to zero on
/// every reset instead of relying on that invariant to stay true.
/// </para>
/// </summary>
private void AbandonPending()
{
_newestForce = null;
_lastCommittedPortal = null;
if (_pending is not { } pending)
return;
_pending = null;
@ -401,6 +513,411 @@ public sealed class RuntimeAcceptedPositionDriveController
return SubmitAndResolve(record, token, route);
}
/// <summary>
/// C4 route 3: executes the local player's portal arrival against the
/// canonical Runtime SetPosition owner. Retail
/// <c>SmartBox::TeleportPlayer</c> @0x00453910 =
/// <c>CPhysicsObj::SetPositionSimple(player, dest, 1)</c> — the SAME
/// generic primitive route 2 already routes through
/// <see cref="TryExecuteAcceptedLocalPosition"/> — plus
/// <c>PlayerPositionUpdated</c>. <paramref name="destination"/> must be
/// the transit's OWN retained accepted destination
/// (<c>RuntimeWorldTransitState.TryGetAcceptedTeleportDestination</c>),
/// never re-derived from live per-tick timestamps: by the time the Place
/// edge fires, the packet merged seconds ago and nothing is "advancing"
/// anymore (docs/research/2026-08-04-c4-route-3-contract.md D-T2.2).
/// </summary>
internal RuntimeAcceptedPositionExecutionStatus TryExecuteAcceptedPortalArrival(
in RuntimeTeleportDestination destination,
in RuntimePortalPlacementAuthority portal)
{
if (!portal.IsValid
|| !_entityObjects.Entities.TryGetActive(
_localPlayerServerGuid(), out RuntimeEntityRecord record)
|| record.PhysicsBody is null
|| record.Key is not { } key
// Route 1 owns an active initial-Create residence exactly like
// route 2's equivalent guard above — the residence executor's
// own tail action already carries any position it needs.
|| _entityObjects.TryGetInitialCreateResidence(record, out _))
{
LogPortalArrivalAttempt(
RuntimeAcceptedPositionExecutionStatus.NotApplicable,
portal,
resolvedCell: 0u);
return RuntimeAcceptedPositionExecutionStatus.NotApplicable;
}
RuntimeAuthoritativePositionRoute route = ClassifyPortalArrival(
record, key, destination, _generation());
if (!route.Accepted)
{
LogPortalArrivalAttempt(
RuntimeAcceptedPositionExecutionStatus.Rejected,
portal,
record.FullCellId);
return RuntimeAcceptedPositionExecutionStatus.Rejected;
}
RuntimeSetPositionState setPosition = _entityObjects.Physics.SetPosition;
ulong acceptedVersion = record.PositionAuthorityVersion;
RuntimeEntityPlacementToken token =
setPosition.TryBeginExclusiveAuthoredPlacement(
record,
acceptedVersion,
route.OperationKind,
portal);
if (!token.IsValid)
{
// Either a concurrent placement authority already owns the
// entity, or Begin's own portal-vs-latest-cell gate refused
// (D-T5's Begin cell-mismatch edge — a second local Position
// merged between the offer and this Place edge). Neither is
// staleness; the caller's D-T5 refusal handling owns what
// happens next.
LogPortalArrivalAttempt(
RuntimeAcceptedPositionExecutionStatus.Contention,
portal,
record.FullCellId);
return RuntimeAcceptedPositionExecutionStatus.Contention;
}
return SubmitAndResolvePortal(record, token, route, portal);
}
/// <summary>
/// C4 route 3: the classifier's LocalPlayer-teleport route
/// (<see cref="RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition"/>,
/// <c>request.Authority.TeleportAdvanced</c> branch) built from the
/// retained destination rather than a live merge. Retail's
/// <c>PhysicsTimestampGate.IsNewer(PreviousTeleportSequence,
/// AcceptedTeleportSequence)</c> gate only needs to be TRUE — its exact
/// magnitude is not read anywhere past that boolean (the classifier's
/// resulting <see cref="RuntimeAuthoritativePositionRoute"/> for this
/// branch does not depend on the previous stamp's value, and the drive
/// controller's own `expectedPositionAuthorityVersion` — not this
/// authority's — gates Begin), so a synthetic strictly-older sequence
/// forces retail's exact branch without any second copy of the merge-time
/// timestamp pair having to survive from offer to Place.
/// </summary>
private static RuntimeAuthoritativePositionRoute ClassifyPortalArrival(
RuntimeEntityRecord record,
RuntimeEntityKey key,
in RuntimeTeleportDestination destination,
RuntimeGenerationToken generation)
{
ushort acceptedTeleport = destination.TeleportSequence;
ushort priorTeleport = unchecked((ushort)(acceptedTeleport - 1));
var authority = new RuntimeAuthoritativePositionAuthority(
generation,
key,
record.PositionAuthorityVersion,
destination.PositionSequence,
priorTeleport,
acceptedTeleport,
PositionTimestampDisposition.Apply);
bool hasAnimations = (record.Snapshot.MotionTableId
?? record.Snapshot.Physics?.MotionTableId) is { } motionTableId
&& motionTableId != 0u;
var wirePosition = new CreateObject.ServerPosition(
destination.CellId,
destination.Position.Frame.Origin.X,
destination.Position.Frame.Origin.Y,
destination.Position.Frame.Origin.Z,
destination.Position.Frame.Orientation.W,
destination.Position.Frame.Orientation.X,
destination.Position.Frame.Orientation.Y,
destination.Position.Frame.Orientation.Z);
var request = new RuntimeAcceptedPositionRouteRequest(
authority,
RuntimePositionEntityKind.LocalPlayer,
RuntimeAcceptedPositionSource.PositionEvent,
wirePosition,
PlacementFrame: null,
PositionPackVelocity: null,
CommittedCellId: record.FullCellId,
HasContact: false,
PlayerDistance: 0f,
UsePositionFromServer: false,
hasAnimations,
new RuntimePositionPlacementFacts(
record.FinalPhysicsState,
record.Snapshot.SetupTableId is not null));
return RuntimeAuthoritativePositionRouteClassifier
.ClassifyAcceptedPosition(request);
}
/// <summary>
/// C4 route 3 (trap T7): the portal SIBLING of <see cref="SubmitAndResolve"/>
/// — shares Begin/Submit/status handling, deliberately does NOT touch
/// <see cref="_newestForce"/> or route through <see cref="SettlePending"/>'s
/// force-shaped re-issue funnel. ACE sends one destination per teleport;
/// a portal placement that fails to commit is never re-applied.
/// </summary>
private RuntimeAcceptedPositionExecutionStatus SubmitAndResolvePortal(
RuntimeEntityRecord record,
in RuntimeEntityPlacementToken token,
in RuntimeAuthoritativePositionRoute route,
in RuntimePortalPlacementAuthority portal)
{
RuntimeSetPositionState setPosition = _entityObjects.Physics.SetPosition;
RuntimeSetPositionMoverPreparationStatus status =
setPosition.TryPrepareAndSubmitAuthoredPlacement(
record,
token,
route.OperationKind,
route.SetPositionFlags,
_collisionSource,
_clock.SimulationTimeSeconds,
out RuntimeSetPositionOutcome outcome,
portal: portal,
resolveWorldOffsetFromRuntimeFrame: true);
if (status != RuntimeSetPositionMoverPreparationStatus.Prepared)
{
if (status.IsRetryable())
{
RetainPending(setPosition, new Pending
{
Record = record,
Token = token,
Route = route,
AwaitingCommitWake = false,
PositionEventOwed = false,
Portal = portal,
});
LogPortalArrivalAttempt(
RuntimeAcceptedPositionExecutionStatus.Contention,
portal,
record.FullCellId);
return RuntimeAcceptedPositionExecutionStatus.Contention;
}
CancelToken(setPosition, token);
LogPortalArrivalAttempt(
RuntimeAcceptedPositionExecutionStatus.Rejected,
portal,
record.FullCellId);
return RuntimeAcceptedPositionExecutionStatus.Rejected;
}
switch (outcome.Status)
{
case RuntimeSetPositionStatus.CommittedHostAcknowledgementPending:
ReconcileAndAcknowledgePortal(record, route, portal);
return RuntimeAcceptedPositionExecutionStatus.Committed;
case RuntimeSetPositionStatus.DeferredCell:
// D-T2.4: a park should be rare (the destination was already
// centered by the host before submit), but must never leak —
// same drain-stale-Withdraw-then-watch shape as the force arm.
while (setPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot parked)
&& parked.Token.Entity == token.Entity
&& parked.Kind is RuntimePlacementProjectionKind.Withdraw)
{
if (!setPosition.AcknowledgeProjection(parked.Token))
break;
}
if (!setPosition.WatchPlacementCompletion(token))
{
CancelToken(setPosition, token);
LogPortalArrivalAttempt(
RuntimeAcceptedPositionExecutionStatus.Rejected,
portal,
record.FullCellId);
return RuntimeAcceptedPositionExecutionStatus.Rejected;
}
RetainPending(setPosition, new Pending
{
Record = record,
Token = token,
Route = route,
AwaitingCommitWake = true,
PositionEventOwed = false,
Portal = portal,
});
LogPortalArrivalAttempt(
RuntimeAcceptedPositionExecutionStatus.DeferredCell,
portal,
record.FullCellId);
return RuntimeAcceptedPositionExecutionStatus.DeferredCell;
default:
// Rejected/Cancelled — authority moved out from under this
// operation; the body never moved. Unlike the force arm,
// retail's teleport branch has no unconditional ack to send,
// so there is nothing left to do here.
CancelToken(setPosition, token);
LogPortalArrivalAttempt(
RuntimeAcceptedPositionExecutionStatus.Rejected,
portal,
record.FullCellId);
return RuntimeAcceptedPositionExecutionStatus.Rejected;
}
}
/// <summary>
/// R8 review fix (2026-08-05): D-T8 specified "one line per
/// portal-arrival ATTEMPT", but the first pass logged only from
/// <see cref="ReconcileAndAcknowledgePortal"/> — reached solely on
/// <c>Committed</c> — so every refusal was invisible under the gate's
/// own pinned <c>ACDREAM_PROBE_LOCAL_TELEPORT</c> env var (the graphical
/// refusal path logged under the DIFFERENT <c>ACDREAM_PROBE_TELEPORT</c>,
/// and headless logged nothing at all). Every non-terminal/refusal exit
/// from <see cref="TryExecuteAcceptedPortalArrival"/> and
/// <see cref="SubmitAndResolvePortal"/> now emits through this one
/// helper; the richer hookTail/leash/autorun facts remain
/// <see cref="ReconcileAndAcknowledgePortal"/>'s own line on the
/// <c>Committed</c> path, since those three booleans are meaningless
/// before a commit.
/// </summary>
private static void LogPortalArrivalAttempt(
RuntimeAcceptedPositionExecutionStatus status,
in RuntimePortalPlacementAuthority portal,
uint resolvedCell)
{
PhysicsDiagnostics.LogLocalTeleportArrival(
cause: "portal",
placementStatus: status.ToString(),
portalGeneration: portal.RevealGeneration,
teleportSequence: portal.TeleportSequence,
destinationCell: portal.Projection.DestinationCell,
resolvedCell: resolvedCell,
hookTailRan: false,
leashArmed: false,
autorunCancelled: false);
}
/// <summary>
/// A2/D-T2.4 review fix (2026-08-05): treats a portal authority as
/// current when no re-validation predicate was wired (today's
/// unconditional behaviour, preserved for fixtures that never park), and
/// otherwise defers to <see cref="_isPortalAuthorityCurrent"/> — which
/// production wires to the SAME
/// <c>RuntimeWorldTransitState.CanPlacePortalDestination</c> query the
/// App/headless Place edge itself uses.
/// </summary>
private bool IsPortalAuthorityCurrent(
in RuntimePortalPlacementAuthority portal) =>
_isPortalAuthorityCurrent is null || _isPortalAuthorityCurrent(portal);
/// <summary>
/// C4 route 3: the committed-portal-placement controller-local
/// reconciliation and outbound tail. Runs
/// <see cref="PlayerMovementController.CommitCanonicalTeleportFrame"/>
/// (the re-homed <c>SetPositionCore</c> duties, D-T3), then the
/// <c>PlayerTeleported</c> port (<c>CommandInterpreter::PlayerTeleported</c>
/// @0x006B32B0 = <c>SetAutoRun(0,1)</c> + <c>SendMovementEvent</c>) — two
/// named behaviour changes versus the deleted App/Headless placement
/// authorities: autorun now cancels on portal arrival, and exactly one
/// movement-event refresh goes out (never an <c>AutonomousPosition</c> —
/// the route's <c>SendPositionImmediately</c> is always false).
///
/// <para>
/// A4 review fix (2026-08-05): <paramref name="route"/>'s
/// <c>ZeroVelocity</c>/<c>ConstrainPhase</c>/<c>TeleportHookPhase</c> are
/// now READ, not assumed — <see cref="RuntimeAuthoritativePositionRoute.RunsTeleportHook"/>
/// gates whether the hook tail runs at all, and its
/// <c>ZeroVelocity</c>/<see cref="RuntimeAuthoritativePositionRoute.ConstrainAfterRouting"/>
/// drive <c>CommitCanonicalTeleportFrame</c>'s two conditional duties.
/// The LocalPlayer-teleport branch's values are unchanged today
/// (<c>AfterPositionOperation</c>/<c>AfterPositionOperation</c>/<c>true</c>),
/// so this is purely a wiring correction: a future classifier edit now
/// changes this method's behaviour instead of silently disagreeing with
/// it, and the contract's own §8 item 11 sabotage (force the classifier
/// onto <c>ConstrainPhase.None</c> — the leash must not re-arm) can
/// finally fail as designed.
/// </para>
/// </summary>
private void ReconcileAndAcknowledgePortal(
RuntimeEntityRecord record,
in RuntimeAuthoritativePositionRoute route,
in RuntimePortalPlacementAuthority portal)
{
// B1 review fix (2026-08-05): this method is called ONLY from the
// two sites that just observed Runtime's canonical
// CommittedHostAcknowledgementPending outcome for THIS portal
// authority (SubmitAndResolvePortal's first-attempt commit and
// Advance's re-validated deferred wake) — so the commit fact is
// true here regardless of whether the two guards below decline the
// REST of this method's App-level suffix work. Latching it FIRST,
// unconditionally, is what lets TryConsumePortalCommit replace the
// unsound PendingCount==0 inference both host gates used to make.
_lastCommittedPortal = (portal.RevealGeneration, portal.TeleportSequence);
if (record.ServerGuid != _localPlayerServerGuid())
return;
if (_localController() is not { } controller)
return;
// N4 review fix (2026-08-05): the frame/cell/stop/input-reset/clock
// commit runs UNCONDITIONALLY (retail's SetPositionInternal
// @0x00515330 has no hook-phase gate); only the UnStick/UnConstrain/
// re-arm tail inside it is conditioned on the hook phase, via
// runTeleportHookTail. Previously this whole call was skipped when
// RunsTeleportHook was false, which would have silently dropped the
// render-root UpdateCellId publish too (the doorway-FLAP class) the
// day a route ever sets TeleportHookPhase.None — today's portal
// route always sets a non-None phase, so this is a structural fix
// with no live behavior change yet.
bool hookTailRan = route.RunsTeleportHook;
controller.CommitCanonicalTeleportFrame(
zeroVelocity: route.ZeroVelocity,
rearmConstraintLeash: route.ConstrainAfterRouting,
runTeleportHookTail: hookTailRan);
bool autorunCancelled = _localMovementState()?.CancelAutoRun() ?? false;
// R7 review fix (2026-08-05): retail CommandInterpreter::SendMovementEvent
// @0x006B4680 (PlayerTeleported's tail-jump) gates on TWO facts — a
// non-null raw motion state (TryGetOutboundPosition/TryGetOutboundMotion
// already cover that) AND `autonomy_level != 0`. This call was
// unconditional. This is route 3's OWN call site only —
// LocalPlayerOutboundController.TrySendMovement is shared with
// route 2's DIFFERENT retail function
// (CommandInterpreter::SendPositionEvent) and is not touched.
//
// Known approximation, filed AP-144 (2026-08-05, R7 round-3 review —
// CLAUDE.md's register rule is binding, not an implementer's call):
// this class only has RuntimeCharacterState.UsePositionFromServer in
// scope (`AutonomyLevel != FullAutonomyLevel(2)`, retail's
// `autonomy_level != 2`), not the raw AutonomyLevel — so
// `!UsePositionFromServer` sends only when AutonomyLevel==2,
// whereas retail's actual gate (`autonomy_level != 0`) ALSO sends
// at AutonomyLevel==1. The two agree everywhere except that one mid
// level, currently unreachable because TrySetAutonomyLevel has zero
// production callers. Retire by threading the raw AutonomyLevel
// through this constructor (and both host compositions) and gating
// on `!= 0` directly instead of reusing UsePositionFromServer.
if (!_usePositionFromServer())
{
_localPlayerOutbound.TrySendMovement(
_session(),
controller,
controller.CapturePresentationResult());
}
// D-T8 probe (temporary): confirms the reconcile suffix actually
// ran its three named duties on THIS commit, not just that the
// commit was reached. R3 review fix: the leash observable is
// ConstraintManager.IsConstrained ("has a leash"), not
// IsFullyConstrained ("has strained past 90% of it") — the latter
// reads false immediately after ConstrainTo re-anchors at distance
// 0, so every committed arrival printed leash=unarmed as coded.
PhysicsDiagnostics.LogLocalTeleportArrival(
cause: "portal",
placementStatus: "Committed",
portalGeneration: portal.RevealGeneration,
teleportSequence: portal.TeleportSequence,
destinationCell: portal.Projection.DestinationCell,
resolvedCell: record.FullCellId,
hookTailRan: hookTailRan,
leashArmed: controller.PositionManager?.Constraint?.IsConstrained
?? false,
autorunCancelled: autorunCancelled);
}
/// <summary>
/// Host cadence pump: resolves a parked DeferredCell operation once its
/// destination landblock's collision generation eventually commits it
@ -461,6 +978,42 @@ public sealed class RuntimeAcceptedPositionDriveController
// Raced against a concurrent consumer; retry next pump.
return;
}
// C4 route 3 (trap T7): a portal pending never enters the
// force funnel — SettlePending's _newestForce re-issue
// decision belongs to the force arm only. The deferred
// commit's own reconciliation is the portal wake's entire
// terminal action.
if (pending.Portal.Present)
{
_pending = null;
// A2/D-T2.4 re-validation: RetryDeferred already moved
// the body (asynchronously, outside this class's
// control — nothing here can prevent that). What this
// CAN prevent is running the reconcile/ack suffix
// against a reveal that ended or was superseded while
// the park sat outstanding, which would otherwise
// publish a Place receipt naming a dead portal
// authority (architecture review A2's FIFO-wedge
// shape).
if (!IsPortalAuthorityCurrent(pending.Portal))
{
PhysicsDiagnostics.LogLocalTeleportArrival(
cause: "portal",
placementStatus: "AbandonedAtWake",
portalGeneration: pending.Portal.RevealGeneration,
teleportSequence: pending.Portal.TeleportSequence,
destinationCell:
pending.Portal.Projection.DestinationCell,
resolvedCell: pending.Record.FullCellId,
hookTailRan: false,
leashArmed: false,
autorunCancelled: false);
return;
}
ReconcileAndAcknowledgePortal(
pending.Record, pending.Route, pending.Portal);
return;
}
// Retail order: the deferred commit's own reconciliation and
// ack come first, THEN the funnel decides whether a newer
// accepted force is still owed a placement (B1).
@ -480,9 +1033,39 @@ public sealed class RuntimeAcceptedPositionDriveController
}
// The watch died — most likely a subsequent accepted Position's
// merge-time Forget. The funnel owns what happens next, including
// this packet's still-unsent position event (retail acks whether
// or not the placement took — see SettlePending).
// merge-time Forget. A portal pending owes no re-issue and no
// ack (SendPositionImmediately is always false for the portal
// route), so it simply clears — matching the D-T5 refusal shape
// (nothing mutates; the transit's own cancellation/supersession
// machinery is the authority on what happens next).
//
// B1/N1 review fix (2026-08-05): this is the drive's own
// documented MODAL park outcome (ACE's 5-10 Hz broadcast Forgets
// any park surviving one interval — "the exact far-destination
// case the park exists to serve"), not a corner case. It does
// NOT set _lastCommittedPortal — nothing committed — so the host
// gate's TryConsumePortalCommit correctly reports "not yet" and
// either re-attempts fresh or converges through the transit's
// own cancellation, instead of the old PendingCount==0
// inference latching a false "committed".
if (pending.Portal.Present)
{
_pending = null;
PhysicsDiagnostics.LogLocalTeleportArrival(
cause: "portal",
placementStatus: "WatchDied",
portalGeneration: pending.Portal.RevealGeneration,
teleportSequence: pending.Portal.TeleportSequence,
destinationCell: pending.Portal.Projection.DestinationCell,
resolvedCell: pending.Record.FullCellId,
hookTailRan: false,
leashArmed: false,
autorunCancelled: false);
return;
}
// The funnel owns what happens next, including this packet's
// still-unsent position event (retail acks whether or not the
// placement took — see SettlePending).
SettlePending(
pending.Record,
pending.Token,
@ -493,14 +1076,54 @@ public sealed class RuntimeAcceptedPositionDriveController
if (setPosition.IsPlacementCurrent(pending.Token))
{
_ = SubmitAndResolve(pending.Record, pending.Token, pending.Route);
if (pending.Portal.Present
&& !IsPortalAuthorityCurrent(pending.Portal))
{
// A2/D-T2.4: unlike the AwaitingCommitWake branch above,
// this retry has NOT submitted yet — re-validating here
// genuinely prevents a stale commit rather than only
// suppressing its suffix.
_pending = null;
CancelToken(setPosition, pending.Token);
PhysicsDiagnostics.LogLocalTeleportArrival(
cause: "portal",
placementStatus: "AbandonedAtWake",
portalGeneration: pending.Portal.RevealGeneration,
teleportSequence: pending.Portal.TeleportSequence,
destinationCell: pending.Portal.Projection.DestinationCell,
resolvedCell: pending.Record.FullCellId,
hookTailRan: false,
leashArmed: false,
autorunCancelled: false);
return;
}
_ = pending.Portal.Present
? SubmitAndResolvePortal(
pending.Record, pending.Token, pending.Route, pending.Portal)
: SubmitAndResolve(pending.Record, pending.Token, pending.Route);
return;
}
// The prepare-retry operation died the same way. (A re-issue retry
// marker also lands here, carrying PositionEventOwed: false — its
// packet's placement was never begun, so its ack belongs to the
// eventual re-issue's terminal outcome.)
// The prepare-retry operation died the same way. B1/N1: no commit,
// no _lastCommittedPortal write — see the watch-died branch above.
if (pending.Portal.Present)
{
_pending = null;
PhysicsDiagnostics.LogLocalTeleportArrival(
cause: "portal",
placementStatus: "PrepareRetryLost",
portalGeneration: pending.Portal.RevealGeneration,
teleportSequence: pending.Portal.TeleportSequence,
destinationCell: pending.Portal.Projection.DestinationCell,
resolvedCell: pending.Record.FullCellId,
hookTailRan: false,
leashArmed: false,
autorunCancelled: false);
return;
}
// (A re-issue retry marker also lands here, carrying
// PositionEventOwed: false — its packet's placement was never begun,
// so its ack belongs to the eventual re-issue's terminal outcome.)
SettlePending(
pending.Record,
pending.Token,