using System.Numerics; using AcDream.Core.Physics; using AcDream.Core.Physics.Motion; using AcDream.Runtime.Entities; using DatReaderWriter.Types; namespace AcDream.Runtime.Physics; internal readonly record struct RuntimePhysicsFrameSnapshot( Vector3 Position, Quaternion Orientation, uint FullCellId); internal sealed class RuntimeOrdinaryPhysicsCommit { internal required RuntimeOrdinaryPhysicsUpdater Owner { get; init; } internal required RuntimeEntityRecord Record { get; init; } internal required PhysicsBody Body { get; init; } internal required ulong ObjectClockEpoch { get; init; } internal required bool FrameChanged { get; init; } internal required Func? ExternalOwnerValid { get; init; } internal bool Completed { get; set; } internal RuntimePhysicsFrameSnapshot Snapshot { get; init; } } /// /// Presentation-free, body-backed branch of retail /// CPhysicsObj::UpdateObjectInternal (`0x005156B0`). App supplies the /// completed animation root frame and acknowledges the resulting projection; /// Runtime owns integration, transition, canonical-body state, and shadows. /// internal sealed class RuntimeOrdinaryPhysicsUpdater { private readonly RuntimePhysicsState _physics; internal RuntimeOrdinaryPhysicsUpdater(RuntimePhysicsState physics) { _physics = physics ?? throw new ArgumentNullException(nameof(physics)); } internal bool TryBegin( RuntimeEntityRecord record, Frame rootFrame, float objectScale, float quantum, float radius, float height, ulong objectClockEpoch, AnimationSequencer? sequencer, Action captureAnimationHooks, Func? externalOwnerValid, out RuntimeOrdinaryPhysicsCommit commit) { ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(rootFrame); ArgumentNullException.ThrowIfNull(captureAnimationHooks); if (record.PhysicsBody is not { } body || !IsCurrent( record, body, objectClockEpoch, externalOwnerValid)) { commit = null!; return false; } body.State = record.FinalPhysicsState; Vector3 priorPosition = body.Position; Quaternion priorOrientation = body.Orientation; bool previousContact = body.InContact; bool previousOnWalkable = body.OnWalkable; // UpdatePositionInternal 0x00512CA1: scale root translation only while // OnWalkable. Complete root orientation composes regardless. Vector3 candidatePosition = priorPosition; if (body.OnWalkable && rootFrame.Origin != Vector3.Zero) { candidatePosition += Vector3.Transform( rootFrame.Origin * objectScale, priorOrientation); } Quaternion candidateOrientation = priorOrientation; if (!rootFrame.Orientation.IsIdentity) { candidateOrientation = FrameOps.SetRotate( candidatePosition, priorOrientation, priorOrientation * rootFrame.Orientation); } body.SetFrameInCurrentCell(candidatePosition, candidateOrientation); body.calc_acceleration(); body.UpdatePhysicsInternal(quantum); body.SetFrameInCurrentCell(body.Position, body.Orientation); if (sequencer is not null) { uint localId = record.LocalEntityId ?? throw new InvalidOperationException( $"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} has no local identity."); captureAnimationHooks(localId, sequencer); } if (!IsCurrent( record, body, objectClockEpoch, externalOwnerValid)) { commit = null!; return false; } Vector3 integratedPosition = body.Position; uint sourceCellId = record.FullCellId; uint resolvedCellId = sourceCellId; bool frameChanged = integratedPosition != priorPosition || body.Orientation != priorOrientation; uint movingEntityId = record.LocalEntityId ?? 0u; if (integratedPosition != priorPosition && sourceCellId != 0 && radius >= 0.05f && _physics.Engine.LandblockCount > 0) { ResolveResult resolved = _physics.Engine.ResolveWithTransition( priorPosition, integratedPosition, sourceCellId, radius, height, stepUpHeight: 0.4f, stepDownHeight: 0.4f, isOnGround: previousOnWalkable, body: body, moverFlags: IsPlayerGuid(record.ServerGuid) ? ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide : ObjectInfoState.EdgeSlide, movingEntityId: movingEntityId); if (resolved.Ok) { resolvedCellId = resolved.CellId != 0 ? resolved.CellId : sourceCellId; body.CommitTransitionPosition( resolvedCellId, resolved.Position); PhysicsObjUpdate.CommitSetPositionTransition( body, resolved.InContact, resolved.OnWalkable, resolved.CollisionNormalValid, resolved.CollisionNormal, previousContact, previousOnWalkable); body.CachedVelocity = quantum > 0f ? (body.Position - priorPosition) / quantum : Vector3.Zero; } else { body.CachedVelocity = Vector3.Zero; } } else { body.CachedVelocity = Vector3.Zero; } if (!IsCurrent( record, body, objectClockEpoch, externalOwnerValid)) { commit = null!; return false; } commit = new RuntimeOrdinaryPhysicsCommit { Owner = this, Record = record, Body = body, ObjectClockEpoch = objectClockEpoch, FrameChanged = frameChanged, ExternalOwnerValid = externalOwnerValid, Snapshot = new RuntimePhysicsFrameSnapshot( body.Position, body.Orientation, resolvedCellId), }; return true; } internal bool Complete( RuntimeOrdinaryPhysicsCommit commit, int liveCenterX, int liveCenterY, Func acknowledgeProjection) { ArgumentNullException.ThrowIfNull(commit); ArgumentNullException.ThrowIfNull(acknowledgeProjection); if (!ReferenceEquals(commit.Owner, this)) { throw new InvalidOperationException( "An ordinary-physics commit belongs to another Runtime owner."); } if (commit.Completed) { throw new InvalidOperationException( "An ordinary-physics commit has already completed."); } commit.Completed = true; if (!IsCurrent( commit.Record, commit.Body, commit.ObjectClockEpoch, commit.ExternalOwnerValid) || !_physics.CommitOrdinaryCell( commit.Record, commit.Body, commit.ObjectClockEpoch, commit.Snapshot.FullCellId, commit.ExternalOwnerValid) || !acknowledgeProjection(commit.Snapshot) || !IsCurrent( commit.Record, commit.Body, commit.ObjectClockEpoch, commit.ExternalOwnerValid)) { return false; } if (commit.FrameChanged && commit.Record.FullCellId != 0) { ShadowPositionSynchronizer.Sync( _physics.Engine.ShadowObjects, commit.Record.LocalEntityId ?? 0u, commit.Body.Position, commit.Body.Orientation, commit.Record.FullCellId, liveCenterX, liveCenterY); } return IsCurrent( commit.Record, commit.Body, commit.ObjectClockEpoch, commit.ExternalOwnerValid); } private bool IsCurrent( RuntimeEntityRecord record, PhysicsBody body, ulong objectClockEpoch, Func? externalOwnerValid) => _physics.IsSpatialRoot(record) && record.ObjectClockEpoch == objectClockEpoch && ReferenceEquals(record.PhysicsBody, body) && record.RemoteMotion is null && (externalOwnerValid?.Invoke() ?? true); private static bool IsPlayerGuid(uint guid) => (guid & 0xFF000000u) == 0x50000000u; }