using System.Numerics;
using AcDream.App.Input;
using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.App.Net;
using AcDream.App.Physics;
using AcDream.App.Rendering.Vfx;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Core.Plugins;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
using AcDream.Runtime.World;
using DatReaderWriter.DBObjs;
namespace AcDream.App.Tests.World;
///
/// C5a §5.1 (2026-08-05) — the #318 composition test, landed WITH its fix
/// (coordinator override of the contract's original "red branch, fix lands
/// separately" plan — every commit in this campaign stays green). Drives a
/// REAL portal arrival through
/// AND the REAL /
/// pair, closing the gap every prior sink test left open: those tests never
/// wired a real at all, so they could only ever
/// assert on 's dedup cache — which
/// AP-145's bug pre-seeded regardless of whether the real collision shadow
/// moved.
///
///
/// AP-145, RETIRED by this commit:
/// RuntimePlacementPresentationSink.TryPublishPlace used to call
/// _localPlayerShadow.Set(...) directly — a pure cache write — instead
/// of routing through ,
/// the ONLY code path that calls ShadowPositionSynchronizer.Sync →
/// ShadowObjectRegistry.UpdatePosition, the real collision-shadow
/// publish. Two things went wrong together: (1) the portal jump's real
/// shadow entry never moved to the destination, and (2) the direct cache
/// write PRE-SEEDED SyncPose's own dedup check, so even a later
/// per-tick SyncPose call would see "nothing changed" and skip the
/// publish it would otherwise have performed. A cache-only assertion — the
/// shape every prior sink test used — was satisfied by the bug: the cache
/// said the right thing, only the real registry didn't.
///
///
///
/// The fix: TryPublishPlace now calls
/// _localPlayerShadowSync.SyncPose(entity, entity.Position, entity.Rotation,
/// record.FullCellId, force: true) — the SAME publisher ordinary per-tick
/// movement uses, constructed once and shared (composition root:
/// LivePresentationComposition.cs now builds the ONE
/// instance BEFORE the sink and
/// threads it through LivePresentationResult to
/// SessionPlayerComposition.cs, which no longer constructs its own).
/// force: true because this is the authoritative placement commit,
/// not an ordinary per-tick refresh — it must never be skipped by the dedup
/// path. (which
/// UpdatePosition calls internally) deregisters every prior cell row
/// for the entity before adding the new ones, so the SOURCE cell's row is
/// replaced, not duplicated — verified explicitly below.
///
///
///
/// Architecture review A2: the exact mirror asymmetry lived six lines
/// below the Place fix, on TryPublishWithdrawal — a bare
/// _localPlayerShadow.Clear() with no ShadowObjects.Suspend,
/// leaving a LIVE phantom collision row at the park's source cell for the
/// whole park window (the #184 shape). Fixed in this same commit, in the
/// same one-call shape: TryPublishWithdrawal now calls
/// _localPlayerShadowSync.Suspend(entity).
///
///
///
/// Architecture review A1: the original route-7-P4 test
/// ()
/// never registered the non-player entity, so
/// ShadowObjectRegistry.UpdatePosition's own not-registered early
/// return made every assertion pass whether or not the player-only gate
/// existed — it was vacuous on both branches. Corrected to establish a REAL
/// baseline registration first, so removing the gate would actually move the
/// row and actually pollute the player's cache.
///
///
///
/// Sabotage-verified (manual, all four facts, both directions): with
/// each fix/gate reverted in turn, the corresponding fact fails at exactly
/// its discriminating assertion; with the fix applied, all four are green.
///
///
public sealed class RuntimePlacementShadowCompositionTests
{
private const uint SourceCell = 0x01010001u;
private const uint DestinationCell = 0x01020001u;
private const uint Guid = 0x7000A101u;
private static readonly Vector3 SourcePosition = new(10f, 10f, 5f);
// Landblock-local (10,10) relative to the destination landblock's
// worldOffsetX=192 — falls in the same grid cell (0x0001, cx=0,cy=0,
// TerrainSurface.CellSize=24) as SourcePosition does in ITS landblock,
// so ShadowObjectRegistry's flood actually lands under DestinationCell.
private static readonly Vector3 DestinationPosition = new(202f, 10f, 5f);
private static readonly Vector3 WirePoseDoubleWrite = new(-900f, -900f, -900f);
private static readonly Quaternion DestinationOrientation =
Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 0.75f);
///
/// The discriminating composition. Establishes a REAL baseline shadow
/// registration at the source pose (proving the synchronizer mechanism
/// works in general), drives a real portal arrival through the sink
/// (T8: with a tolerated "wire pose" double-write already sitting on the
/// entity, exactly like LiveEntityNetworkUpdateController's early
/// generic write — the committed suffix must overwrite it), then checks
/// the dedup cache (informational negative control — would pass even
/// under the AP-145 bug, kept to make the sabotage argument
/// self-contained), the REAL
/// registry at the destination (the load-bearing assertion — this is
/// what the fix makes true), AND that the source cell's row is gone, not
/// duplicated.
///
[Fact]
public void Place_PublishesRealPhysicsShadowAtDestination_NotOnlyTheDedupCache()
{
Fixture fixture = Fixture.Create();
LiveEntityRecord record = fixture.Materialize(Spawn(Guid, 1, SourceCell));
WorldEntity entity = Assert.IsType(record.WorldEntity);
// Baseline: establish the player's REAL collision shadow at the
// source pose exactly like ordinary world entry does, and prove the
// synchronizer mechanism is not itself broken (sanity, not the gate).
fixture.Physics.ShadowObjects.Register(
entity.Id,
gfxObjId: entity.SourceGfxObjOrSetupId,
worldPos: SourcePosition,
rotation: Quaternion.Identity,
radius: 0.48f,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: SourceCell & 0xFFFF0000u,
collisionType: ShadowCollisionType.Sphere,
cylHeight: 1.835f,
seedCellId: SourceCell);
fixture.Synchronizer.Sync(entity, SourceCell, force: true);
Assert.Contains(
fixture.Physics.ShadowObjects.GetObjectsInCell(SourceCell),
e => e.EntityId == entity.Id);
// T8 (route 3 §8 item 9): model the tolerated generic render-pose
// write (LiveEntityNetworkUpdateController.cs:2284-2314) landing on
// the entity BEFORE the committed placement suffix runs. The
// intermediate "wire pose" must never leak past the Place edge.
entity.Position = WirePoseDoubleWrite;
entity.Rotation = Quaternion.Identity;
// A real portal arrival through the canonical placement authority.
RuntimePortalPlacementAuthority portal = fixture.BeginPortal(
DestinationCell,
teleportSequence: 1);
record.FullCellId = DestinationCell;
record.CanonicalLandblockId = (DestinationCell & 0xFFFF0000u) | 0xFFFFu;
record.Canonical.AdvancePlacementCommit();
RuntimePlacementProjectionSnapshot place = Placement(
fixture,
record,
portal,
DestinationPosition,
DestinationOrientation);
Assert.True(fixture.Sink.TryApply(in place));
// T8 END STATE: the committed suffix's resolved pose wins — the
// intermediate wire-pose write never leaks past the Place edge.
Assert.Equal(DestinationPosition, entity.Position);
Assert.Equal(DestinationOrientation, entity.Rotation);
Assert.Equal(DestinationCell, entity.ParentCellId);
// NEGATIVE CONTROL — deliberately NOT the gate. This is the exact
// assertion shape every prior sink test uses
// (RuntimePlacementPresentationSinkTests.Place_Reframes...); it
// would pass EVEN under the AP-145 bug, since the cache is what the
// buggy direct write updated. Kept here to make the sabotage
// argument self-contained: if this assertion were the only
// coverage, the bug would have been invisible.
Assert.Equal(
new LocalPlayerShadowState.Snapshot(
DestinationPosition,
DestinationOrientation,
DestinationCell),
fixture.LocalShadow.Current);
// THE DISCRIMINATING ASSERTION — this is what AP-145's bug broke and
// the fix restores: the real collision shadow used by every OTHER
// entity's collision sweep now actually followed the portal jump.
Assert.Contains(
fixture.Physics.ShadowObjects.GetObjectsInCell(DestinationCell),
e => e.EntityId == entity.Id);
// No double-publish, no stale row left behind: Register/UpdatePosition
// deregisters every prior cell row before adding the new ones, so the
// SOURCE cell must carry zero rows for this entity now.
Assert.DoesNotContain(
fixture.Physics.ShadowObjects.GetObjectsInCell(SourceCell),
e => e.EntityId == entity.Id);
}
///
/// The second half of AP-145's mechanism, and the second half of the
/// fix's proof: the OLD direct cache write did not merely skip the
/// publish once — it pre-seeded
/// 's own dedup
/// check, so even a SUBSEQUENT ordinary per-tick Sync call (the
/// thing that would normally self-heal a one-frame miss) saw "nothing
/// changed" and skipped the publish too. With the fix, TryPublishPlace
/// itself now runs the real publish through the SAME synchronizer, so
/// the subsequent ordinary tick's dedup skip is no longer a bug — it is
/// CORRECTLY a no-op, because the real registry is already right.
///
[Fact]
public void Place_ThenOrdinaryTick_DoesNotNeedToSelfHeal_RealShadowAlreadyRight()
{
Fixture fixture = Fixture.Create();
LiveEntityRecord record = fixture.Materialize(Spawn(Guid, 1, SourceCell));
WorldEntity entity = Assert.IsType(record.WorldEntity);
fixture.Physics.ShadowObjects.Register(
entity.Id,
gfxObjId: entity.SourceGfxObjOrSetupId,
worldPos: SourcePosition,
rotation: Quaternion.Identity,
radius: 0.48f,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: SourceCell & 0xFFFF0000u,
collisionType: ShadowCollisionType.Sphere,
cylHeight: 1.835f,
seedCellId: SourceCell);
fixture.Synchronizer.Sync(entity, SourceCell, force: true);
RuntimePortalPlacementAuthority portal = fixture.BeginPortal(
DestinationCell,
teleportSequence: 1);
record.FullCellId = DestinationCell;
record.CanonicalLandblockId = (DestinationCell & 0xFFFF0000u) | 0xFFFFu;
record.Canonical.AdvancePlacementCommit();
RuntimePlacementProjectionSnapshot place = Placement(
fixture,
record,
portal,
DestinationPosition,
DestinationOrientation);
Assert.True(fixture.Sink.TryApply(in place));
// Simulate the very next ordinary frame's shadow-sync tick — exactly
// what production runs every frame for the local player. Not forced:
// this is the real per-tick call shape, dedup and all. It is
// EXPECTED to be a no-op now: TryPublishPlace's own SyncPose call
// already did the real work, so the cache already matches.
fixture.Synchronizer.Sync(entity, DestinationCell);
Assert.Contains(
fixture.Physics.ShadowObjects.GetObjectsInCell(DestinationCell),
e => e.EntityId == entity.Id);
Assert.DoesNotContain(
fixture.Physics.ShadowObjects.GetObjectsInCell(SourceCell),
e => e.EntityId == entity.Id);
// Exactly one row for this entity anywhere — the ordinary tick's
// dedup no-op did not create a second one.
Assert.Single(
fixture.Physics.ShadowObjects.AllEntriesForDebug(),
e => e.EntityId == entity.Id);
}
///
/// Route 7 P4 still binds: a committed CHILD (or any non-local-player
/// entity) must not gain a broadphase row, and must not have its OWN
/// pose written into the PLAYER's dedup cache. The fix lives entirely
/// inside TryPublishPlace's pre-existing
/// record.ServerGuid == _localPlayerGuid() gate — unchanged by
/// this fix, only what runs INSIDE it changed.
///
///
/// C5a architecture review A1 (2026-08-05): the FIRST version of this
/// test registered nothing for the child, so
/// ShadowObjectRegistry.UpdatePosition's own
/// not-registered early return (ShadowObjectRegistry.cs:696) made
/// every assertion pass whether or not the gate existed — removing the
/// gate was unobservable because the sabotaged code path was ALSO a
/// no-op. This version establishes a REAL baseline registration for the
/// child first (mirroring fact 1's own baseline), so a gate-removed
/// sabotage would actually move the child's row and would actually
/// pollute the player's cache — both of which the assertions below now
/// check directly, not by absence of registration.
///
///
[Fact]
public void Place_ForNonLocalPlayerEntity_NeverTouchesShadowObjects()
{
const uint ChildGuid = 0x7000A102u;
Fixture fixture = Fixture.Create();
LiveEntityRecord record = fixture.Materialize(Spawn(ChildGuid, 1, SourceCell));
WorldEntity entity = Assert.IsType(record.WorldEntity);
Assert.NotEqual(Guid, ChildGuid);
// REAL baseline: the child has its own genuine collision shadow at
// the source pose, exactly like fact 1 establishes for the player.
// A sabotaged (gate-removed) TryPublishPlace WOULD move this row to
// the destination and WOULD write the child's pose into the
// player's LocalShadow cache — both are asserted against below.
fixture.Physics.ShadowObjects.Register(
entity.Id,
gfxObjId: entity.SourceGfxObjOrSetupId,
worldPos: SourcePosition,
rotation: Quaternion.Identity,
radius: 0.48f,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: SourceCell & 0xFFFF0000u,
collisionType: ShadowCollisionType.Sphere,
cylHeight: 1.835f,
seedCellId: SourceCell);
Assert.Contains(
fixture.Physics.ShadowObjects.GetObjectsInCell(SourceCell),
e => e.EntityId == entity.Id);
RuntimePortalPlacementAuthority portal = fixture.BeginPortal(
DestinationCell,
teleportSequence: 1);
record.FullCellId = DestinationCell;
record.CanonicalLandblockId = (DestinationCell & 0xFFFF0000u) | 0xFFFFu;
record.Canonical.AdvancePlacementCommit();
RuntimePlacementProjectionSnapshot place = Placement(
fixture,
record,
portal,
DestinationPosition,
DestinationOrientation);
Assert.True(fixture.Sink.TryApply(in place));
// The render entity DID move (Place still works for a non-player
// entity) — only the shadow-publish branch is player-gated.
Assert.Equal(DestinationPosition, entity.Position);
// THE DISCRIMINATING ASSERTIONS. Under a sabotaged (gate-removed)
// TryPublishPlace, SyncPose would run for the child: the child's own
// record IS its own current visible projection, so
// IsCurrentVisibleProjection would be true and the real publish
// would execute — moving the row to DestinationCell and vacating
// SourceCell (exactly what fact 1 asserts is CORRECT for the
// player). Here it must NOT happen — this is what tells the
// sabotage apart from the fix.
Assert.Contains(
fixture.Physics.ShadowObjects.GetObjectsInCell(SourceCell),
e => e.EntityId == entity.Id);
Assert.DoesNotContain(
fixture.Physics.ShadowObjects.GetObjectsInCell(DestinationCell),
e => e.EntityId == entity.Id);
Assert.Equal(1, fixture.Physics.ShadowObjects.TotalRegistered);
// The player's OWN dedup cache must stay untouched by a non-player
// Place — a sabotaged TryPublishPlace would call
// _localPlayerShadowSync.SyncPose using the CHILD's guid check but
// the PLAYER's cache field (the gate exists to prevent exactly a
// non-player entity's pose from being written where the player's
// pose belongs).
Assert.Null(fixture.LocalShadow.Current);
}
///
/// Architecture review A2 (2026-08-05): the exact mirror of AP-145's
/// Place-side bug lived six lines below it, on the Withdraw path
/// — TryPublishWithdrawal cleared only the dedup cache, leaving a
/// LIVE phantom row in at the
/// park's source cell for the whole park window (the #184 shape: every
/// other entity's collision sweep in that cell would collide with a
/// player who is, per every other acdream predicate, gone). Fixed in the
/// same commit as the Place half, via the SAME one-call shape:
/// TryPublishWithdrawal now calls
/// _localPlayerShadowSync.Suspend(entity), which does the real
/// registry suspend AND the cache clear together.
///
[Fact]
public void Withdraw_SuspendsRealPhysicsShadow_NotOnlyTheDedupCache()
{
Fixture fixture = Fixture.Create();
LiveEntityRecord record = fixture.Materialize(Spawn(Guid, 1, SourceCell));
WorldEntity entity = Assert.IsType(record.WorldEntity);
// Baseline: a real collision shadow at the source pose, exactly like
// ordinary world entry / fact 1 establishes.
fixture.Physics.ShadowObjects.Register(
entity.Id,
gfxObjId: entity.SourceGfxObjOrSetupId,
worldPos: SourcePosition,
rotation: Quaternion.Identity,
radius: 0.48f,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: SourceCell & 0xFFFF0000u,
collisionType: ShadowCollisionType.Sphere,
cylHeight: 1.835f,
seedCellId: SourceCell);
fixture.Synchronizer.Sync(entity, SourceCell, force: true);
Assert.Contains(
fixture.Physics.ShadowObjects.GetObjectsInCell(SourceCell),
e => e.EntityId == entity.Id);
Assert.NotNull(fixture.LocalShadow.Current);
RuntimePlacementProjectionSnapshot withdraw = Placement(
fixture,
record,
portal: default,
entity.Position,
entity.Rotation,
RuntimePlacementProjectionKind.Withdraw);
Assert.True(fixture.Sink.TryApply(in withdraw));
// THE DISCRIMINATING ASSERTIONS. Under the pre-fix bug, the cache
// clears (this passes either way) but the real registry keeps the
// SOURCE-cell row (a live phantom for the park's duration) — that is
// what the sabotage below must be able to catch.
Assert.Null(fixture.LocalShadow.Current);
Assert.DoesNotContain(
fixture.Physics.ShadowObjects.GetObjectsInCell(SourceCell),
e => e.EntityId == entity.Id);
// Suspend removes the entity from every cell bucket (TotalRegistered
// — what any collision sweep can find) but is deliberately NOT
// logical teardown (ShadowObjectRegistry.Suspend's own xmldoc: "the
// registry counterpart of retail CPhysicsObj::remove_shadows_from_cells
// during temporary leave-world/pending-cell residence"), so the
// RETAINED registration survives for TryPublishPlace's later
// force:true SyncPose (WithdrawalRestored, or a fresh Place) to
// re-publish from.
Assert.Equal(0, fixture.Physics.ShadowObjects.TotalRegistered);
Assert.Equal(1, fixture.Physics.ShadowObjects.RetainedRegistrationCount);
}
private static RuntimePlacementProjectionSnapshot Placement(
Fixture fixture,
LiveEntityRecord record,
RuntimePortalPlacementAuthority portal,
Vector3 position,
Quaternion orientation,
RuntimePlacementProjectionKind kind = RuntimePlacementProjectionKind.Place)
{
RuntimeEntityRecord canonical = record.Canonical;
var token = new RuntimePlacementProjectionToken(
Sequence: 1,
Revision: 1,
Entity: record.ProjectionKey!.Value,
PositionAuthorityVersion: canonical.PositionAuthorityVersion,
SpatialAuthorityVersion: canonical.SpatialAuthorityVersion,
PlacementCommitVersion: canonical.PlacementCommitVersion,
SessionLifetimeVersion: fixture.Runtime.SessionLifetimeVersion,
ExactCellId: canonical.FullCellId,
CollisionGeneration: 1,
Portal: portal);
return new RuntimePlacementProjectionSnapshot(
token,
kind,
position,
orientation,
CellLocalPosition: position,
InContact: false,
OnWalkable: false);
}
private static WorldSession.EntitySpawn Spawn(
uint guid,
ushort instance,
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: instance);
var physics = new PhysicsSpawnData(
RawState: (uint)PhysicsStateFlags.ReportCollisions,
Position: position,
Movement: null,
AnimationFrame: null,
SetupTableId: 0x02000001u,
MotionTableId: 0x09000001u,
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,
0x02000001u,
Array.Empty(),
Array.Empty(),
Array.Empty(),
null,
null,
"fixture",
null,
null,
0x09000001u,
PhysicsState: (uint)PhysicsStateFlags.ReportCollisions,
InstanceSequence: instance,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: 1,
Physics: physics);
}
private sealed class Fixture
{
private Fixture(
PhysicsEngine physics,
GpuWorldState spatial,
LiveEntityRuntime runtime,
RuntimeWorldTransitState transit,
WorldGameState worldState,
WorldEvents worldEvents,
EntityEffectPoseRegistry effectPoses,
LocalPlayerShadowState localShadow,
LocalPlayerShadowSynchronizer synchronizer)
{
Physics = physics;
Spatial = spatial;
Runtime = runtime;
Transit = transit;
WorldState = worldState;
WorldEvents = worldEvents;
EffectPoses = effectPoses;
LocalShadow = localShadow;
Synchronizer = synchronizer;
Sink = new RuntimePlacementPresentationSink(
runtime,
transit,
worldState,
worldEvents,
effectPoses,
synchronizer,
() => Guid,
_ => { },
[(_, _) => { }]);
}
internal PhysicsEngine Physics { get; }
internal GpuWorldState Spatial { get; }
internal LiveEntityRuntime Runtime { get; }
internal RuntimeWorldTransitState Transit { get; }
internal WorldGameState WorldState { get; }
internal WorldEvents WorldEvents { get; }
internal EntityEffectPoseRegistry EffectPoses { get; }
internal LocalPlayerShadowState LocalShadow { get; }
internal LocalPlayerShadowSynchronizer Synchronizer { get; }
internal RuntimePlacementPresentationSink Sink { get; }
internal static Fixture Create()
{
var physics = new PhysicsEngine { DataCache = new PhysicsDataCache() };
physics.AddLandblock(
SourceCell & 0xFFFF0000u,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty(),
Array.Empty(),
worldOffsetX: 0f,
worldOffsetY: 0f);
physics.AddLandblock(
DestinationCell & 0xFFFF0000u,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty(),
Array.Empty(),
worldOffsetX: 192f,
worldOffsetY: 0f);
var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(SourceCell | 0xFFFFu));
spatial.AddLandblock(EmptyLandblock(DestinationCell | 0xFFFFu));
var resources = new RecordingResources();
LiveEntityRuntime runtime = LiveEntityRuntimeFixture.Create(
spatial,
resources,
physics);
var identity = new LocalPlayerIdentityState { ServerGuid = Guid };
var origin = new LiveWorldOriginState();
origin.SetPlaceholder(0, 0);
var localShadow = new LocalPlayerShadowState();
var synchronizer = new LocalPlayerShadowSynchronizer(
physics,
runtime,
identity,
origin,
localShadow);
return new Fixture(
physics,
spatial,
runtime,
new RuntimeWorldTransitState(),
new WorldGameState(),
new WorldEvents(),
new EntityEffectPoseRegistry(),
localShadow,
synchronizer);
}
internal LiveEntityRecord Materialize(WorldSession.EntitySpawn spawn)
{
LiveEntityRecord record = Runtime.RegisterAndMaterializeProjection(spawn);
Assert.False(Runtime.HasActiveInitialCreateResidence(
record.Canonical));
Assert.True(record.ResourcesRegistered);
WorldEntity entity = record.WorldEntity!;
var snapshot = new AcDream.Plugin.Abstractions.WorldEntitySnapshot(
entity.Id,
entity.SourceGfxObjOrSetupId,
entity.Position,
entity.Rotation);
WorldState.Add(snapshot);
WorldEvents.UpsertCurrent(snapshot);
EffectPoses.PublishMeshRefs(entity);
return record;
}
internal RuntimePortalPlacementAuthority BeginPortal(
uint cell,
ushort teleportSequence)
{
Assert.True(Transit.TryQueueTeleportStart(teleportSequence));
Assert.True(Transit.ActivateQueuedTeleport());
Assert.True(Transit.OfferTeleportDestination(
new RuntimeTeleportDestination(
Guid,
InstanceSequence: 1,
PositionSequence: 1,
TeleportSequence: teleportSequence,
ForcePositionSequence: 1,
new Position(
cell,
new Vector3(1f, 2f, 3f),
Quaternion.Identity)),
teleportTimestampAdvanced: true));
Assert.True(Transit.TryBeginPortalReveal(
teleportSequence,
cell,
out long generation));
Assert.True(Transit.TryRegisterHostProjection(
generation,
cell,
out RuntimeWorldHostProjectionToken host));
return new RuntimePortalPlacementAuthority(
true,
generation,
teleportSequence,
host);
}
private static LoadedLandblock EmptyLandblock(uint canonicalId) =>
new(canonicalId, new LandBlock(), Array.Empty());
}
private sealed class RecordingResources : ILiveEntityResourceLifecycle
{
public void Register(WorldEntity entity) { }
public void Unregister(WorldEntity entity) { }
}
}