using AcDream.Core.Physics; using AcDream.Runtime.Entities; namespace AcDream.Runtime.Physics; public readonly record struct RuntimePhysicsOwnershipSnapshot( int LandblockCount, int RetainedShadowRegistrationCount, int SpatialRootCount, int SpatialRemoteCount, int SpatialProjectileCount, int CollisionAdmissionCount, int CollisionGenerationCount, bool OwnsProductionDataCache, bool IsDisposed) { public bool IsConverged => IsDisposed && LandblockCount == 0 && RetainedShadowRegistrationCount == 0 && SpatialRootCount == 0 && SpatialRemoteCount == 0 && SpatialProjectileCount == 0 && CollisionAdmissionCount == 0 && CollisionGenerationCount == 0 && OwnsProductionDataCache; } public readonly record struct RuntimePhysicsCellCommit( RuntimeEntityRecord Record, uint PreviousFullCellId, uint FullCellId, ulong SpatialAuthorityVersion); public sealed record RuntimeLandblockCollisionAssets( uint LandblockId, TerrainSurface Terrain, IReadOnlyList CellSurfaces, IReadOnlyList PortalPlanes, float WorldOffsetX, float WorldOffsetY, uint CurrentCellId); public sealed class RuntimeCollisionAdmission { internal RuntimeCollisionAdmission( RuntimePhysicsState owner, uint landblockId, ulong generation) { Owner = owner; LandblockId = landblockId; Generation = generation; } internal RuntimePhysicsState Owner { get; } internal bool AssetsAdmitted { get; set; } internal bool Completed { get; set; } public uint LandblockId { get; } public ulong Generation { get; } } public readonly record struct RuntimeCollisionAcknowledgement( uint LandblockId, ulong Generation, bool WasResident); /// /// Presentation-free mutable physics world for one Runtime/session owner. /// Immutable prepared collision inputs may be supplied by a graphical or /// no-window host, but the engine, transition scratch, cell graph, and shadow /// registry are never shared between Runtime instances. /// public sealed class RuntimePhysicsState : IDisposable { private readonly TimeProvider _timeProvider; private readonly Dictionary _spatialRoots = new(); private readonly Dictionary _spatialRemotes = new(); private readonly Dictionary _spatialProjectiles = new(); private readonly Dictionary _collisionGenerations = new(); private readonly Dictionary _collisionAdmissions = new(); private bool _disposed; public event Action? CellCommitted; internal RuntimePhysicsState( RuntimeEntityDirectory entities, PhysicsDataCache? dataCache = null, TimeProvider? timeProvider = null) { Entities = entities ?? throw new ArgumentNullException(nameof(entities)); _timeProvider = timeProvider ?? TimeProvider.System; DataCache = dataCache ?? PhysicsDataCache.CreateProduction(); Engine = new PhysicsEngine { DataCache = DataCache, }; } internal RuntimePhysicsState( RuntimeEntityDirectory entities, PhysicsEngine engine, TimeProvider? timeProvider = null) { Entities = entities ?? throw new ArgumentNullException(nameof(entities)); _timeProvider = timeProvider ?? TimeProvider.System; Engine = engine ?? throw new ArgumentNullException(nameof(engine)); DataCache = engine.DataCache ?? PhysicsDataCache.CreateProduction(); Engine.DataCache = DataCache; } internal RuntimeEntityDirectory Entities { get; } public PhysicsEngine Engine { get; } public PhysicsDataCache DataCache { get; } public int SpatialRootCount => _spatialRoots.Count; public int SpatialRemoteCount => _spatialRemotes.Count; public int SpatialProjectileCount => _spatialProjectiles.Count; internal double UtcNowSeconds => (_timeProvider.GetUtcNow() - DateTimeOffset.UnixEpoch) .TotalSeconds; public RuntimePhysicsOwnershipSnapshot CaptureOwnership() => new( Engine.LandblockCount, Engine.ShadowObjects.RetainedRegistrationCount, _spatialRoots.Count, _spatialRemotes.Count, _spatialProjectiles.Count, _collisionAdmissions.Count, _collisionGenerations.Count, ReferenceEquals(Engine.DataCache, DataCache), _disposed); public void AcknowledgeSpatialProjection( RuntimeEntityRecord record, bool spatial) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); if (record.Key is not { } key) { if (spatial) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} cannot enter the physics workset without a local identity."); } return; } if (spatial && Entities.IsCurrent(record)) { _spatialRoots[key] = record; if (record.RemoteMotion is { } remote) _spatialRemotes[key] = remote; else _spatialRemotes.Remove(key); if (record.Projectile is { } projectile) _spatialProjectiles[key] = projectile; else _spatialProjectiles.Remove(key); return; } RemoveSpatialProjection(record); } public void RefreshRemoteComponent(RuntimeEntityRecord record) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); if (record.Key is not { } key || !_spatialRoots.TryGetValue(key, out RuntimeEntityRecord? root) || !ReferenceEquals(root, record) || !Entities.IsCurrent(record)) { RemoveSpatialRemote(record); return; } if (record.RemoteMotion is { } remote) _spatialRemotes[key] = remote; else _spatialRemotes.Remove(key); } public void RefreshProjectileComponent(RuntimeEntityRecord record) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); if (record.Key is not { } key || !_spatialRoots.TryGetValue(key, out RuntimeEntityRecord? root) || !ReferenceEquals(root, record) || !Entities.IsCurrent(record)) { RemoveSpatialProjectile(record); return; } if (record.Projectile is { } projectile) _spatialProjectiles[key] = projectile; else _spatialProjectiles.Remove(key); } /// /// Installs the one projectile component for an exact canonical /// incarnation. App may prepare the DAT collision sphere, but Runtime owns /// component identity, prediction invalidation, and workset membership. /// public IRuntimeProjectile BindProjectile( RuntimeEntityRecord record, PhysicsBody body, ProjectileCollisionSphere collisionSphere, Func? externalOwnerValid = null) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(body); EnsureCurrent(record); if (!collisionSphere.IsValid) { throw new ArgumentOutOfRangeException( nameof(collisionSphere), "A Runtime projectile requires one valid prepared Setup sphere."); } if (!(externalOwnerValid?.Invoke() ?? true)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed external ownership before projectile binding."); } if (record.Projectile is { } retained) { if (!ReferenceEquals(retained.Body, body) || !ReferenceEquals(record.PhysicsBody, body)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} projectile changed its canonical physics body."); } retained.Body.State = record.FinalPhysicsState; RefreshProjectileComponent(record); return retained; } if (record.ProjectileBindingInProgress) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} projectile binding is already in progress."); } if (!ReferenceEquals(record.PhysicsBody, body)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} projectile must borrow its canonical physics body."); } ulong sessionVersion = Entities.SessionLifetimeVersion; Entities.SetProjectileBindingInProgress(record, true); try { var component = new RuntimeProjectile(body, collisionSphere); if (Entities.SessionLifetimeVersion != sessionVersion || !Entities.IsCurrent(record) || !ReferenceEquals(record.PhysicsBody, body) || record.Projectile is not null || !(externalOwnerValid?.Invoke() ?? true)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed ownership during projectile binding."); } Entities.SetProjectile(record, component); body.State = record.FinalPhysicsState; SynchronizeBodyActiveState(record); RefreshProjectileComponent(record); return component; } finally { if (Entities.IsCurrent(record)) Entities.SetProjectileBindingInProgress(record, false); else record.ProjectileBindingInProgress = false; } } public bool ClearProjectile(RuntimeEntityRecord record) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); if (record.Projectile is null) return false; Entities.SetProjectile(record, null); Entities.SetProjectileBindingInProgress(record, false); RefreshProjectileComponent(record); return true; } /// /// Commits retail CPhysicsObj::set_velocity (0x005113F0) and, /// when supplied, the paired set_omega write to the incarnation's /// canonical body. Runtime owns the shared body/object-clock activation /// edge; graphical hosts only validate and translate inbound payloads. /// internal bool TryCommitAuthoritativeVector( RuntimeEntityRecord record, PhysicsBody body, System.Numerics.Vector3 velocity, System.Numerics.Vector3? angularVelocity, double currentTime, Func? externalOwnerValid = null) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(body); if (!IsFinite(velocity) || (angularVelocity is { } omega && !IsFinite(omega)) || !double.IsFinite(currentTime) || !Entities.IsCurrent(record) || !ReferenceEquals(record.PhysicsBody, body) || !(externalOwnerValid?.Invoke() ?? true)) { return false; } bool wasBodyActive = (body.TransientState & TransientStateFlags.Active) != 0; body.set_velocity(velocity); if ((record.FinalPhysicsState & PhysicsStateFlags.Static) != 0) { if (!wasBodyActive) body.TransientState &= ~TransientStateFlags.Active; } else { bool clockReactivated = record.ObjectClock.Activate(); if (clockReactivated || !wasBodyActive) body.LastUpdateTime = currentTime; } if (angularVelocity is { } acceptedOmega) body.Omega = acceptedOmega; return Entities.IsCurrent(record) && ReferenceEquals(record.PhysicsBody, body) && (externalOwnerValid?.Invoke() ?? true); } public void SetRemoteMotion( RuntimeEntityRecord record, IRuntimeRemoteMotion runtime, Func? externalOwnerValid = null) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(runtime); EnsureCurrent(record); if (!(externalOwnerValid?.Invoke() ?? true)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed external ownership before remote-motion binding."); } if (record.RemoteMotionBindingInProgress) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} remote-motion binding is already in progress."); } PhysicsBody candidateBody = runtime.Body ?? throw new InvalidOperationException( "A remote-motion runtime returned no physics body."); if (ReferenceEquals(record.RemoteMotion, runtime)) { if (!ReferenceEquals(record.PhysicsBody, candidateBody)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} remote motion changed its canonical physics body."); } candidateBody.State = record.FinalPhysicsState; SynchronizeBodyActiveState(record); RefreshRemoteComponent(record); return; } if (record.PhysicsBodyAcquisitionInProgress && record.PhysicsBody is null) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} cannot bind remote motion during physics-body acquisition."); } if (record.PhysicsBody is { } canonicalBody && !ReferenceEquals(canonicalBody, candidateBody)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} cannot replace its canonical physics body."); } if (record.RequiresRemotePlacementRuntime && runtime is not IRuntimeRemotePlacement) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} cannot discard its remote-placement contract."); } ulong sessionVersion = Entities.SessionLifetimeVersion; PhysicsBody? expectedBody = record.PhysicsBody; IRuntimeRemoteMotion? expectedRuntime = record.RemoteMotion; bool expectedPlacementContract = record.RequiresRemotePlacementRuntime; Func readPhysicsHost = () => Entities.IsCurrent(record) && ReferenceEquals(record.RemoteMotion, runtime) ? record.PhysicsHost : null; Func readCell = () => record.FullCellId; Action writeCell = cellId => CommitCanonicalCell(record, runtime, cellId); Entities.SetRemoteMotionBindingInProgress(record, true); try { if (runtime is IRuntimeCanonicalPhysicsConsumer canonicalConsumer) { canonicalConsumer.BindCanonicalRuntime( readPhysicsHost, readCell, writeCell); } else { bool consumesHost = runtime is IRuntimePhysicsHostConsumer; bool consumesCell = runtime is IRuntimeCanonicalCellConsumer; if (consumesHost && consumesCell) { throw new InvalidOperationException( "A remote runtime that consumes both canonical host and cell identity must bind them atomically."); } if (runtime is IRuntimePhysicsHostConsumer hostConsumer) hostConsumer.BindPhysicsHost(readPhysicsHost); if (runtime is IRuntimeCanonicalCellConsumer cellConsumer) cellConsumer.BindCanonicalCell(readCell, writeCell); } if (Entities.SessionLifetimeVersion != sessionVersion || !Entities.IsCurrent(record) || !ReferenceEquals(record.PhysicsBody, expectedBody) || !ReferenceEquals(record.RemoteMotion, expectedRuntime) || record.RequiresRemotePlacementRuntime != expectedPlacementContract || !(externalOwnerValid?.Invoke() ?? true)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed ownership during remote-motion binding."); } Entities.SetRequiresRemotePlacementRuntime( record, expectedPlacementContract || runtime is IRuntimeRemotePlacement); if (expectedBody is null) InitializeNewPhysicsBody(record, candidateBody); Entities.SetRemoteMotion(record, runtime); Entities.SetPhysicsBody(record, candidateBody); candidateBody.State = record.FinalPhysicsState; SynchronizeBodyActiveState(record); RefreshRemoteComponent(record); } finally { if (Entities.IsCurrent(record)) { Entities.SetRemoteMotionBindingInProgress(record, false); } } } /// /// Constructs the one built-in remote-motion component for an exact /// canonical incarnation. A graphical host may attach animation and pose /// sinks afterward, but cannot construct or replace simulation state. /// internal RemoteMotion GetOrCreateRemoteMotion( RuntimeEntityRecord record, Func? externalOwnerValid = null) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); EnsureCurrent(record); if (record.RemoteMotion is { } retained) { return retained as RemoteMotion ?? throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} owns a non-production remote-motion component."); } var created = new RemoteMotion(record.PhysicsBody); created.Movement.ActivatePhysicsObject = () => TryActivateOrdinaryObject(record, created); SetRemoteMotion(record, created, externalOwnerValid); return created; } internal bool TryActivateOrdinaryObject( RuntimeEntityRecord record, IRuntimeRemoteMotion runtime, Func? externalOwnerValid = null) { ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(runtime); if (!Entities.IsCurrent(record) || !ReferenceEquals(record.RemoteMotion, runtime) || (record.FinalPhysicsState & PhysicsStateFlags.Static) != 0 || !(externalOwnerValid?.Invoke() ?? true)) { return false; } record.ObjectClock.Activate(); runtime.Body.TransientState |= TransientStateFlags.Active; return true; } /// /// Acquires the one retail CPhysicsObj body for an exact accepted /// object incarnation. Factories may cross a host boundary, so ownership /// is revalidated after the callback before the body becomes canonical. /// public PhysicsBody GetOrCreatePhysicsBody( RuntimeEntityRecord record, Func factory, Func? externalOwnerValid = null) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(factory); EnsureCurrent(record); if (!(externalOwnerValid?.Invoke() ?? true)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed external ownership before physics-body acquisition."); } if (record.PhysicsBody is { } retained) return retained; if (record.PhysicsBodyAcquisitionInProgress) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} physics-body acquisition is already in progress."); } Entities.SetPhysicsBodyAcquisitionInProgress(record, true); try { PhysicsBody candidate = factory(record) ?? throw new InvalidOperationException( "Physics-body factory returned null."); if (!Entities.IsCurrent(record) || !(externalOwnerValid?.Invoke() ?? true)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed ownership during physics-body acquisition."); } if (record.PhysicsBody is { } concurrentlyBound) { if (ReferenceEquals(concurrentlyBound, candidate)) return concurrentlyBound; throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} acquired two physics bodies within one incarnation."); } InitializeNewPhysicsBody(record, candidate); Entities.SetPhysicsBody(record, candidate); candidate.State = record.FinalPhysicsState; SynchronizeBodyActiveState(record); return candidate; } finally { // A callback can retire this incarnation. The in-progress bit // belongs to the record itself and must not strand its teardown. if (Entities.IsCurrent(record)) Entities.SetPhysicsBodyAcquisitionInProgress(record, false); else record.PhysicsBodyAcquisitionInProgress = false; } } public void InstallPhysicsHost( RuntimeEntityRecord record, AcDream.Core.Physics.Motion.IPhysicsObjHost host, Func? externalOwnerValid = null) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(host); EnsureCurrent(record); if (host.Id != record.ServerGuid) { throw new ArgumentException( "A physics host must match its runtime entity GUID.", nameof(host)); } if (!(externalOwnerValid?.Invoke() ?? true)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed external ownership before physics-host installation."); } if (record.PhysicsHost is not null) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} already owns its incarnation-stable physics host."); } Entities.SetPhysicsHost(record, host); if (!Entities.IsCurrent(record) || !(externalOwnerValid?.Invoke() ?? true)) { if (ReferenceEquals(record.PhysicsHost, host)) record.PhysicsHost = null; throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed ownership during physics-host installation."); } } public EntityPhysicsHost InstallOrRebindPhysicsHost( RuntimeEntityRecord record, EntityPhysicsHost configuration, Func? externalOwnerValid = null) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(configuration); EnsureCurrent(record); if (!(externalOwnerValid?.Invoke() ?? true)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed external ownership before physics-host composition."); } if (record.PhysicsHost is null) { InstallPhysicsHost(record, configuration, externalOwnerValid); return configuration; } if (record.PhysicsHost is not EntityPhysicsHost existing) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} owns an incompatible physics-host implementation."); } existing.RebindFrom(configuration); if (!Entities.IsCurrent(record) || !(externalOwnerValid?.Invoke() ?? true)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed ownership during physics-host composition."); } return existing; } public EntityPhysicsHost SelectStablePhysicsHostWithoutRebind( RuntimeEntityRecord record, EntityPhysicsHost configuration, Func? externalOwnerValid = null) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(configuration); EnsureCurrent(record); if (!(externalOwnerValid?.Invoke() ?? true)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} changed external ownership during physics-host preparation."); } return record.PhysicsHost switch { null => configuration, EntityPhysicsHost existing => existing, _ => throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} owns an incompatible physics-host implementation."), }; } public bool TryGetPhysicsHost( uint serverGuid, out AcDream.Core.Physics.Motion.IPhysicsObjHost host) { EnsureNotDisposed(); if (Entities.TryGetActive(serverGuid, out RuntimeEntityRecord record) && record.PhysicsHost is { } existing) { host = existing; return true; } host = null!; return false; } public bool ClearRemoteMotion(RuntimeEntityRecord record) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); if (!Entities.IsCurrent(record) || record.RemoteMotion is null) { return false; } Entities.SetRemoteMotion(record, null); RefreshRemoteComponent(record); return true; } public void RemoveSpatialProjection(RuntimeEntityRecord record) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); if (record.Key is not { } key) return; if (_spatialRoots.TryGetValue(key, out RuntimeEntityRecord? root) && ReferenceEquals(root, record)) { _spatialRoots.Remove(key); } RemoveSpatialRemote(record); RemoveSpatialProjectile(record); } public bool IsSpatialRoot(RuntimeEntityRecord record) => record.Key is { } key && Entities.IsCurrent(record) && _spatialRoots.TryGetValue(key, out RuntimeEntityRecord? indexed) && ReferenceEquals(indexed, record); public bool IsSpatialRemote( RuntimeEntityRecord record, IRuntimeRemoteMotion remote) => IsSpatialRoot(record) && ReferenceEquals(record.RemoteMotion, remote) && record.Key is { } key && _spatialRemotes.TryGetValue(key, out IRuntimeRemoteMotion? indexed) && ReferenceEquals(indexed, remote); public bool IsSpatialProjectile( RuntimeEntityRecord record, IRuntimeProjectile projectile) => IsSpatialRoot(record) && ReferenceEquals(record.Projectile, projectile) && record.Key is { } key && _spatialProjectiles.TryGetValue(key, out IRuntimeProjectile? indexed) && ReferenceEquals(indexed, projectile); public void CopySpatialRootsTo(List destination) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(destination); destination.Clear(); foreach ((RuntimeEntityKey key, RuntimeEntityRecord record) in _spatialRoots) { if (record.Key == key && Entities.IsCurrent(record)) { destination.Add(record); } } } public void CopySpatialRemotesTo(List destination) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(destination); destination.Clear(); foreach ((RuntimeEntityKey key, IRuntimeRemoteMotion remote) in _spatialRemotes) { if (Entities.TryGetByLocalId( key.LocalEntityId, out RuntimeEntityRecord record) && record.Key == key && ReferenceEquals(record.RemoteMotion, remote) && IsSpatialRoot(record)) { destination.Add(record); } } } public void CopySpatialProjectilesTo(List destination) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(destination); destination.Clear(); foreach ((RuntimeEntityKey key, IRuntimeProjectile projectile) in _spatialProjectiles) { if (Entities.TryGetByLocalId( key.LocalEntityId, out RuntimeEntityRecord record) && record.Key == key && ReferenceEquals(record.Projectile, projectile) && IsSpatialRoot(record)) { destination.Add(record); } } } internal bool CommitOrdinaryCell( RuntimeEntityRecord record, PhysicsBody body, ulong objectClockEpoch, uint fullCellId, Func? externalOwnerValid) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(body); bool IsExactOwner() => IsSpatialRoot(record) && record.ObjectClockEpoch == objectClockEpoch && ReferenceEquals(record.PhysicsBody, body) && record.RemoteMotion is null && (externalOwnerValid?.Invoke() ?? true); return CommitCanonicalCell( record, fullCellId, IsExactOwner); } internal bool CommitProjectileCell( RuntimeEntityRecord record, IRuntimeProjectile projectile, ulong predictionAuthorityVersion, uint fullCellId, Func? externalOwnerValid) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(projectile); bool IsExactOwner() => Entities.IsCurrent(record) && ReferenceEquals(record.Projectile, projectile) && ReferenceEquals(record.PhysicsBody, projectile.Body) && projectile.PredictionAuthorityVersion == predictionAuthorityVersion && (externalOwnerValid?.Invoke() ?? true); return CommitCanonicalCell( record, fullCellId, IsExactOwner); } public void ClearSpatialWorksets() { EnsureNotDisposed(); _spatialRemotes.Clear(); _spatialProjectiles.Clear(); _spatialRoots.Clear(); } public RuntimeCollisionAdmission BeginCollisionAdmission( uint landblockId) { EnsureNotDisposed(); uint canonical = CanonicalLandblock(landblockId); ulong generation = _collisionGenerations.TryGetValue( canonical, out ulong current) ? checked(current + 1UL) : 1UL; _collisionGenerations[canonical] = generation; var admission = new RuntimeCollisionAdmission( this, canonical, generation); _collisionAdmissions[canonical] = admission; return admission; } public void AdmitCollisionAssets( RuntimeCollisionAdmission admission, RuntimeLandblockCollisionAssets assets) { ValidateAdmission(admission); ArgumentNullException.ThrowIfNull(assets); if (CanonicalLandblock(assets.LandblockId) != admission.LandblockId) { throw new ArgumentException( "Collision assets do not match their admission landblock.", nameof(assets)); } if (admission.Completed) { throw new InvalidOperationException( "A completed collision admission cannot publish more assets."); } if (admission.AssetsAdmitted) { throw new InvalidOperationException( "Collision assets were already admitted by this receipt."); } Engine.AddLandblock( admission.LandblockId, assets.Terrain, assets.CellSurfaces, assets.PortalPlanes, assets.WorldOffsetX, assets.WorldOffsetY); if ((assets.CurrentCellId & 0xFFFF0000u) == (admission.LandblockId & 0xFFFF0000u)) { Engine.UpdatePlayerCurrCell(assets.CurrentCellId); } admission.AssetsAdmitted = true; } public RuntimeCollisionAcknowledgement CompleteCollisionAdmission( RuntimeCollisionAdmission admission) { ValidateAdmission(admission); if (!admission.AssetsAdmitted) { throw new InvalidOperationException( "Collision admission cannot complete before its assets publish."); } if (admission.Completed) { throw new InvalidOperationException( "Collision admission has already completed."); } admission.Completed = true; _collisionAdmissions.Remove(admission.LandblockId); return new RuntimeCollisionAcknowledgement( admission.LandblockId, admission.Generation, Engine.IsLandblockTerrainResident(admission.LandblockId)); } public RuntimeCollisionAcknowledgement DemoteCollisionToTerrain( uint landblockId) { EnsureNotDisposed(); uint canonical = CanonicalLandblock(landblockId); bool resident = Engine.IsLandblockTerrainResident(canonical); InvalidateCollisionAdmission(canonical); Engine.DemoteLandblockToTerrain(canonical); return new RuntimeCollisionAcknowledgement( canonical, _collisionGenerations[canonical], resident); } public RuntimeCollisionAcknowledgement WithdrawCollision( uint landblockId) { EnsureNotDisposed(); uint canonical = CanonicalLandblock(landblockId); bool resident = Engine.IsLandblockTerrainResident(canonical); InvalidateCollisionAdmission(canonical); Engine.RemoveLandblock(canonical); return new RuntimeCollisionAcknowledgement( canonical, _collisionGenerations[canonical], resident); } public void Dispose() { if (_disposed) return; Engine.Clear(); _spatialRemotes.Clear(); _spatialProjectiles.Clear(); _spatialRoots.Clear(); _collisionAdmissions.Clear(); _collisionGenerations.Clear(); CellCommitted = null; _disposed = true; } private void CommitCanonicalCell( RuntimeEntityRecord record, IRuntimeRemoteMotion expectedRuntime, uint fullCellId) { _ = CommitCanonicalCell( record, fullCellId, () => Entities.IsCurrent(record) && ReferenceEquals(record.RemoteMotion, expectedRuntime)); } private bool CommitCanonicalCell( RuntimeEntityRecord record, uint fullCellId, Func exactOwnerValid) { if (fullCellId == 0 || !exactOwnerValid()) return false; if (fullCellId == record.FullCellId) return true; uint previousCellId = record.FullCellId; Entities.SetFullCell( record, fullCellId, (fullCellId & 0xFFFF0000u) | 0xFFFFu); CellCommitted?.Invoke( new RuntimePhysicsCellCommit( record, previousCellId, fullCellId, record.SpatialAuthorityVersion)); return exactOwnerValid() && record.FullCellId == fullCellId; } private void RemoveSpatialRemote(RuntimeEntityRecord record) { if (record.Key is not { } key) return; if (_spatialRemotes.TryGetValue( key, out IRuntimeRemoteMotion? remote) && (record.RemoteMotion is null || ReferenceEquals(remote, record.RemoteMotion) || !Entities.IsCurrent(record))) { _spatialRemotes.Remove(key); } } private void RemoveSpatialProjectile(RuntimeEntityRecord record) { if (record.Key is not { } key) return; if (_spatialProjectiles.TryGetValue( key, out IRuntimeProjectile? projectile) && (record.Projectile is null || ReferenceEquals(projectile, record.Projectile) || !Entities.IsCurrent(record))) { _spatialProjectiles.Remove(key); } } private void EnsureNotDisposed() => ObjectDisposedException.ThrowIf(_disposed, this); private void EnsureCurrent(RuntimeEntityRecord record) { if (!Entities.IsCurrent(record)) { throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} is not current."); } } private void ValidateAdmission(RuntimeCollisionAdmission admission) { EnsureNotDisposed(); ArgumentNullException.ThrowIfNull(admission); if (!ReferenceEquals(admission.Owner, this) || !_collisionAdmissions.TryGetValue( admission.LandblockId, out RuntimeCollisionAdmission? current) || !ReferenceEquals(current, admission) || !_collisionGenerations.TryGetValue( admission.LandblockId, out ulong generation) || generation != admission.Generation) { throw new InvalidOperationException( "Collision admission is stale or belongs to another Runtime."); } } private void InvalidateCollisionAdmission(uint landblockId) { ulong generation = _collisionGenerations.TryGetValue( landblockId, out ulong current) ? checked(current + 1UL) : 1UL; _collisionGenerations[landblockId] = generation; _collisionAdmissions.Remove(landblockId); } private static uint CanonicalLandblock(uint value) => (value & 0xFFFF0000u) | 0xFFFFu; private static bool IsFinite(System.Numerics.Vector3 value) => float.IsFinite(value.X) && float.IsFinite(value.Y) && float.IsFinite(value.Z); private static void SynchronizeBodyActiveState(RuntimeEntityRecord record) { if (record.PhysicsBody is not { } body) return; if (record.ObjectClock.IsActive) body.TransientState |= TransientStateFlags.Active; else body.TransientState &= ~TransientStateFlags.Active; } /// /// Retail CPhysicsObj::set_description (0x00514F40) installs the /// accepted CreateObject velocity and omega once, when the incarnation's /// CPhysicsObj is first acquired. Later SetState never replays them. /// private static void InitializeNewPhysicsBody( RuntimeEntityRecord record, PhysicsBody body) { body.State = record.FinalPhysicsState; if (record.Snapshot.Physics is not { } physics) return; if (physics.Velocity is { } velocity && IsFinite(velocity)) body.set_velocity(velocity); if (physics.AngularVelocity is { } omega && IsFinite(omega)) body.Omega = omega; } }