refactor(runtime): close simulation ownership

Move remote-motion construction, CreateObject vector initialization, final simulation-component retirement, and the combined J5 ownership ledger into Runtime. Delete App compatibility views and moved-state reconstruction while preserving the existing graphical projection and retail update order.
This commit is contained in:
Erik 2026-07-26 15:53:31 +02:00
parent c30a3efeb0
commit cdee7a4b49
57 changed files with 1013 additions and 410 deletions

View file

@ -41,7 +41,6 @@ internal sealed class LiveEntityNetworkUpdateController
private readonly ProjectileController _projectileController;
private readonly RemoteTeleportController _remoteTeleportController;
private readonly LiveEntityAnimationRuntimeView<LiveEntityAnimationState> _animatedEntities;
private readonly LiveEntityRemoteMotionRuntimeView<RemoteMotion> _remoteDeadReckon;
private readonly RemoteMovementObservationTracker _remoteMovementObservations;
private readonly RemotePhysicsUpdater _remotePhysicsUpdater;
private readonly RemoteInboundMotionDispatcher _remoteInboundMotion;
@ -53,7 +52,7 @@ internal sealed class LiveEntityNetworkUpdateController
private readonly LiveWorldOriginState _origin;
private readonly AcDream.App.Streaming.ILocalPlayerTeleportNetworkSink
_localPlayerTeleport;
private readonly ILocalPlayerControllerSource _playerControllerSource;
private readonly IRuntimeLocalPlayerControllerSource _playerControllerSource;
private readonly LocalPlayerOutboundController _localPlayerOutbound;
private readonly ILocalPlayerPhysicsHostSource _playerHostSource;
private readonly ILocalPlayerIdentitySource _playerIdentity;
@ -83,7 +82,6 @@ internal sealed class LiveEntityNetworkUpdateController
ProjectileController projectileController,
RemoteTeleportController remoteTeleportController,
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> animatedEntities,
LiveEntityRemoteMotionRuntimeView<RemoteMotion> remoteDeadReckon,
RemoteMovementObservationTracker remoteMovementObservations,
RemotePhysicsUpdater remotePhysicsUpdater,
RemoteInboundMotionDispatcher remoteInboundMotion,
@ -94,7 +92,7 @@ internal sealed class LiveEntityNetworkUpdateController
RuntimeCombatTargetState? combatTargetController,
LiveWorldOriginState origin,
AcDream.App.Streaming.ILocalPlayerTeleportNetworkSink localPlayerTeleport,
ILocalPlayerControllerSource playerControllerSource,
IRuntimeLocalPlayerControllerSource playerControllerSource,
LocalPlayerOutboundController localPlayerOutbound,
ILocalPlayerPhysicsHostSource playerHostSource,
ILocalPlayerIdentitySource playerIdentity,
@ -113,7 +111,6 @@ internal sealed class LiveEntityNetworkUpdateController
_projectileController = projectileController ?? throw new ArgumentNullException(nameof(projectileController));
_remoteTeleportController = remoteTeleportController ?? throw new ArgumentNullException(nameof(remoteTeleportController));
_animatedEntities = animatedEntities ?? throw new ArgumentNullException(nameof(animatedEntities));
_remoteDeadReckon = remoteDeadReckon ?? throw new ArgumentNullException(nameof(remoteDeadReckon));
_remoteMovementObservations = remoteMovementObservations ?? throw new ArgumentNullException(nameof(remoteMovementObservations));
_remotePhysicsUpdater = remotePhysicsUpdater ?? throw new ArgumentNullException(nameof(remotePhysicsUpdater));
_remoteInboundMotion = remoteInboundMotion ?? throw new ArgumentNullException(nameof(remoteInboundMotion));
@ -151,7 +148,10 @@ internal sealed class LiveEntityNetworkUpdateController
uint localEntityId,
Func<bool> isCurrent)
{
_remoteDeadReckon.TryGetValue(serverGuid, out RemoteMotion? remote);
_liveEntities.TryGetRemoteMotionRuntime(
serverGuid,
out IRuntimeRemoteMotion? remoteRuntime);
RemoteMotion? remote = remoteRuntime as RemoteMotion;
EntityPhysicsHost? host =
_liveEntities.TryGetPhysicsHost(serverGuid, out var registered)
? registered as EntityPhysicsHost
@ -570,7 +570,10 @@ internal sealed class LiveEntityNetworkUpdateController
_remoteMovementObservations[motionKey] =
(prev.Pos, refreshedTime);
}
if (_remoteDeadReckon.TryGetValue(update.Guid, out var dr))
if (_liveEntities.TryGetRemoteMotionRuntime(
update.Guid,
out IRuntimeRemoteMotion? remoteRuntime)
&& remoteRuntime is RemoteMotion dr)
dr.LastServerPosTime = (refreshedTime - System.DateTime.UnixEpoch).TotalSeconds;
}
@ -635,12 +638,15 @@ internal sealed class LiveEntityNetworkUpdateController
if (!IsCurrentOwner())
return default;
if (!_remoteDeadReckon.TryGetValue(update.Guid, out var remote))
if (!_liveEntities.TryGetRemoteMotionRuntime(
update.Guid,
out IRuntimeRemoteMotion? remoteRuntime)
|| remoteRuntime is not RemoteMotion remote)
{
remote = CreateRemoteMotion(update.Guid);
remote = _liveEntities.GetOrCreateRemoteMotionRuntime(
update.Guid);
remote.Body.Orientation = entity.Rotation;
remote.Body.Position = entity.Position;
_remoteDeadReckon[update.Guid] = remote;
}
if (!IsCurrentOwner(remote))
return default;
@ -719,43 +725,6 @@ internal sealed class LiveEntityNetworkUpdateController
&& runtime.IsCurrentSpatialRemoteMotion(record, remote);
}
/// <summary>
/// Creates the optional MovementManager companion for one live object.
/// A retained projectile or static-animation owner contributes the
/// already-owned CPhysicsObj body; an ordinary remote gets local storage.
/// </summary>
private RemoteMotion CreateRemoteMotion(uint serverGuid)
{
LiveEntityRecord? record = null;
if (_liveEntities is { } liveEntities
&& liveEntities.TryGetRecord(serverGuid, out var currentRecord))
{
record = currentRecord;
}
RemoteMotion remote;
if (record?.PhysicsBody is { } canonicalBody)
{
// Retail has one CPhysicsObj. A projectile or Physics-Static
// animation workset can create it before the MovementManager;
// adopt its current frame/vectors without replaying CreateObject.
remote = new RemoteMotion(canonicalBody);
}
else
{
remote = new RemoteMotion();
if (record is not null)
AcDream.App.Physics.RemotePhysicsBodyInitializer.Initialize(
remote.Body,
record);
}
RemoteMotion incarnation = remote;
remote.Movement.ActivatePhysicsObject = () =>
_liveEntities?.TryActivateOrdinaryObject(serverGuid, incarnation);
return remote;
}
/// <summary>
/// K-fix9 (2026-04-26): handle 0xF74E VectorUpdate from remote jumps.
/// The payload seeds the world-space launch velocity and angular velocity.
@ -822,7 +791,13 @@ internal sealed class LiveEntityNetworkUpdateController
if (!_liveEntities.ContainsWorldEntity(update.Guid)) return;
if (update.Guid == _playerServerGuid) return; // local jump uses our own physics
if (!_remoteDeadReckon.TryGetValue(update.Guid, out var rm)) return;
if (!_liveEntities.TryGetRemoteMotionRuntime(
update.Guid,
out IRuntimeRemoteMotion? remoteRuntime)
|| remoteRuntime is not RemoteMotion rm)
{
return;
}
LiveEntityRecord remoteRecord = acceptedVectorRecord;
// World-space velocity. Apply directly to the body — the per-tick
@ -1249,10 +1224,14 @@ internal sealed class LiveEntityNetworkUpdateController
// small. When HasVelocity is set, we also seed PhysicsBody
// velocity (matches retail's set_velocity call in the same
// dispatcher).
if (!_remoteDeadReckon.TryGetValue(update.Guid, out var rmState))
if (!_liveEntities.TryGetRemoteMotionRuntime(
update.Guid,
out IRuntimeRemoteMotion? remoteRuntime)
|| remoteRuntime is not RemoteMotion rmState)
{
rmState = CreateRemoteMotion(update.Guid);
_remoteDeadReckon[update.Guid] = rmState;
rmState =
_liveEntities.GetOrCreateRemoteMotionRuntime(
update.Guid);
// Hard-snap orientation on first spawn so the per-tick
// slerp doesn't visibly rotate from Identity to truth.
rmState.Body.Orientation = rot;

View file

@ -14,7 +14,7 @@ internal static class LiveEntityShadowPublisher
LiveEntityRuntime runtime,
LiveEntityRecord record,
WorldEntity entity,
ILiveEntityRemoteMotionRuntime remote,
IRuntimeRemoteMotion remote,
ulong positionAuthorityVersion,
Action publish)
{
@ -47,7 +47,7 @@ internal static class LiveEntityShadowPublisher
LiveEntityRuntime runtime,
LiveEntityRecord record,
WorldEntity entity,
ILiveEntityRemoteMotionRuntime remote,
IRuntimeRemoteMotion remote,
ulong positionAuthorityVersion) =>
(record.FinalPhysicsState & PhysicsStateFlags.Hidden) == 0
&& ReferenceEquals(record.WorldEntity, entity)

