acdream/tests/AcDream.App.Tests/Streaming/LocalPlayerTeleportControllerTests.cs
Erik e0f96a55bf 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>
2026-08-05 03:57:37 +02:00

1509 lines
62 KiB
C#

using System.Numerics;
using AcDream.App.Input;
using AcDream.App.Rendering;
using AcDream.App.Streaming;
using AcDream.App.Update;
using AcDream.App.World;
using AcDream.Content;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Physics;
using AcDream.Runtime.Session;
using AcDream.Runtime.World;
using DatReaderWriter.Enums;
namespace AcDream.App.Tests.Streaming;
public sealed class LocalPlayerTeleportControllerTests
{
[Fact]
public void DeferredNetworkOwnedBindingReleasesExactlyAndAllowsRebind()
{
var deferred = new DeferredLocalPlayerTeleportNetworkSink();
var first = new FakeNetworkSink();
var second = new FakeNetworkSink();
IDisposable firstBinding = deferred.BindOwned(first);
Assert.Throws<InvalidOperationException>(() => deferred.BindOwned(second));
firstBinding.Dispose();
using IDisposable secondBinding = deferred.BindOwned(second);
firstBinding.Dispose();
deferred.OnTeleportStarted(9);
Assert.Empty(first.Starts);
Assert.Equal([9u], second.Starts);
}
[Fact]
public void DestinationBeforeStart_IsReplayedOnceAfterPresentationActivates()
{
var harness = new Harness();
RuntimeTeleportDestination destination = Position(
cellId: 0x20210001u,
teleportSequence: 7,
x: 12f,
y: 34f,
z: 5f);
harness.OfferDestination(
destination,
teleportTimestampAdvanced: true);
Assert.Empty(harness.Streaming.Reservations);
harness.Controller.OnTeleportStarted(7);
Assert.Equal(1, harness.Input.EndCount);
Assert.Equal(1, harness.Mode.EnterPortalCount);
Assert.Equal(Matrix4x4.Identity, harness.Presentation.BeginProjection);
Assert.Equal(
(1L, 0x20210001u, WorldRevealReadinessBarrier.OutdoorNeighborhoodRadius),
Assert.Single(harness.Streaming.Reservations));
Assert.True(harness.Reveal.Snapshot.IsActive);
Assert.Equal(RuntimePortalKind.Portal, harness.Reveal.Snapshot.Kind);
harness.OfferDestination(
destination,
teleportTimestampAdvanced: false);
Assert.Single(harness.Streaming.Reservations);
}
[Fact]
public void MissingPlayerController_KeepsStartAndDestinationPendingUntilProjectionExists()
{
var harness = new Harness();
harness.Mode.Controller = null;
harness.Controller.OnTeleportStarted(2);
harness.OfferDestination(
Position(0x20210001u, 2, 7f, 8f, 9f),
teleportTimestampAdvanced: true);
Assert.False(harness.Controller.IsActive);
Assert.Equal(0u, harness.Controller.ActiveDestinationCell);
Assert.Null(harness.Presentation.BeginProjection);
harness.Mode.Controller = new PlayerMovementController(new PhysicsEngine());
harness.Mode.Controller.SetPosition(
Vector3.Zero,
0x20210001u,
Vector3.Zero);
harness.Controller.Tick(0.016f);
Assert.True(harness.Controller.IsActive);
Assert.Equal(0x20210001u, harness.Controller.ActiveDestinationCell);
Assert.NotNull(harness.Presentation.BeginProjection);
Assert.Single(harness.Streaming.Reservations);
}
[Fact]
public void ControllerWithdrawnAfterActivation_KeepsAcceptedDestinationUntilItReturns()
{
var harness = new Harness();
harness.Controller.OnTeleportStarted(14);
Assert.True(harness.Controller.IsActive);
harness.Mode.Controller = null;
harness.OfferDestination(
Position(0x20210001u, 14, 7f, 8f, 9f),
teleportTimestampAdvanced: true);
Assert.Equal(0u, harness.Controller.ActiveDestinationCell);
Assert.Empty(harness.Streaming.Reservations);
harness.Mode.Controller = new PlayerMovementController(new PhysicsEngine());
harness.Mode.Controller.SetPosition(Vector3.Zero, 0x20210001u, Vector3.Zero);
harness.Controller.Tick(0.016f);
Assert.Equal(0x20210001u, harness.Controller.ActiveDestinationCell);
Assert.Single(harness.Streaming.Reservations);
}
// C4 route 3 root-caused (closed): the earlier "bare-fixture InvalidData"
// diagnosis was WRONG - these never reached
// RuntimeSetPositionMoverPreparer.TryBuild at all. The real defect was in
// LocalPlayerTeleportController.TryExecuteCanonicalPortalPlacement: it
// re-read _transit.TryGetAcceptedTeleportDestination at Place time, but
// RuntimeWorldTransitState.TryBeginPortalReveal (driven by AimDestination
// via WorldRevealCoordinator.TryBeginPortal) atomically CONSUMES that one
// accepted-destination slot the instant Aim claims the reveal generation
// - so the Place-time re-read always found it empty and refused with
// "cause=host-token-unavailable" before ever building a mover. Fixed by
// caching the destination at Aim time (_pendingDestination), mirroring
// the existing _pendingCell/_pendingRotation/_pendingRevealGeneration
// pattern in the same method. Production fix, not a fixture workaround.
[Fact]
public void ControllerWithdrawnAfterAim_HoldsPresentationUntilModeRebuildsIt()
{
var harness = new Harness();
harness.Controller.OnTeleportStarted(15);
harness.OfferDestination(
Position(0x20210001u, 15, 7f, 8f, 9f),
teleportTimestampAdvanced: true);
harness.Mode.Controller = null;
harness.Presentation.Enqueue(TeleportAnimEvent.Place);
harness.Controller.Tick(0.016f);
Assert.False(harness.Placement.Called);
Assert.Empty(harness.Presentation.WorldReadyValues);
harness.Mode.RebuildOnEnter = () =>
{
var controller = new PlayerMovementController(new PhysicsEngine());
controller.SetPosition(Vector3.Zero, 0x20210001u, Vector3.Zero);
return controller;
};
harness.Controller.Tick(0.016f);
Assert.NotNull(harness.Mode.Controller);
Assert.True(harness.Placement.Called);
// A6 review fix: assert the REAL canonical body resolved the
// offered destination (7,8,9), not just that the acknowledge-only
// suffix ran.
Assert.Equal(
new Vector3(7f, 8f, 9f),
harness.Movement.Controller!.Position);
Assert.Equal(0x20210001u, harness.Movement.Controller.CellId);
}
[Fact]
public void SameLandblockDestination_DoesNotRecenterAndKeepsTranslatedPosition()
{
var harness = new Harness(centerX: 0x20, centerY: 0x21);
// 0x20210123 is an INDOOR cell (low word 0x0123 >= 0x0100) within the
// same landblock the constructor already committed. It still needs
// its own synthetic CellStruct - PhysicsEngine.IsSpawnCellReady
// requires DataCache.GetCellStruct to resolve for indoor cells, and
// a bare AddLandblock never populates one.
harness.AddSyntheticIndoorCell(0x20210123u);
harness.Controller.OnTeleportStarted(3);
harness.OfferDestination(
Position(0x20210123u, 3, 20f, 30f, 4f),
teleportTimestampAdvanced: true);
harness.Presentation.EmitPlaceWhenReady = true;
harness.Controller.Tick(0.016f);
Assert.Empty(harness.Streaming.Recenters);
// C4 route 3: the canonical Runtime arm - not this fake - resolves
// and commits the destination; the acknowledge-only suffix carries
// no position/cell to capture (D-T4). The observable fact left at
// this layer is that the committed receipt reached the suffix.
Assert.True(harness.Placement.Called);
// A6/R4 review fix: assert the REAL canonical body resolved the
// offered destination, not just that the suffix ran.
Assert.Equal(
new Vector3(20f, 30f, 4f),
harness.Movement.Controller!.Position);
Assert.Equal(0x20210123u, harness.Movement.Controller.CellId);
}
/// <summary>
/// A1/R1 review fix (2026-08-05): the retail/architecture review's own
/// required test — "refused arm -&gt; the anim stream does not reach
/// RevealWorldViewport/FireLoginComplete with an unplaced body." Forces
/// a genuine Runtime-level Contention refusal (a competing operation
/// already owns the entity's placement token, same shape as the Runtime
/// layer's <c>PortalContention_WhenTheEntityAlreadyOwnsAnActiveOperation</c>)
/// and drives several ticks past where the OLD unconditional-march code
/// would have already fired LoginComplete. The player must still be
/// standing at the origin, in portal space, with the transit still
/// active — never released into the world unplaced.
/// </summary>
[Fact]
public void RefusedPlace_HoldsTheStreamAndConvergesOnlyAfterContentionClears()
{
var harness = new Harness();
harness.Controller.OnTeleportStarted(50);
harness.OfferDestination(
Position(0x20210001u, 50, 11f, 12f, 13f),
teleportTimestampAdvanced: true);
harness.Presentation.EmitPlaceWhenReady = true;
Vector3 positionBefore = harness.Movement.Controller!.Position;
// A competing operation already owns the entity's placement token -
// the canonical Runtime Begin call inside TryAdvancePortalCommit
// refuses with Contention, exactly like the Runtime-layer
// PortalContention_WhenTheEntityAlreadyOwnsAnActiveOperation test.
RuntimeEntityRecord record = harness.Lifetime.Entities
.TryGetActive(0x50000001u, out RuntimeEntityRecord active)
? active
: throw new InvalidOperationException("fixture entity missing");
RuntimeEntityPlacementToken displaced = harness.Lifetime.Physics
.SetPosition.TryBeginExclusiveAuthoredPlacement(
record,
record.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.LocalAuthoritative);
Assert.True(displaced.IsValid);
// Several ticks, well past where the pre-fix code would already
// have reached PlayExitSound -> WorldFadeIn -> FireLoginComplete
// and reset the transit (the tunnel's own retail timing is 2-5s;
// this drives 3x that in 0.1s steps).
for (int i = 0; i < 100; i++)
harness.Controller.Tick(0.1f);
Assert.False(harness.Placement.Called);
Assert.Equal(positionBefore, harness.Movement.Controller.Position);
Assert.True(harness.Controller.IsActive);
Assert.False(harness.Reveal.Snapshot.Completed);
Assert.Equal(0, harness.Session.LoginCompleteCount);
// Release the competing operation - the SAME `dataReady` predicate
// that already held the stream in Tunnel now lets the commit
// succeed on the very next tick (D-T5 row 2's "next Tick
// re-attempts the Place edge", made real without ever touching
// TeleportAnimSequencer).
RuntimePlacementCancellationReceipt cancellation = harness.Lifetime
.Physics.SetPosition.ForgetExactPlacement(displaced);
if (cancellation.IsValid)
harness.Lifetime.Physics.SetPosition.PublishCancellation(cancellation);
harness.Controller.Tick(0.1f);
Assert.True(harness.Placement.Called);
Assert.Equal(
new Vector3(11f, 12f, 13f),
harness.Movement.Controller.Position);
}
/// <summary>
/// B1 review fix (2026-08-05): the required test #1 — a DeferredCell
/// park killed by an ORDINARY merge-time <c>Forget</c> (not a
/// collision-generation commit) must NOT be latched as "committed".
/// The destination landblock is never committed
/// (<c>CommitLandblockCollision</c> is deliberately not called), so the
/// portal parks; an unrelated ordinary accepted Position for the same
/// entity then Forgets the outstanding operation exactly like a normal
/// 5-10 Hz ACE broadcast would — <c>RuntimeSetPositionState</c>'s own
/// doc names this the EXPECTED way a far-destination park resolves
/// without committing. Before the fix, <c>PendingCount</c> hitting 0
/// made the host gate latch <c>_placementCommitted = true</c>, release
/// the sequencer, and run the ENTIRE completion sequence
/// (materialize/reveal/LoginComplete) against the unmoved body.
/// </summary>
[Fact]
public void ParkedPlace_ForgottenByOrdinaryMergeDoesNotLatchAsCommitted()
{
var harness = new Harness();
const uint destinationLandblock = 0x40410000u;
harness.Controller.OnTeleportStarted(60);
harness.OfferDestination(
Position(destinationLandblock | 0x0001u, 60, 21f, 22f, 23f),
teleportTimestampAdvanced: true);
harness.Presentation.EmitPlaceWhenReady = true;
// First tick: destinationLandblock's collision generation was never
// committed, so the canonical arm parks DeferredCell.
harness.Controller.Tick(0.016f);
Assert.False(harness.Placement.Called);
Assert.Equal(1, harness.AcceptedPositionDrive.PendingCount);
// An ordinary, UNRELATED accepted Position for the same entity -
// no new teleport, just a normal broadcast - Forgets the parked
// operation the same way ACE's 5-10 Hz cadence would.
harness.MergeOrdinaryPosition(
new Vector3(48f, 49f, 50f), 0x20210001u, teleportSequence: 60);
// Forget (inside TryApplyPosition) cancels the underlying
// RuntimeSetPositionState operation immediately, but the drive's
// OWN _pending cache only notices on its next Advance() pump - the
// App host's real per-frame retry lease does this; the test drives
// it explicitly.
harness.AcceptedPositionDrive.Advance();
Assert.Equal(0, harness.AcceptedPositionDrive.PendingCount);
// Drive well past where the pre-fix inference would have latched
// "committed" on the very next tick and then marched to
// LoginComplete.
for (int i = 0; i < 100; i++)
harness.Controller.Tick(0.1f);
// NOTE: the body's Position/CellId are NOT asserted unchanged here.
// The DeferredCell park's dormant stage (RuntimeSetPositionState's
// SubmitPreparedPlacementCore deferred-commit path,
// StageDormantCellFrame) already writes the staged destination onto
// the body's controller while body.InWorld stays false and the
// canonical placement has not committed - the reader-visible
// Position/CellId moving early is that benign staging mechanism, not
// evidence the placement committed (see the analogous headless test
// HeadlessPortalPrepareDestinationParksThenCommitsOnCollisionGenerationWake
// for the same finding). The discriminators below - the presentation
// suffix never running, no materialize/reveal/LoginComplete, and the
// controller staying active in an incomplete transit - are what
// actually distinguish "committed" from "parked-then-forgotten".
Assert.False(harness.Placement.Called);
Assert.Equal(0, harness.Reveal.PortalMaterializationCount);
Assert.Equal(0, harness.Session.LoginCompleteCount);
Assert.True(harness.Controller.IsActive);
Assert.False(harness.Reveal.Snapshot.Completed);
}
[Fact]
public void CrossLandblockDestination_RecentersBeforeItCanBecomeReady()
{
var harness = new Harness(centerX: 0x20, centerY: 0x21);
harness.CommitLandblockCollision(0x30310000u);
// 0x30310100 is an INDOOR cell (low word 0x0100 >= 0x0100); see the
// AddSyntheticIndoorCell doc comment for why CommitLandblockCollision
// alone (terrain-only) cannot make it spawn-ready.
harness.AddSyntheticIndoorCell(0x30310100u);
harness.Mode.Controller!.SetPosition(
Vector3.Zero,
0x20210001u,
Vector3.Zero);
harness.Controller.OnTeleportStarted(4);
harness.Streaming.RecenterPending = true;
harness.OfferDestination(
Position(0x30310100u, 4, -2f, 8f, 9f),
teleportTimestampAdvanced: true);
harness.Presentation.EmitPlaceWhenReady = true;
harness.Controller.Tick(1f);
Assert.Equal((0x30, 0x31, true), Assert.Single(harness.Streaming.Recenters));
Assert.False(Assert.Single(harness.Presentation.WorldReadyValues));
Assert.False(harness.Placement.Called);
harness.Streaming.RecenterPending = false;
harness.Controller.Tick(0.016f);
Assert.True(harness.Placement.Called);
// A6/R4 review fix: assert the REAL canonical body resolved the
// cross-landblock destination, not just that the suffix ran.
Assert.Equal(
new Vector3(-2f, 8f, 9f),
harness.Movement.Controller!.Position);
Assert.Equal(0x30310100u, harness.Movement.Controller.CellId);
}
[Fact]
public void ReadinessHold_NeverRevealsIncompleteWorldAndShowsWaitCueAfterFiveSeconds()
{
var harness = new Harness(worldReady: false);
harness.Controller.OnTeleportStarted(5);
harness.OfferDestination(
Position(0x20210001u, 5, 1f, 2f, 3f),
teleportTimestampAdvanced: true);
harness.Presentation.EmitPlaceWhenReady = true;
harness.Controller.Tick(4.9f);
Assert.False(Assert.Single(harness.Presentation.WorldReadyValues));
Assert.False(harness.Presentation.WaitCueValues[^1]);
Assert.False(harness.Placement.Called);
harness.Controller.Tick(0.1f);
harness.Controller.Tick(30f);
Assert.All(harness.Presentation.WorldReadyValues, Assert.False);
Assert.True(harness.Presentation.WaitCueValues[^1]);
Assert.False(harness.Placement.Called);
Assert.True(harness.Reveal.WaitCueShown);
}
[Fact]
public void Place_ReconcilesInsidePlacementBeforeRevealMaterialized()
{
var order = new List<string>();
var harness = new Harness(order: order);
harness.Controller.OnTeleportStarted(8);
harness.OfferDestination(
Position(0x20210001u, 8, 4f, 5f, 6f),
teleportTimestampAdvanced: true);
order.Clear();
harness.Presentation.Enqueue(TeleportAnimEvent.Place);
harness.Controller.Tick(0.016f);
Assert.True(Index(order, "placement") < Index(order, "[world-reveal] event=materialized"));
Assert.Equal(1, harness.Reveal.PortalMaterializationCount);
}
[Fact]
public void Place_RechecksExactRuntimeLifetimeBeforeMutatingPresentation()
{
var harness = new Harness();
harness.Controller.OnTeleportStarted(81);
harness.OfferDestination(
Position(0x20210001u, 81, 4f, 5f, 6f),
teleportTimestampAdvanced: true);
long generation = harness.Reveal.Snapshot.Generation;
Assert.True(harness.Transit.Cancel(generation));
harness.Presentation.Enqueue(TeleportAnimEvent.Place);
harness.Controller.Tick(0.016f);
Assert.False(harness.Placement.Called);
Assert.Equal(0, harness.Reveal.PortalMaterializationCount);
}
[Fact]
public void LoginComplete_EntersWorldThenSendsThenCompletesAndResets()
{
var order = new List<string>();
var harness = new Harness(order: order);
harness.Controller.OnTeleportStarted(9);
harness.OfferDestination(
Position(0x20210001u, 9, 4f, 5f, 6f),
teleportTimestampAdvanced: true);
harness.Presentation.Enqueue(TeleportAnimEvent.Place);
harness.Controller.Tick(0.016f);
order.Clear();
harness.Presentation.Enqueue(TeleportAnimEvent.FireLoginComplete);
harness.Controller.Tick(0.016f);
Assert.True(Index(order, "enter-world") < Index(order, "login-complete"));
Assert.True(Index(order, "login-complete") < Index(order, "[world-reveal] event=complete"));
Assert.True(Index(order, "[world-reveal] event=complete") < Index(order, "presentation-reset"));
Assert.False(harness.Controller.IsActive);
Assert.Equal(0u, harness.Controller.ActiveDestinationCell);
Assert.Equal(1, harness.Session.LoginCompleteCount);
Assert.True(harness.Reveal.Snapshot.Completed);
Assert.False(harness.Reveal.Snapshot.Cancelled);
}
[Fact]
public void ExitSound_ReleasesDestinationBeforeHidingTunnelButKeepsProtocolActive()
{
var order = new List<string>();
var harness = new Harness(order: order);
harness.Controller.OnTeleportStarted(91);
harness.OfferDestination(
Position(0x20210001u, 91, 4f, 5f, 6f),
teleportTimestampAdvanced: true);
harness.Streaming.ReservationEnds.Clear();
harness.Presentation.Enqueue(TeleportAnimEvent.PlayExitSound);
harness.Controller.Tick(0.016f);
Assert.False(harness.Presentation.IsPortalViewportVisible);
Assert.True(harness.Controller.IsActive);
Assert.False(harness.Reveal.Snapshot.Completed);
Assert.Single(harness.Streaming.ReservationEnds);
}
[Fact]
public void NewerStart_ReplacesOldDestinationWithoutReusingIt()
{
var harness = new Harness();
harness.Controller.OnTeleportStarted(10);
harness.OfferDestination(
Position(0x20210001u, 10, 1f, 1f, 1f),
teleportTimestampAdvanced: true);
harness.Controller.OnTeleportStarted(11);
harness.Controller.Tick(30f);
Assert.False(harness.Placement.Called);
Assert.Equal(0u, harness.Controller.ActiveDestinationCell);
harness.OfferDestination(
Position(0x20210001u, 11, 2f, 2f, 2f),
teleportTimestampAdvanced: true);
harness.Presentation.Enqueue(TeleportAnimEvent.Place);
harness.Controller.Tick(0.016f);
Assert.True(harness.Placement.Called);
// A6 review fix: this is the specific test A6 named — the removed
// assertion was the ONLY thing proving the SECOND destination
// (2,2,2) placed and not the superseded first one (1,1,1).
// Assert.True(Placement.Called) alone cannot distinguish them. X/Y
// are exact; Z settles to the Harness's uniform terrain height (5)
// because both candidate wire Z values (1 and 2) sit BELOW the
// floor and the placement resolve's anti-penetration push-out is
// instant (unlike gravity settling from ABOVE the floor, which
// takes further physics ticks this test never runs) - X/Y alone
// already discriminate (1,1) from (2,2) unambiguously.
Assert.Equal(2f, harness.Movement.Controller!.Position.X);
Assert.Equal(2f, harness.Movement.Controller.Position.Y);
Assert.Equal(0x20210001u, harness.Movement.Controller.CellId);
}
[Fact]
public void SessionReset_ClearsTransitAndLetsRecenterConvergeAcrossLaterFrames()
{
var harness = new Harness();
harness.Controller.OnTeleportStarted(12);
harness.OfferDestination(
Position(0x20210001u, 12, 1f, 2f, 3f),
teleportTimestampAdvanced: true);
harness.Controller.ResetSession();
Assert.False(harness.Controller.IsActive);
Assert.Equal(RuntimePortalSnapshot.Idle, harness.Reveal.Snapshot);
Assert.True(harness.Streaming.LastResetWasSessionEnding);
var failed = new Harness();
failed.Streaming.ResetResult = false;
failed.Controller.ResetSession();
Assert.False(failed.Controller.IsActive);
Assert.True(failed.Streaming.LastResetWasSessionEnding);
}
[Fact]
public void StaleStart_DoesNotMutateInputModeOrPresentation()
{
var harness = new Harness();
harness.Authority.IsFresh = false;
harness.Controller.OnTeleportStarted(13);
Assert.Equal(0, harness.Input.EndCount);
Assert.Equal(0, harness.Mode.EnterPortalCount);
Assert.Null(harness.Presentation.BeginProjection);
Assert.False(harness.Controller.IsActive);
}
[Fact]
public void ReentrantNewStartDuringPlacement_CannotBeCompletedByOldTail()
{
var harness = new Harness();
harness.Controller.OnTeleportStarted(20);
harness.OfferDestination(
Position(0x20210001u, 20, 1f, 2f, 3f),
teleportTimestampAdvanced: true);
harness.Streaming.ReservationEnds.Clear();
harness.Placement.OnPlace = () => harness.Controller.OnTeleportStarted(21);
harness.Presentation.Enqueue(TeleportAnimEvent.Place);
harness.Controller.Tick(0.016f);
Assert.True(harness.Controller.IsActive);
Assert.Equal(0u, harness.Controller.ActiveDestinationCell);
Assert.Equal(0, harness.Reveal.PortalMaterializationCount);
Assert.Single(harness.Streaming.ReservationEnds);
}
[Fact]
public void ReentrantNewStartDuringLoginComplete_PreservesReplacementLifetime()
{
var harness = new Harness();
harness.Controller.OnTeleportStarted(30);
harness.OfferDestination(
Position(0x20210001u, 30, 1f, 2f, 3f),
teleportTimestampAdvanced: true);
harness.Session.OnSend = () => harness.Controller.OnTeleportStarted(31);
harness.Presentation.Enqueue(TeleportAnimEvent.FireLoginComplete);
harness.Controller.Tick(0.016f);
Assert.True(harness.Controller.IsActive);
Assert.Equal(0u, harness.Controller.ActiveDestinationCell);
Assert.False(harness.Reveal.Snapshot.Completed);
Assert.Equal(1, harness.Session.LoginCompleteCount);
}
[Fact]
public void DisposeFailure_CanBeRetriedWithoutDoubleDisposingAfterSuccess()
{
var harness = new Harness();
harness.Presentation.DisposeFailuresRemaining = 1;
Assert.Throws<InvalidOperationException>(() => harness.Controller.Dispose());
harness.Controller.Dispose();
harness.Controller.Dispose();
Assert.Equal(2, harness.Presentation.DisposeCount);
}
[Fact]
public void ConcretePlacement_CommitsEntityControllerAndOneSpatialReconcile()
{
const uint guid = 0x50000001u;
const uint cell = 0x20210001u;
var world = new GpuWorldState();
world.AddLandblock(new LoadedLandblock(
0x2021FFFFu,
new DatReaderWriter.DBObjs.LandBlock(),
Array.Empty<WorldEntity>()));
var runtime = LiveEntityRuntimeFixture.Create(world, new NullResources());
runtime.RegisterLiveEntity(Spawn(guid, cell));
WorldEntity entity = runtime.MaterializeLiveEntity(
guid,
cell,
id => new WorldEntity
{
Id = id,
ServerGuid = guid,
SourceGfxObjOrSetupId = 0x02000001u,
Position = Vector3.Zero,
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty<MeshRef>(),
})!;
var identity = new LocalPlayerIdentityState { ServerGuid = guid };
var controllerSlot = new RuntimeLocalPlayerMovementState
{
Controller = new PlayerMovementController(new PhysicsEngine()),
};
var position = new Vector3(12f, 24f, 6f);
Quaternion rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 0.5f);
// C4 route 3: the canonical Runtime commit (RuntimeSetPositionState
// .CommitCanonical, driven by RuntimeAcceptedPositionDriveController
// .ReconcileAndAcknowledgePortal -> PlayerMovementController
// .CommitCanonicalTeleportFrame in production) already resolves the
// body/cell/orientation BEFORE this suffix runs. Reproduce that
// pre-state directly rather than re-resolving it here - Place() no
// longer resolves anything (D-T4).
controllerSlot.Controller.SetPosition(position, cell, position);
controllerSlot.Controller.SetBodyOrientation(rotation);
var cameras = new ChaseCameraInputState
{
Legacy = new ChaseCamera(),
Retail = new RetailChaseCamera(),
};
var spatial = new FakeSpatialReconcile(() => new PlacementSnapshot(
entity.Position,
entity.ParentCellId ?? 0u,
entity.Rotation,
controllerSlot.Controller.Position,
controllerSlot.Controller.CellId,
controllerSlot.Controller.BodyOrientation));
var placement = new LocalPlayerTeleportPlacement(
runtime,
identity,
controllerSlot,
new LocalPlayerPhysicsHostSlot(),
cameras,
spatial);
placement.Place(rotation);
Assert.Equal(entity.Position, controllerSlot.Controller.Position);
Assert.Equal(entity.ParentCellId, controllerSlot.Controller.CellId);
Assert.Equal(cell & 0xFFFF0000u, entity.ParentCellId & 0xFFFF0000u);
Assert.Equal(rotation, entity.Rotation);
Assert.Equal(rotation, controllerSlot.Controller.BodyOrientation);
Assert.Equal(1, spatial.Count);
Assert.Equal(entity.Position, spatial.Snapshot.EntityPosition);
Assert.Equal(entity.ParentCellId, spatial.Snapshot.EntityCell);
Assert.Equal(entity.Rotation, spatial.Snapshot.EntityRotation);
Assert.Equal(
controllerSlot.Controller.Position,
spatial.Snapshot.ControllerPosition);
Assert.Equal(
controllerSlot.Controller.CellId,
spatial.Snapshot.ControllerCell);
Assert.Equal(
controllerSlot.Controller.BodyOrientation,
spatial.Snapshot.ControllerRotation);
}
[Fact]
public void ConcretePlacement_CommitsDestinationSpatialBucketBeforeReconcile()
{
const uint guid = 0x50000001u;
const uint sourceCell = 0x20210001u;
const uint destinationCell = 0x30310001u;
var world = new GpuWorldState();
world.AddLandblock(new LoadedLandblock(
sourceCell & 0xFFFF0000u | 0xFFFFu,
new DatReaderWriter.DBObjs.LandBlock(),
Array.Empty<WorldEntity>()));
world.AddLandblock(new LoadedLandblock(
destinationCell & 0xFFFF0000u | 0xFFFFu,
new DatReaderWriter.DBObjs.LandBlock(),
Array.Empty<WorldEntity>()));
var runtime = LiveEntityRuntimeFixture.Create(world, new NullResources());
runtime.RegisterLiveEntity(Spawn(guid, sourceCell));
WorldEntity entity = runtime.MaterializeLiveEntity(
guid,
sourceCell,
id => new WorldEntity
{
Id = id,
ServerGuid = guid,
SourceGfxObjOrSetupId = 0x02000001u,
Position = Vector3.Zero,
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty<MeshRef>(),
})!;
var identity = new LocalPlayerIdentityState { ServerGuid = guid };
var controllerSlot = new RuntimeLocalPlayerMovementState
{
Controller = new PlayerMovementController(new PhysicsEngine()),
};
controllerSlot.Controller.SetPosition(
Vector3.Zero,
sourceCell,
Vector3.Zero);
// C4 route 3: reproduce the canonical Runtime commit's pre-state
// (body/cell already resolved) - Place() no longer resolves it.
var destinationPosition = new Vector3(12f, 24f, 6f);
controllerSlot.Controller.SetPosition(
destinationPosition,
destinationCell,
destinationPosition);
var cameras = new ChaseCameraInputState
{
Legacy = new ChaseCamera(),
Retail = new RetailChaseCamera(),
};
LiveEntityRecord? recordAtReconcile = null;
var spatial = new FakeSpatialReconcile(() =>
{
Assert.True(runtime.TryGetRecord(guid, out recordAtReconcile));
return new PlacementSnapshot(
entity.Position,
entity.ParentCellId ?? 0u,
entity.Rotation,
controllerSlot.Controller.Position,
controllerSlot.Controller.CellId,
controllerSlot.Controller.BodyOrientation);
});
var placement = new LocalPlayerTeleportPlacement(
runtime,
identity,
controllerSlot,
new LocalPlayerPhysicsHostSlot(),
cameras,
spatial);
placement.Place(Quaternion.Identity);
Assert.NotNull(recordAtReconcile);
uint resolvedDestinationCell = controllerSlot.Controller.CellId;
Assert.Equal(
destinationCell & 0xFFFF0000u,
resolvedDestinationCell & 0xFFFF0000u);
Assert.Equal(resolvedDestinationCell, recordAtReconcile.FullCellId);
Assert.Equal(
destinationCell & 0xFFFF0000u | 0xFFFFu,
recordAtReconcile.CanonicalLandblockId);
Assert.True(recordAtReconcile.IsSpatiallyProjected);
Assert.True(recordAtReconcile.IsSpatiallyVisible);
Assert.Contains(entity, world.Entities);
Assert.Equal(resolvedDestinationCell, entity.ParentCellId);
Assert.Equal(1, spatial.Count);
}
private static int Index(IReadOnlyList<string> events, string prefix)
{
for (int i = 0; i < events.Count; i++)
{
if (events[i].StartsWith(prefix, StringComparison.Ordinal))
return i;
}
return int.MaxValue;
}
private static RuntimeTeleportDestination Position(
uint cellId,
ushort teleportSequence,
float x,
float y,
float z) => new(
EntityGuid: 0x50000001u,
InstanceSequence: 1,
PositionSequence: 1,
TeleportSequence: teleportSequence,
ForcePositionSequence: 1,
Position: new Position(
cellId,
new Vector3(x, y, z),
Quaternion.Identity));
private static WorldSession.EntitySpawn Spawn(uint guid, uint cell) => new WorldSession.EntitySpawn(
Guid: guid,
Position: new CreateObject.ServerPosition(
cell,
0f,
0f,
0f,
1f,
0f,
0f,
0f),
SetupTableId: 0x02000001u,
AnimPartChanges: [],
TextureChanges: [],
SubPalettes: [],
BasePaletteId: null,
ObjScale: null,
Name: "player",
ItemType: null,
MotionState: null,
MotionTableId: null).WithConsistentPhysics();
private sealed class NullResources : ILiveEntityResourceLifecycle
{
public void Register(WorldEntity entity) { }
public void Unregister(WorldEntity entity) { }
}
private sealed class FakeSpatialReconcile : ILiveSpatialReconcilePhase
{
private readonly Func<PlacementSnapshot> _capture;
public FakeSpatialReconcile(Func<PlacementSnapshot> capture) =>
_capture = capture;
public int Count;
public PlacementSnapshot Snapshot;
public void Reconcile()
{
Snapshot = _capture();
Count++;
}
}
private readonly record struct PlacementSnapshot(
Vector3 EntityPosition,
uint EntityCell,
Quaternion EntityRotation,
Vector3 ControllerPosition,
uint ControllerCell,
Quaternion ControllerRotation);
/// <summary>
/// C4 route 3: the Place edge now drives a REAL
/// <see cref="RuntimeAcceptedPositionDriveController"/> portal arm
/// against a REAL (bare, session-less) local-player canonical record -
/// the class this file's <c>LocalPlayerTeleportController</c> fake
/// wiring cannot substitute (it is a sealed Runtime type, not an
/// interface). <see cref="OfferDestination"/> also performs the SAME
/// upstream merge production runs before ever offering a destination
/// (<c>RuntimeEntityObjectLifetime.TryApplyPosition</c>, called by
/// <c>LiveEntityNetworkUpdateController.OnPosition</c> before
/// <c>OfferDestination</c>) so <c>BeginAcceptedPlacementCore</c>'s
/// portal-vs-latest-cell gate sees the SAME destination the offer
/// carries - exactly the production ordering, not a shortcut.
/// </summary>
private sealed class Harness
{
private const uint PlayerGuid = 0x50000001u;
private const uint HomeCell = 0x20210001u;
private ushort _mergePositionSequence = 1;
public readonly FakeAuthority Authority = new();
public readonly FakeInput Input = new();
public readonly FakeMode Mode;
public readonly FakeStreaming Streaming;
public readonly FakePlacement Placement;
public readonly FakeSession Session;
public readonly FakePresentation Presentation;
public readonly RuntimeWorldTransitState Transit;
public readonly WorldRevealCoordinator Reveal;
public readonly LocalPlayerTeleportController Controller;
public readonly RuntimeEntityObjectLifetime Lifetime;
public readonly RuntimeAcceptedPositionDriveController AcceptedPositionDrive;
/// <summary>
/// A6/R4/A5 review fix (2026-08-05): exposed so tests can assert the
/// REAL canonical body's resolved position/cell after a committed
/// portal placement, not just <c>Placement.Called</c> — the property
/// that let A6's superseded-teleport test lose its destination
/// discriminator (which of two destinations actually placed).
/// </summary>
public readonly RuntimeLocalPlayerMovementState Movement;
public IPreparedCollisionSource DiagnosticCollisionSource => new UnusedCollisionSource();
public Harness(
int centerX = 0x20,
int centerY = 0x21,
bool worldReady = true,
List<string>? order = null)
{
order ??= new List<string>();
Mode = new FakeMode(order);
Streaming = new FakeStreaming(centerX, centerY, order);
Placement = new FakePlacement(order);
Session = new FakeSession(order);
Presentation = new FakePresentation(order);
Transit = new RuntimeWorldTransitState(order.Add);
Reveal = new WorldRevealCoordinator(
Transit,
isRenderNeighborhoodReady: (_, _) => worldReady,
isSpawnCellReady: _ => worldReady,
isTerrainNeighborhoodReady: (_, _) => worldReady,
areCompositeTexturesReady: () => worldReady,
prepareCompositeTextures: (_, _) => { },
invalidateCompositeTextures: () => { },
isSpawnClaimUnhydratable: _ => false,
streaming: Streaming);
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
var heights = new byte[81];
Array.Fill(heights, (byte)5f);
var heightTable = new float[256];
for (int i = 0; i < heightTable.Length; i++)
heightTable[i] = i;
engine.AddLandblock(
HomeCell & 0xFFFF0000u,
new TerrainSurface(heights, heightTable),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 0f,
worldOffsetY: 0f);
Lifetime = new RuntimeEntityObjectLifetime(engine);
// The bare AddLandblock above only seeds the ENGINE's terrain -
// TryPrepareAndSubmitAuthoredPlacement's DeferredCell park keys
// off the SEPARATE collision-GENERATION ledger
// (RuntimeSetPositionState.BeginCollisionGeneration/
// CommitCollisionGeneration), which a bare AddLandblock never
// touches. Commit it too, or every placement parks forever.
Lifetime.Physics.SetPosition.BeginCollisionGeneration(
HomeCell & 0xFFFF0000u, 1UL);
Lifetime.Physics.SetPosition.CommitCollisionGeneration(
HomeCell & 0xFFFF0000u, 1UL, ready: true);
Lifetime.Physics.ObserveLocalWorldFrame(HomeCell, teleportAdvanced: false);
Lifetime.BindEventContext(
static () => new RuntimeGenerationToken(1UL),
static () => 1UL);
Movement = new RuntimeLocalPlayerMovementState();
RuntimeLocalPlayerMovementState movement = Movement;
var identity = new RuntimeLocalPlayerIdentityState
{
ServerGuid = PlayerGuid,
};
movement.AttachPhysicsPublication(new RuntimeLocalPlayerPhysicsPublicationState(
Lifetime.Entities, Lifetime.Physics, movement, identity));
Lifetime.LocalPlayerFirstEntry.BindPublication(movement.PhysicsPublication);
Lifetime.BindLiveInputs(() => false, () => movement.Controller?.Position);
var clock = new GameRuntimeClock();
// C4 route 3 fixture note: RuntimeFirstEntryDriveController's
// ctor subscribes to RuntimeEntityObjectLifetime
// .BindInitialResidenceBeginNotification - it must exist BEFORE
// RegisterEntityWithInitialResidence runs, or it misses the
// notification and never learns the residence exists at all
// (production constructs it once per host session, ahead of
// every Create).
var firstEntry = new RuntimeFirstEntryDriveController(
Lifetime,
clock,
new UnusedCollisionSource(),
() => PlayerMovementConstructionOptions.Fallback,
static _ => new RuntimeLocalPlayerPhysicsActivationPreparation(
0.48f, 1.835f, RuntimeLocalPlayerShadowDisposition.ProvenShapeless));
// Production's OnSpawned shape: RegisterEntityWithInitialResidence
// then ApplyAcceptedSpawn (RuntimeLiveEntitySessionController.cs).
RuntimeEntityRegistrationResult registration =
Lifetime.RegisterEntityWithInitialResidence(
Spawn(PlayerGuid, HomeCell), isLocalPlayer: true);
RuntimeEntityRecord canonical = registration.Canonical
?? throw new InvalidOperationException(
"fixture failed to register the local player");
Lifetime.ApplyAcceptedSpawn(
canonical,
canonical.CreateIntegrationVersion,
canonical.Snapshot,
replaceGeneration: registration.Inbound.Disposition
is CreateObjectTimestampDisposition.NewGeneration);
for (int attempt = 0; attempt < 8 && firstEntry.PendingCount != 0; attempt++)
{
firstEntry.DriveAll();
DrainPlacementFifo();
}
Assert.Equal(0, firstEntry.PendingCount);
Assert.True(
Lifetime.Entities.TryGetActive(PlayerGuid, out RuntimeEntityRecord seeded),
"fixture failed to register the local player");
Assert.False(
Lifetime.TryGetInitialCreateResidence(seeded, out _),
"fixture left the local player's initial-create residence open");
AcceptedPositionDrive = new RuntimeAcceptedPositionDriveController(
Lifetime,
clock,
new UnusedCollisionSource(),
new LocalPlayerOutboundController((_, _, _, _, _, _) => { }),
() => new RuntimeGenerationToken(1UL),
() => PlayerGuid,
() => movement.Controller,
() => false,
() => null,
() => movement);
Controller = new LocalPlayerTeleportController(
Authority,
Input,
Mode,
Streaming,
Transit,
Reveal,
Placement,
Session,
Presentation,
AcceptedPositionDrive);
}
/// <summary>
/// Merges the destination into the SAME canonical record the portal
/// arm reads from, exactly like production's upstream merge, then
/// offers it to the controller.
/// </summary>
public void OfferDestination(
RuntimeTeleportDestination destination,
bool teleportTimestampAdvanced)
{
_mergePositionSequence++;
var update = new WorldSession.EntityPositionUpdate(
destination.EntityGuid,
new CreateObject.ServerPosition(
destination.Position.ObjCellId,
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),
Velocity: null,
PlacementId: null,
IsGrounded: true,
InstanceSequence: 1,
PositionSequence: _mergePositionSequence,
TeleportSequence: destination.TeleportSequence,
ForcePositionSequence: 0);
Lifetime.TryApplyPosition(
update,
isLocalPlayer: true,
forcePositionRotation: Quaternion.Identity,
currentLocalVelocity: Vector3.Zero,
acknowledgeProjection: null,
out _,
out _,
out _);
Controller.OfferDestination(destination, teleportTimestampAdvanced);
}
/// <summary>
/// B1 review fix test support (2026-08-05): merges an ORDINARY
/// accepted Position for the local player — no portal offer, no
/// transit involvement — the same
/// <c>RuntimeEntityObjectLifetime.TryApplyPosition</c> call every
/// real inbound ACE broadcast makes. Its doc names this the
/// mechanism that <c>Forget</c>s any outstanding SetPosition
/// operation for the entity, including a DeferredCell park — "the
/// exact far-destination case the park exists to serve".
/// </summary>
public void MergeOrdinaryPosition(
Vector3 position, uint cellId, ushort teleportSequence)
{
_mergePositionSequence++;
var update = new WorldSession.EntityPositionUpdate(
PlayerGuid,
new CreateObject.ServerPosition(
cellId,
position.X,
position.Y,
position.Z,
1f, 0f, 0f, 0f),
Velocity: null,
PlacementId: null,
IsGrounded: true,
InstanceSequence: 1,
PositionSequence: _mergePositionSequence,
// The SAME already-accepted teleport sequence, not a
// regression - an ordinary ACE broadcast Apply during an
// active teleport still carries it unchanged; only a NEW
// portal advances it.
TeleportSequence: teleportSequence,
ForcePositionSequence: 0);
Lifetime.TryApplyPosition(
update,
isLocalPlayer: true,
forcePositionRotation: Quaternion.Identity,
currentLocalVelocity: Vector3.Zero,
acknowledgeProjection: null,
out _,
out _,
out _);
}
/// <summary>
/// Commits a SECOND landblock's collision so a cross-landblock
/// portal destination can resolve <c>Committed</c> instead of
/// parking <c>DeferredCell</c> - the constructor's own home
/// landblock is the only one committed by default.
/// </summary>
public void CommitLandblockCollision(uint landblockId)
{
var heights = new byte[81];
Array.Fill(heights, (byte)5f);
var heightTable = new float[256];
for (int i = 0; i < heightTable.Length; i++)
heightTable[i] = i;
Lifetime.Physics.SetPosition.BeginCollisionGeneration(landblockId, 1UL);
Lifetime.Physics.Engine.AddLandblock(
landblockId,
new TerrainSurface(heights, heightTable),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 0f,
worldOffsetY: 0f);
Lifetime.Physics.SetPosition.CommitCollisionGeneration(
landblockId, 1UL, ready: true);
}
/// <summary>
/// Registers a minimal synthetic indoor cell so a portal destination
/// whose low word is &gt;= 0x0100 can resolve <c>Committed</c> on its
/// FIRST placement attempt instead of parking <c>DeferredCell</c>
/// forever. <see cref="PhysicsEngine.IsSpawnCellReady"/> treats every
/// outdoor cell (low word &lt; 0x0100) as trivially ready, but an
/// indoor cell requires <c>DataCache.GetCellStruct</c> to resolve a
/// real entry - the bare fixture's <c>AddLandblock</c> calls only
/// ever pass an empty <c>CellSurface</c> list, so without this an
/// indoor destination cell is permanently un-spawnable regardless of
/// how many times collision generation is committed for its
/// landblock. Mirrors
/// <c>RuntimeSetPositionStateTests.AddSyntheticCell</c>.
/// </summary>
public void AddSyntheticIndoorCell(uint envCellId)
{
Lifetime.Physics.DataCache.RegisterCellStructForTest(
envCellId,
new CellPhysics
{
WorldTransform = Matrix4x4.Identity,
InverseWorldTransform = Matrix4x4.Identity,
Resolved = new Dictionary<ushort, ResolvedPolygon>(),
Portals = [new PortalInfo(0, 0, 0)],
CellBSP = new DatReaderWriter.Types.CellBSPTree
{
Root = new DatReaderWriter.Types.CellBSPNode
{
Type = BSPNodeType.Leaf,
},
},
});
Lifetime.Physics.DataCache.CellGraph.Add(
new AcDream.Core.World.Cells.EnvCell(
envCellId,
Matrix4x4.Identity,
Matrix4x4.Identity,
Vector3.Zero,
Vector3.One,
Array.Empty<AcDream.Core.World.Cells.CellPortal>(),
Array.Empty<uint>(),
seenOutside: false,
containmentBsp: null));
}
public void DrainPlacementFifo()
{
while (Lifetime.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot head))
{
if (!Lifetime.Physics.SetPosition.AcknowledgeProjection(head.Token))
break;
}
}
private static WorldSession.EntitySpawn Spawn(uint guid, uint cell)
{
var position = new CreateObject.ServerPosition(
cell, 10f, 10f, 5f, 1f, 0f, 0f, 0f);
var timestamps = new PhysicsTimestamps(
Position: 1,
Movement: 1,
State: 1,
Vector: 1,
Teleport: 0,
ServerControlledMove: 1,
ForcePosition: 0,
ObjDesc: 1,
Instance: 1);
var physics = new PhysicsSpawnData(
RawState: (uint)PhysicsStateFlags.ReportCollisions,
Position: position,
Movement: null,
AnimationFrame: null,
SetupTableId: null,
MotionTableId: null,
SoundTableId: null,
PhysicsScriptTableId: null,
Parent: null,
Children: null,
Scale: null,
Friction: null,
Elasticity: null,
Translucency: null,
Velocity: null,
Acceleration: null,
AngularVelocity: null,
DefaultScriptType: null,
DefaultScriptIntensity: null,
Timestamps: timestamps);
return new WorldSession.EntitySpawn(
guid,
position,
null,
[],
[],
[],
null,
null,
"teleport-controller-fixture",
null,
null,
null,
PhysicsState: physics.RawState,
InstanceSequence: 1,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: 1,
Physics: physics);
}
private sealed class UnusedCollisionSource : IPreparedCollisionSource
{
public PreparedAssetPresence ProbeCollision(
AcDream.Content.Pak.PakAssetType type, uint sourceFileId) =>
PreparedAssetPresence.Available;
public PreparedCollisionReadResult<FlatSetupCollision> ReadSetupCollision(
uint sourceFileId, CancellationToken cancellationToken = default) =>
PreparedCollisionReadResult<FlatSetupCollision>.Missing;
public PreparedCollisionReadResult<FlatGfxObjCollisionAsset> ReadGfxObjCollision(
uint sourceFileId, CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public PreparedCollisionReadResult<FlatCellStructureCollisionAsset>
ReadCellStructureCollision(
uint sourceFileId, CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public PreparedCollisionReadResult<FlatEnvCellTopology> ReadEnvCellTopology(
uint sourceFileId, CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public PreparedCollisionSourceStats CollisionStats => default;
public void Dispose()
{
}
}
}
private sealed class FakeAuthority : ILocalPlayerTeleportAuthority
{
public bool IsFresh = true;
public bool IsFreshStart(ushort sequence) => IsFresh;
}
private sealed class FakeInput : ILocalPlayerTeleportInputLifetime
{
public int EndCount;
public void EndMouseLook() => EndCount++;
}
private sealed class FakeMode : ILocalPlayerTeleportModeOperations
{
private readonly List<string> _order;
public FakeMode(List<string> order)
{
_order = order;
Controller = new PlayerMovementController(new PhysicsEngine());
Controller.SetPosition(Vector3.Zero, 0x20210001u, Vector3.Zero);
}
public PlayerMovementController? Controller { get; set; }
public Matrix4x4 Projection => Matrix4x4.Identity;
public int EnterPortalCount;
public Func<PlayerMovementController?>? RebuildOnEnter;
public bool TryEnterPortalSpace()
{
EnterPortalCount++;
Controller ??= RebuildOnEnter?.Invoke();
if (Controller is null)
return false;
Controller.State = PlayerState.PortalSpace;
return true;
}
public void EnterWorld()
{
_order.Add("enter-world");
if (Controller is { } controller)
controller.State = PlayerState.InWorld;
}
}
private sealed class FakeStreaming
: ILocalPlayerTeleportStreamingOperations,
IWorldRevealStreamingScheduler
{
private readonly List<string> _order;
public FakeStreaming(int centerX, int centerY, List<string> order)
{
CenterX = centerX;
CenterY = centerY;
_order = order;
}
public int CenterX { get; }
public int CenterY { get; }
public bool IsRecenterPending => RecenterPending;
public bool RecenterPending;
public bool ResetResult = true;
public bool LastResetWasSessionEnding;
public readonly List<(int X, int Y, bool Sealed)> Recenters = new();
public readonly List<(long Generation, uint Cell, int Radius)>
Reservations = new();
public readonly List<long> ReservationEnds = new();
public bool BeginRecenter(int x, int y, bool isSealedDungeon)
{
Recenters.Add((x, y, isSealedDungeon));
return !RecenterPending;
}
public bool ResetRecenter(bool sessionEnding)
{
LastResetWasSessionEnding = sessionEnding;
return ResetResult;
}
public bool IsSealedDungeon(uint cellId) =>
(cellId & 0xFFFFu) >= 0x0100u;
public void BeginDestinationReservation(
long revealGeneration,
uint destinationCell,
int requiredRenderRadius) =>
Reservations.Add(
(revealGeneration, destinationCell, requiredRenderRadius));
public void EndDestinationReservation(long revealGeneration)
{
ReservationEnds.Add(revealGeneration);
_order.Add("reservation-end");
}
}
/// <summary>
/// C4 route 3: acknowledge-only per D-T4 - no position/cell arguments
/// (the canonical Runtime commit already resolved them; this suffix only
/// runs on a COMMITTED receipt, never authors one). <see cref="Called"/>
/// stands in for the deleted <c>Position</c>/<c>CellId</c> capture: it is
/// the fact that matters now (whether the arm committed and the Place
/// edge acknowledged it), never a value this fake could plausibly
/// mis-record.
/// </summary>
private sealed class FakePlacement : ILocalPlayerTeleportPlacement
{
private readonly List<string> _order;
public FakePlacement(List<string> order) => _order = order;
public bool Called;
public Quaternion Rotation;
public Action? OnPlace;
public void Place(Quaternion rotation)
{
_order.Add("placement");
Called = true;
Rotation = rotation;
OnPlace?.Invoke();
}
}
private sealed class FakeSession : ILocalPlayerTeleportSession
{
private readonly List<string> _order;
public FakeSession(List<string> order) => _order = order;
public int LoginCompleteCount;
public Action? OnSend;
public void SendLoginComplete()
{
_order.Add("login-complete");
LoginCompleteCount++;
OnSend?.Invoke();
}
}
private sealed class FakeNetworkSink : ILocalPlayerTeleportNetworkSink
{
public List<uint> Starts { get; } = [];
public void OnTeleportStarted(uint sequence) => Starts.Add(sequence);
public void OfferDestination(
RuntimeTeleportDestination destination,
bool teleportTimestampAdvanced)
{
}
public void ResetSession()
{
}
public void ResetGenerationPresentation()
{
}
}
private sealed class FakePresentation : ILocalPlayerTeleportPresentation
{
private readonly List<string> _order;
private readonly Queue<IReadOnlyList<TeleportAnimEvent>> _events = new();
public FakePresentation(List<string> order) => _order = order;
public bool IsPortalViewportVisible { get; private set; }
public int CurrentTunnelFrame => 72;
public Matrix4x4? BeginProjection;
public bool EmitPlaceWhenReady;
public readonly List<bool> WorldReadyValues = new();
public readonly List<bool> WaitCueValues = new();
public int DisposeFailuresRemaining;
public int DisposeCount;
public void Enqueue(params TeleportAnimEvent[] events) => _events.Enqueue(events);
public void Begin(Matrix4x4 projection)
{
BeginProjection = projection;
_order.Add("presentation-begin");
}
public (TeleportAnimSnapshot Snapshot, IReadOnlyList<TeleportAnimEvent> Events)
Tick(float deltaSeconds, bool worldReady)
{
WorldReadyValues.Add(worldReady);
if (_events.Count > 0)
return (default, _events.Dequeue());
if (EmitPlaceWhenReady && worldReady)
{
EmitPlaceWhenReady = false;
return (default, new[] { TeleportAnimEvent.Place });
}
return (default, Array.Empty<TeleportAnimEvent>());
}
public void TickTunnel(float deltaSeconds) => _order.Add("tunnel-tick");
public void EnterTunnel() => IsPortalViewportVisible = true;
public void ExitTunnel() => IsPortalViewportVisible = false;
public void SetWaitCue(bool visible) => WaitCueValues.Add(visible);
public void Reset()
{
_order.Add("presentation-reset");
IsPortalViewportVisible = false;
BeginProjection = null;
_events.Clear();
}
public Matrix4x4 ApplyViewPlane(Matrix4x4 projection) => projection;
public ICamera ApplyViewPlane(ICamera camera) => camera;
public void DrawPortalViewport(int width, int height, Matrix4x4 projection) { }
public void Dispose()
{
DisposeCount++;
if (DisposeFailuresRemaining-- > 0)
throw new InvalidOperationException("injected disposal failure");
}
}
}