View file

@ -246,7 +246,6 @@ internal sealed class ProjectileController
{
var created = new PhysicsBody
{
State = record.FinalPhysicsState,
Friction = NormalizeFriction(
physics.Friction ?? record.Snapshot.Friction),
Elasticity = NormalizeElasticity(
@ -254,8 +253,6 @@ internal sealed class ProjectileController
Orientation = entity.Rotation,
LastUpdateTime = currentTime,
};
SetVelocity(created, velocity, currentTime);
created.Omega = omega;
created.SnapToCell(
wirePosition.LandblockId,
entity.Position,
@ -919,22 +916,6 @@ internal sealed class ProjectileController
private static void Deactivate(PhysicsBody body) =>
body.TransientState &= ~TransientStateFlags.Active;
private static void SetVelocity(
PhysicsBody body,
Vector3 velocity,
double currentTime)
{
bool wasActive = (body.TransientState & TransientStateFlags.Active) != 0;
body.set_velocity(velocity);
if ((body.State & PhysicsStateFlags.Static) != 0)
{
Deactivate(body);
return;
}
if (!wasActive && (body.State & PhysicsStateFlags.Static) == 0)
body.LastUpdateTime = currentTime;
}
private static float NormalizeFriction(float? value) =>
value is >= 0f and <= 1f && float.IsFinite(value.Value)
? value.Value

View file

@ -1,39 +0,0 @@
using System.Numerics;
using AcDream.App.World;
using AcDream.Core.Physics;
namespace AcDream.App.Physics;
/// <summary>
/// Applies CreateObject physics vectors when the ordinary remote-motion body
/// is first constructed. This keeps initialization at object/body creation;
/// a later SetState classification change never replays retained vectors.
/// </summary>
/// <remarks>
/// Retail <c>CPhysicsObj::set_description</c> <c>0x00514F40</c> installs the
/// unpacked velocity/omega during object initialization. Retail
/// <c>SmartBox::DoSetState</c> <c>0x004520D0</c> only calls
/// <c>CPhysicsObj::set_state</c> and therefore preserves the current vectors.
/// </remarks>
internal static class RemotePhysicsBodyInitializer
{
internal static void Initialize(PhysicsBody body, LiveEntityRecord record)
{
ArgumentNullException.ThrowIfNull(body);
ArgumentNullException.ThrowIfNull(record);
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;
}
private static bool IsFinite(Vector3 value) =>
float.IsFinite(value.X)
&& float.IsFinite(value.Y)
&& float.IsFinite(value.Z);
}

View file

@ -73,7 +73,7 @@ internal sealed class RemoteTeleportController : IDisposable
LiveEntityRecord Record,
ulong PositionAuthorityVersion,
ulong VelocityAuthorityVersion,
ILiveEntityRemotePlacementRuntime Remote,
IRuntimeRemotePlacement Remote,
PhysicsBody Body,
WorldEntity Entity,
Vector3 RequestedWorldPosition,
@ -110,7 +110,7 @@ internal sealed class RemoteTeleportController : IDisposable
/// revalidation once admitted.
/// </summary>
internal Result TryApply(
ILiveEntityRemotePlacementRuntime remote,
IRuntimeRemotePlacement remote,
WorldEntity entity,
Vector3 requestedWorldPosition,
uint requestedCellId,
@ -147,7 +147,7 @@ internal sealed class RemoteTeleportController : IDisposable
LiveEntityRecord expectedRecord,
ulong expectedPositionAuthorityVersion,
ulong expectedVelocityAuthorityVersion,
ILiveEntityRemotePlacementRuntime remote,
IRuntimeRemotePlacement remote,
WorldEntity entity,
Vector3 requestedWorldPosition,
uint requestedCellId,
@ -422,7 +422,7 @@ internal sealed class RemoteTeleportController : IDisposable
_pending.Remove(key);
return;
}
if (record.RemoteMotionRuntime is not ILiveEntityRemotePlacementRuntime currentRemote
if (record.RemoteMotionRuntime is not IRuntimeRemotePlacement currentRemote
|| record.PhysicsBody is null
|| !ReferenceEquals(record.PhysicsBody, pending.Body)
|| !ReferenceEquals(currentRemote.Body, record.PhysicsBody))
@ -467,7 +467,7 @@ internal sealed class RemoteTeleportController : IDisposable
}
private static RollbackPlacement CaptureRollback(
ILiveEntityRemotePlacementRuntime remote,
IRuntimeRemotePlacement remote,
PhysicsBody body)
{
return new RollbackPlacement(

View file

@ -13,7 +13,7 @@ namespace AcDream.App.Physics;
internal static class RemoteTeleportPlacement
{
internal static bool Apply(
ILiveEntityRemotePlacementRuntime remote,
IRuntimeRemotePlacement remote,
PhysicsBody body,
ResolveResult placement,
Vector3 cellLocalPosition,