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

@ -122,7 +122,7 @@ internal sealed class LiveCombatAttackOperations
private readonly CombatState _combat;
private readonly ICombatAttackTargetSource _targets;
private readonly ICombatGameplaySettingsSource _settings;
private readonly ILocalPlayerControllerSource _player;
private readonly IRuntimeLocalPlayerControllerSource _player;
private readonly LocalPlayerOutboundController _outbound;
private readonly ILiveInWorldSource _inWorld;
private readonly ILiveWorldSessionSource _session;
@ -132,7 +132,7 @@ internal sealed class LiveCombatAttackOperations
CombatState combat,
ICombatAttackTargetSource targets,
ICombatGameplaySettingsSource settings,
ILocalPlayerControllerSource player,
IRuntimeLocalPlayerControllerSource player,
LocalPlayerOutboundController outbound,
ILiveInWorldSource inWorld,
ILiveWorldSessionSource session,

View file

@ -33,7 +33,7 @@ internal sealed record FrameRootDependencies(
LocalPlayerModeState PlayerMode,
LocalPlayerIdentityState PlayerIdentity,
ChaseCameraInputState ChaseCameraInput,
LocalPlayerControllerSlot PlayerController,
RuntimeLocalPlayerMovementState PlayerController,
LiveWorldOriginState WorldOrigin,
ParticleVisibilityController ParticleVisibility,
EntityEffectPoseRegistry EffectPoses,

View file

@ -56,7 +56,7 @@ internal sealed record InteractionRetainedUiDependencies(
BufferedUiRegistry? UiRegistry,
LiveCombatModeCommandSlot CombatModeCommands,
ILocalPlayerIdentitySource PlayerIdentity,
ILocalPlayerControllerSource PlayerController,
IRuntimeLocalPlayerControllerSource PlayerController,
ILocalPlayerModeSource PlayerMode,
Func<DeferredSelectionViewPlaneSource, SelectionCameraSource>
SelectionCameraFactory,

View file

@ -60,7 +60,7 @@ internal sealed record LivePresentationDependencies(
CellVisibility CellVisibility,
LiveWorldOriginState WorldOrigin,
LocalPlayerIdentityState PlayerIdentity,
LocalPlayerControllerSlot PlayerController,
RuntimeLocalPlayerMovementState PlayerController,
PointerPositionState PointerPosition,
PlayerApproachCompletionState PlayerApproachCompletions,
GameRenderResourceLifetime RenderResourceLifetime,

View file

@ -60,7 +60,7 @@ internal sealed record SessionPlayerDependencies(
RetailInboundEventDispatcher InboundEntityEvents,
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
LocalPlayerIdentityState PlayerIdentity,
LocalPlayerControllerSlot PlayerController,
RuntimeLocalPlayerMovementState PlayerController,
LocalPlayerPhysicsHostSlot PlayerHost,
LocalPlayerModeState PlayerMode,
ChaseCameraInputState ChaseCameraInput,
@ -507,7 +507,6 @@ internal sealed class SessionPlayerCompositionPhase
live.ProjectileController,
live.RemoteTeleport,
d.AnimatedEntities,
new LiveEntityRemoteMotionRuntimeView<RemoteMotion>(d.RuntimeSlot),
d.RemoteMovementObservations,
d.RemotePhysicsUpdater,
d.RemoteInboundMotion,
@ -874,7 +873,6 @@ internal sealed class SessionPlayerCompositionPhase
sessionHost,
liveSessionCommands,
d.PlayerIdentity,
live.LiveEntities,
d.EntityObjects,
d.Inventory,
d.Character,

View file

@ -162,7 +162,7 @@ internal interface INearbyWorldDiagnosticSource
internal sealed class RuntimeNearbyWorldDiagnosticSource : INearbyWorldDiagnosticSource
{
private readonly ILocalPlayerModeSource _mode;
private readonly ILocalPlayerControllerSource _player;
private readonly IRuntimeLocalPlayerControllerSource _player;
private readonly CameraController _camera;
private readonly LiveWorldOriginState _origin;
private readonly GpuWorldState _world;
@ -170,7 +170,7 @@ internal sealed class RuntimeNearbyWorldDiagnosticSource : INearbyWorldDiagnosti
public RuntimeNearbyWorldDiagnosticSource(
ILocalPlayerModeSource mode,
ILocalPlayerControllerSource player,
IRuntimeLocalPlayerControllerSource player,
CameraController camera,
LiveWorldOriginState origin,
GpuWorldState world,

View file

@ -1,18 +1,4 @@
global using AcDream.Runtime.Gameplay;
global using AcDream.Runtime.Physics;
global using ILiveEntityRemoteMotionRuntime =
AcDream.Runtime.Physics.IRuntimeRemoteMotion;
global using ILiveEntityPhysicsHostConsumer =
AcDream.Runtime.Physics.IRuntimePhysicsHostConsumer;
global using ILiveEntityCanonicalRuntimeConsumer =
AcDream.Runtime.Physics.IRuntimeCanonicalPhysicsConsumer;
global using ILiveEntityCanonicalCellConsumer =
AcDream.Runtime.Physics.IRuntimeCanonicalCellConsumer;
global using ILiveEntityRemotePlacementRuntime =
AcDream.Runtime.Physics.IRuntimeRemotePlacement;
global using LocalPlayerControllerSlot =
AcDream.Runtime.Gameplay.RuntimeLocalPlayerMovementState;
global using ILocalPlayerControllerSource =
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerControllerSource;
global using ILocalPlayerMotionSource =
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerMotionSource;

View file

@ -35,7 +35,7 @@ internal sealed class LiveLocalPlayerFrameRuntime : ILocalPlayerFrameRuntime
{
private readonly CameraController _camera;
private readonly ILocalPlayerModeSource _mode;
private readonly ILocalPlayerControllerSource _controller;
private readonly IRuntimeLocalPlayerControllerSource _controller;
private readonly IChaseCameraSource _chase;
private readonly DispatcherMovementInputSource _input;
private readonly IInputCaptureSource _capture;
@ -49,7 +49,7 @@ internal sealed class LiveLocalPlayerFrameRuntime : ILocalPlayerFrameRuntime
public LiveLocalPlayerFrameRuntime(
CameraController camera,
ILocalPlayerModeSource mode,
ILocalPlayerControllerSource controller,
IRuntimeLocalPlayerControllerSource controller,
IChaseCameraSource chase,
DispatcherMovementInputSource input,
IInputCaptureSource capture,

View file

@ -96,7 +96,7 @@ internal sealed class MouseLookController : IMouseLookInputFrameController
private readonly IMouseSource _mouseSource;
private readonly IPointerPositionSource _pointer;
private readonly ILocalPlayerModeSource _playerMode;
private readonly ILocalPlayerControllerSource _playerController;
private readonly IRuntimeLocalPlayerControllerSource _playerController;
private readonly CameraController _camera;
private readonly ChaseCameraInputState _chase;
private readonly IMovementInputSource _movementInput;
@ -111,7 +111,7 @@ internal sealed class MouseLookController : IMouseLookInputFrameController
IMouseSource mouseSource,
IPointerPositionSource pointer,
ILocalPlayerModeSource playerMode,
ILocalPlayerControllerSource playerController,
IRuntimeLocalPlayerControllerSource playerController,
CameraController camera,
ChaseCameraInputState chase,
IMovementInputSource movementInput,

View file

@ -7,7 +7,7 @@ internal sealed class MovementTruthDiagnosticController
: IMovementTruthDiagnosticSink
{
private readonly bool _enabled;
private readonly ILocalPlayerControllerSource _player;
private readonly IRuntimeLocalPlayerControllerSource _player;
private readonly ILocalPlayerIdentitySource _identity;
private MovementTruthOutbound? _lastOutbound;
@ -24,7 +24,7 @@ internal sealed class MovementTruthDiagnosticController
public MovementTruthDiagnosticController(
bool enabled,
ILocalPlayerControllerSource player,
IRuntimeLocalPlayerControllerSource player,
ILocalPlayerIdentitySource identity)
{
_enabled = enabled;

View file

@ -23,7 +23,7 @@ internal sealed class PlayerModeController :
IDevToolsPlayerModeTarget
{
private readonly LocalPlayerModeState _mode;
private readonly LocalPlayerControllerSlot _controllerSlot;
private readonly RuntimeLocalPlayerMovementState _controllerSlot;
private readonly LocalPlayerPhysicsHostSlot _hostSlot;
private readonly ChaseCameraInputState _chase;
private readonly CameraController _camera;
@ -49,7 +49,7 @@ internal sealed class PlayerModeController :
public PlayerModeController(
LocalPlayerModeState mode,
LocalPlayerControllerSlot controllerSlot,
RuntimeLocalPlayerMovementState controllerSlot,
LocalPlayerPhysicsHostSlot hostSlot,
ChaseCameraInputState chase,
CameraController camera,

View file

@ -35,7 +35,7 @@ namespace AcDream.App.Net;
internal sealed record LiveSessionPlayerRuntime(
LocalPlayerIdentityState Identity,
LocalPlayerControllerSlot Controller,
RuntimeLocalPlayerMovementState Controller,
LiveWorldOriginState WorldOrigin);
internal sealed record LiveSessionDomainRuntime(

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,

View file

@ -1005,7 +1005,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
incarnation =>
{
var created = new PhysicsBody { Orientation = entity.Rotation };
RemotePhysicsBodyInitializer.Initialize(created, incarnation);
created.SnapToCell(
staticPosition.LandblockId,
entity.Position,

View file

@ -257,7 +257,7 @@ internal interface IDevToolsRuntimeFacts
internal sealed class DevToolsRuntimeFacts : IDevToolsRuntimeFacts
{
private readonly ILocalPlayerModeSource _mode;
private readonly ILocalPlayerControllerSource _player;
private readonly IRuntimeLocalPlayerControllerSource _player;
private readonly CameraController _camera;
private readonly ICanonicalWorldEntityCountSource _world;
private readonly LiveEntityAnimationRuntimeView<LiveEntityAnimationState> _animations;
@ -273,7 +273,7 @@ internal sealed class DevToolsRuntimeFacts : IDevToolsRuntimeFacts
public DevToolsRuntimeFacts(
ILocalPlayerModeSource mode,
ILocalPlayerControllerSource player,
IRuntimeLocalPlayerControllerSource player,
CameraController camera,
ICanonicalWorldEntityCountSource world,
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> animations,

View file

@ -218,7 +218,7 @@ internal sealed class RuntimeWorldFrameRootSource : IWorldFrameRootSource
private readonly CellVisibility _cells;
private readonly ILocalPlayerModeSource _mode;
private readonly IChaseCameraSource _chase;
private readonly ILocalPlayerControllerSource _player;
private readonly IRuntimeLocalPlayerControllerSource _player;
private readonly LiveWorldOriginState _origin;
public RuntimeWorldFrameRootSource(
@ -226,7 +226,7 @@ internal sealed class RuntimeWorldFrameRootSource : IWorldFrameRootSource
CellVisibility cells,
ILocalPlayerModeSource mode,
IChaseCameraSource chase,
ILocalPlayerControllerSource player,
IRuntimeLocalPlayerControllerSource player,
LiveWorldOriginState origin)
{
_physics = physics ?? throw new ArgumentNullException(nameof(physics));

View file

@ -60,7 +60,7 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
private readonly DebugLineRenderer? _lines;
private readonly PhysicsEngine _physics;
private readonly ILocalPlayerModeSource _mode;
private readonly ILocalPlayerControllerSource _player;
private readonly IRuntimeLocalPlayerControllerSource _player;
private readonly DebugVmRenderFactsPublisher _debugVm;
private readonly bool _debugVmConsumerActive;
private int _debugDrawLogCount;
@ -72,7 +72,7 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
DebugLineRenderer? lines,
PhysicsEngine physics,
ILocalPlayerModeSource mode,
ILocalPlayerControllerSource player,
IRuntimeLocalPlayerControllerSource player,
DebugVmRenderFactsPublisher debugVm,
bool debugVmConsumerActive)
{

View file

@ -55,12 +55,12 @@ internal interface IWorldScenePViewDiagnosticSource
internal sealed class RuntimeWorldScenePViewDiagnosticSource :
IWorldScenePViewDiagnosticSource
{
private readonly ILocalPlayerControllerSource _player;
private readonly IRuntimeLocalPlayerControllerSource _player;
private readonly PhysicsEngine _physics;
private readonly CellVisibility _visibility;
public RuntimeWorldScenePViewDiagnosticSource(
ILocalPlayerControllerSource player,
IRuntimeLocalPlayerControllerSource player,
PhysicsEngine physics,
CellVisibility visibility)
{

View file

@ -32,7 +32,6 @@ internal sealed class CurrentGameRuntimeAdapter
LiveSessionHost sessionHost,
ICommandBus commands,
LocalPlayerIdentityState playerIdentity,
LiveEntityRuntime entities,
RuntimeEntityObjectLifetime entityObjects,
RuntimeInventoryState inventory,
RuntimeCharacterState character,
@ -46,7 +45,6 @@ internal sealed class CurrentGameRuntimeAdapter
_view = new CurrentGameRuntimeViewAdapter(
session,
playerIdentity,
entities,
entityObjects,
inventory,
character,

View file

@ -1,7 +1,6 @@
using AcDream.App.Input;
using AcDream.App.Net;
using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.Core.Chat;
using AcDream.Core.Items;
using AcDream.Runtime;
@ -19,8 +18,6 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
{
private readonly LiveSessionController _session;
private readonly LocalPlayerIdentityState _playerIdentity;
private readonly LiveEntityRuntime _entities;
private readonly ClientObjectTable _objects;
private readonly IGameRuntimeClock _clock;
private readonly IRuntimeEntityView _entityView;
private readonly IRuntimeInventoryView _inventoryView;
@ -36,7 +33,6 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
public CurrentGameRuntimeViewAdapter(
LiveSessionController session,
LocalPlayerIdentityState playerIdentity,
LiveEntityRuntime entities,
RuntimeEntityObjectLifetime entityObjects,
RuntimeInventoryState inventory,
RuntimeCharacterState character,
@ -49,9 +45,7 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
_session = session ?? throw new ArgumentNullException(nameof(session));
_playerIdentity = playerIdentity
?? throw new ArgumentNullException(nameof(playerIdentity));
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
ArgumentNullException.ThrowIfNull(entityObjects);
_objects = entityObjects.Objects;
ArgumentNullException.ThrowIfNull(communication);
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
@ -115,10 +109,10 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
Generation,
Lifecycle.State,
_clock.FrameNumber,
_entities.Count,
_entities.MaterializedCount,
_objects.ObjectCount,
_objects.ContainerCount,
_entityView.Count,
_entityView.MaterializedCount,
_inventoryView.ObjectCount,
_inventoryView.ContainerCount,
_inventoryStateView.Snapshot,
_characterView.Snapshot,
_socialView.Snapshot,

View file

@ -179,7 +179,7 @@ internal sealed class LocalPlayerTeleportPlacement : ILocalPlayerTeleportPlaceme
private readonly PhysicsEngine _physics;
private readonly LiveEntityRuntime _liveEntities;
private readonly ILocalPlayerIdentitySource _identity;
private readonly ILocalPlayerControllerSource _controller;
private readonly IRuntimeLocalPlayerControllerSource _controller;
private readonly ILocalPlayerPhysicsHostSource _host;
private readonly ChaseCameraInputState _cameras;
private readonly LiveWorldOriginState _origin;
@ -189,7 +189,7 @@ internal sealed class LocalPlayerTeleportPlacement : ILocalPlayerTeleportPlaceme
PhysicsEngine physics,
LiveEntityRuntime liveEntities,
ILocalPlayerIdentitySource identity,
ILocalPlayerControllerSource controller,
IRuntimeLocalPlayerControllerSource controller,
ILocalPlayerPhysicsHostSource host,
ChaseCameraInputState cameras,
LiveWorldOriginState origin,

View file

@ -106,7 +106,7 @@ internal sealed class StreamingFrameController : IStreamingFramePhase
private readonly bool _liveModeEnabled;
private readonly ILocalPlayerModeSource _playerMode;
private readonly ILocalPlayerControllerSource _playerController;
private readonly IRuntimeLocalPlayerControllerSource _playerController;
private readonly ILiveInWorldSource _session;
private readonly LiveWorldOriginState _origin;
private readonly ILocalPlayerLandblockSource _playerLandblock;
@ -119,7 +119,7 @@ internal sealed class StreamingFrameController : IStreamingFramePhase
public StreamingFrameController(
bool liveModeEnabled,
ILocalPlayerModeSource playerMode,
ILocalPlayerControllerSource playerController,
IRuntimeLocalPlayerControllerSource playerController,
ILiveInWorldSource session,
LiveWorldOriginState origin,
ILocalPlayerLandblockSource playerLandblock,

View file

@ -223,33 +223,27 @@ public sealed class LiveEntityRecord
public PhysicsBody? PhysicsBody
{
get => Canonical.PhysicsBody;
internal set => _directory.SetPhysicsBody(Canonical, value);
}
internal bool PhysicsBodyAcquisitionInProgress
{
get => Canonical.PhysicsBodyAcquisitionInProgress;
set => _directory.SetPhysicsBodyAcquisitionInProgress(Canonical, value);
}
public ILiveEntityAnimationRuntime? AnimationRuntime { get; internal set; }
public ILiveEntityRemoteMotionRuntime? RemoteMotionRuntime
public IRuntimeRemoteMotion? RemoteMotionRuntime
{
get => Canonical.RemoteMotion;
internal set => _directory.SetRemoteMotion(Canonical, value);
}
internal bool RemoteMotionBindingInProgress
{
get => Canonical.RemoteMotionBindingInProgress;
set => _directory.SetRemoteMotionBindingInProgress(Canonical, value);
}
public AcDream.Core.Physics.Motion.IPhysicsObjHost? PhysicsHost
{
get => Canonical.PhysicsHost;
internal set => _directory.SetPhysicsHost(Canonical, value);
}
internal bool RequiresRemotePlacementRuntime
{
get => Canonical.RequiresRemotePlacementRuntime;
set => _directory.SetRequiresRemotePlacementRuntime(Canonical, value);
}
public IRuntimeProjectile? ProjectileRuntime => Canonical.Projectile;
public ILiveEntityEffectProfile? EffectProfile { get; internal set; }
@ -1389,7 +1383,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
ProjectionIsCurrent);
}
public void SetRemoteMotionRuntime(uint serverGuid, ILiveEntityRemoteMotionRuntime runtime)
internal void SetRemoteMotionRuntime(uint serverGuid, IRuntimeRemoteMotion runtime)
{
ArgumentNullException.ThrowIfNull(runtime);
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record))
@ -1442,7 +1436,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
public bool TryGetRemoteMotionRuntime(
uint serverGuid,
out ILiveEntityRemoteMotionRuntime runtime)
out IRuntimeRemoteMotion runtime)
{
if (_projections.TryGetCurrent(
serverGuid,
@ -1457,7 +1451,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
return false;
}
public bool ClearRemoteMotionRuntime(uint serverGuid)
internal bool ClearRemoteMotionRuntime(uint serverGuid)
{
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|| record.RemoteMotionRuntime is null)
@ -1468,6 +1462,26 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
return true;
}
internal RemoteMotion GetOrCreateRemoteMotionRuntime(uint serverGuid)
{
if (!_projections.TryGetCurrent(
serverGuid,
out LiveEntityRecord? record))
{
throw new InvalidOperationException(
$"Cannot acquire remote motion before live entity 0x{serverGuid:X8} exists.");
}
bool ProjectionIsCurrent() =>
_projections.TryGetCurrent(
serverGuid,
out LiveEntityRecord? current)
&& ReferenceEquals(current, record);
return _physics.GetOrCreateRemoteMotion(
record.Canonical,
ProjectionIsCurrent);
}
internal IRuntimeProjectile BindProjectileRuntime(
uint serverGuid,
PhysicsBody body,
@ -1874,27 +1888,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
destination.Add(key.LocalEntityId);
}
/// <summary>
/// Host side of retail <c>CPhysicsObj::set_active(1)</c> for a movement
/// manager owned by this exact incarnation. Static is a required no-op.
/// </summary>
internal bool TryActivateOrdinaryObject(
uint serverGuid,
ILiveEntityRemoteMotionRuntime runtime)
{
ArgumentNullException.ThrowIfNull(runtime);
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|| !ReferenceEquals(record.RemoteMotionRuntime, runtime)
|| (record.FinalPhysicsState & PhysicsStateFlags.Static) != 0)
{
return false;
}
record.ObjectClock.Activate();
runtime.Body.TransientState |= TransientStateFlags.Active;
return true;
}
/// <summary>
/// Commits retail <c>CPhysicsObj::set_velocity</c> (0x005113F0) to the
/// canonical body for this exact incarnation. The body and the retained
@ -1941,41 +1934,19 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
{
ArgumentNullException.ThrowIfNull(record);
ArgumentNullException.ThrowIfNull(body);
if (!IsFinite(velocity)
|| (angularVelocity is { } omega && !IsFinite(omega))
|| !double.IsFinite(currentTime)
|| !_projections.TryGetCurrent(
bool ProjectionIsCurrent() =>
_projections.TryGetCurrent(
record.ServerGuid,
out LiveEntityRecord? current)
|| !ReferenceEquals(current, record)
|| !ReferenceEquals(current.PhysicsBody, body))
{
return false;
}
bool wasBodyActive =
(body.TransientState & TransientStateFlags.Active) != 0;
body.set_velocity(velocity);
if ((current.FinalPhysicsState & PhysicsStateFlags.Static) != 0)
{
// PhysicsBody models set_velocity's leaf write and therefore sets
// Active unconditionally. Retail CPhysicsObj::set_active(1) is a
// no-op for Static, so restore the exact prior bit here.
if (!wasBodyActive)
body.TransientState &= ~TransientStateFlags.Active;
}
else
{
bool clockReactivated = current.ObjectClock.Activate();
// PhysicsBody.LastUpdateTime remains only the compatibility clock
// for older absolute-time helpers; the record clock is canonical.
if (clockReactivated || !wasBodyActive)
body.LastUpdateTime = currentTime;
}
if (angularVelocity is { } acceptedOmega)
body.Omega = acceptedOmega;
return true;
&& ReferenceEquals(current, record)
&& ReferenceEquals(current.PhysicsBody, body);
return _physics.TryCommitAuthoritativeVector(
record.Canonical,
body,
velocity,
angularVelocity,
currentTime,
ProjectionIsCurrent);
}
/// <summary>
@ -2007,7 +1978,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
internal bool IsCurrentSpatialRemoteMotion(
LiveEntityRecord record,
ILiveEntityRemoteMotionRuntime runtime) =>
IRuntimeRemoteMotion runtime) =>
IsCurrentRecord(record)
&& _physics.IsSpatialRemote(record.Canonical, runtime)
&& HasSpatialRuntimeProjection(record);
@ -2546,11 +2517,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
body.TransientState &= ~TransientStateFlags.Active;
}
private static bool IsFinite(Vector3 value) =>
float.IsFinite(value.X)
&& float.IsFinite(value.Y)
&& float.IsFinite(value.Z);
private void PublishProjectionVisibilityChanged(LiveEntityRecord record, bool visible)
{
Delegate[] subscribers = ProjectionVisibilityChanged?.GetInvocationList()
@ -2728,16 +2694,11 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
}
if (record.WorldEntity is not null)
{
_ = RequireProjectionKey(record);
_directory.ReleaseLocalId(record.Canonical);
}
_entityObjects.CompleteProjectionRetirement(
record.Canonical);
record.AnimationRuntime = null;
record.RemoteMotionRuntime = null;
record.PhysicsHost = null;
record.RequiresRemotePlacementRuntime = false;
record.PhysicsBody = null;
record.EffectProfile = null;
record.IsSpatiallyProjected = false;
record.IsSpatiallyVisible = false;

View file

@ -133,36 +133,3 @@ public sealed class LiveEntityAnimationRuntimeView<TAnimation>
throw new NotSupportedException();
}
}
/// <summary>Storage-free typed view over remote-motion components.</summary>
public sealed class LiveEntityRemoteMotionRuntimeView<TRemoteMotion>
where TRemoteMotion : class, ILiveEntityRemoteMotionRuntime
{
private readonly ILiveEntityRuntimeSource _runtime;
internal LiveEntityRemoteMotionRuntimeView(ILiveEntityRuntimeSource runtime) =>
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
public TRemoteMotion this[uint serverGuid]
{
set => (_runtime.Current
?? throw new InvalidOperationException("Live entity runtime is not initialized."))
.SetRemoteMotionRuntime(serverGuid, value);
}
public bool TryGetValue(uint serverGuid, out TRemoteMotion motion)
{
if (_runtime.Current?.TryGetRemoteMotionRuntime(serverGuid, out var found) == true
&& found is TRemoteMotion typed)
{
motion = typed;
return true;
}
motion = null!;
return false;
}
public bool Remove(uint serverGuid) =>
_runtime.Current?.ClearRemoteMotionRuntime(serverGuid) == true;
}

View file

@ -168,6 +168,27 @@ public sealed class PhysicsEngine
DataCache?.CellGraph.RemoveLandblock(landblockId);
}
/// <summary>
/// Releases every collision landblock and retained shadow registration
/// owned by this engine. Runtime calls this only at terminal disposal;
/// ordinary streaming still uses the typed per-landblock retirement path.
/// </summary>
public void Clear()
{
if (_landblocks.Count != 0)
{
var landblocks = new uint[_landblocks.Count];
_landblocks.Keys.CopyTo(landblocks, 0);
foreach (uint landblockId in landblocks)
RemoveLandblock(landblockId);
}
// Dynamic live registrations can remain intentionally suspended with
// no cell rows after their last landblock retires. Terminal engine
// ownership must retire those logical registrations as well.
ShadowObjects.Clear();
}
/// <summary>
/// Retire a Near landblock's indoor-cell and static-object collision layer
/// while preserving its terrain surface and world offset for Far-tier use.

View file

@ -795,6 +795,21 @@ public sealed class ShadowObjectRegistry
/// <summary>Suspended logical registrations awaiting spatial re-entry.</summary>
public int SuspendedRegistrationCount => _suspendedEntities.Count;
/// <summary>
/// Retires the complete logical registry at terminal physics-engine
/// disposal, including suspended live registrations that own no cell row.
/// </summary>
public void Clear()
{
_cells.Clear();
_entityToCells.Clear();
_suspendedEntities.Clear();
_withdrawnPrefixesByOwner.Clear();
_entityShapes.Clear();
_entityReg.Clear();
_fallback = null;
}
/// <summary>
/// Debug: enumerate every registered ShadowEntry (deduplicated across cells).
/// Single-shape entities return one entry per shape; multi-part entities

View file

@ -32,7 +32,29 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
bool HasLastStreamDispatchFailure,
int PendingDispatchCount,
bool IsDispatching,
bool IsSessionClearInProgress);
bool IsSessionClearInProgress,
bool IsDisposed)
{
public bool IsConverged =>
IsDisposed
&& ActiveEntityCount == 0
&& TeardownEntityCount == 0
&& ClaimedLocalIdCount == 0
&& AcceptedSnapshotCount == 0
&& UnresolvedParentRelationCount == 0
&& StagedParentRelationCount == 0
&& RecoveryParentRelationCount == 0
&& CommittedParentRelationCount == 0
&& ObjectCount == 0
&& ContainerCount == 0
&& ContainerProjectionCount == 0
&& EquipmentOwnerCount == 0
&& PendingMoveCount == 0
&& StreamSubscriberCount == 0
&& PendingDispatchCount == 0
&& !IsDispatching
&& !IsSessionClearInProgress;
}
/// <summary>
/// One exact DeleteObject acceptance issued by a
@ -140,7 +162,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Events.LastDispatchFailure is not null,
Events.PendingDispatchCount,
Events.IsDispatching,
_sessionClearInProgress);
_sessionClearInProgress,
_disposed);
}
public void BindEventContext(
@ -355,17 +378,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Entities.RetainTeardown(canonical);
try
{
Physics.RemoveSpatialProjection(canonical);
Entities.SetRemoteMotion(canonical, null);
Entities.SetRemoteMotionBindingInProgress(canonical, false);
Entities.SetProjectile(canonical, null);
Entities.SetProjectileBindingInProgress(canonical, false);
Entities.SetRequiresRemotePlacementRuntime(canonical, false);
Entities.SetPhysicsHost(canonical, null);
Entities.SetPhysicsBody(canonical, null);
Entities.SetPhysicsBodyAcquisitionInProgress(canonical, false);
Entities.SetHasPartArray(canonical, false);
Entities.ReleaseLocalId(canonical);
CompleteProjectionRetirement(canonical);
}
finally
{
@ -379,6 +392,30 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
}
}
/// <summary>
/// Completes the presentation acknowledgement for one retained
/// incarnation. Runtime retires every presentation-independent component
/// and releases its local identity only after the graphical host has
/// finished using those exact components for ExitWorld teardown.
/// </summary>
internal void CompleteProjectionRetirement(
RuntimeEntityRecord canonical)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(canonical);
Physics.RemoveSpatialProjection(canonical);
Entities.SetRemoteMotion(canonical, null);
Entities.SetRemoteMotionBindingInProgress(canonical, false);
Entities.SetProjectile(canonical, null);
Entities.SetProjectileBindingInProgress(canonical, false);
Entities.SetRequiresRemotePlacementRuntime(canonical, false);
Entities.SetPhysicsHost(canonical, null);
Entities.SetPhysicsBody(canonical, null);
Entities.SetPhysicsBodyAcquisitionInProgress(canonical, false);
Entities.SetHasPartArray(canonical, false);
Entities.ReleaseLocalId(canonical);
}
/// <summary>
/// Applies the retained-object half of an accepted CreateObject while the
/// exact canonical incarnation and its integration version remain current.

View file

@ -85,6 +85,7 @@ internal sealed class RuntimeEntityObjectViews
: IRuntimeEntityView
{
public int Count => owner.Count;
public int MaterializedCount => owner.ClaimedLocalIdCount;
public bool TryGet(
uint serverGuid,

View file

@ -22,6 +22,8 @@ public interface IRuntimeEntityView
{
int Count { get; }
int MaterializedCount { get; }
bool TryGet(uint serverGuid, out RuntimeEntitySnapshot entity);
/// <summary>

View file

@ -12,7 +12,19 @@ public readonly record struct RuntimePhysicsOwnershipSnapshot(
int CollisionAdmissionCount,
int CollisionGenerationCount,
bool OwnsProductionDataCache,
bool IsDisposed);
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,
@ -437,6 +449,8 @@ public sealed class RuntimePhysicsState : IDisposable
record,
expectedPlacementContract
|| runtime is IRuntimeRemotePlacement);
if (expectedBody is null)
InitializeNewPhysicsBody(record, candidateBody);
Entities.SetRemoteMotion(record, runtime);
Entities.SetPhysicsBody(record, candidateBody);
candidateBody.State = record.FinalPhysicsState;
@ -452,6 +466,52 @@ public sealed class RuntimePhysicsState : IDisposable
}
}
/// <summary>
/// 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.
/// </summary>
internal RemoteMotion GetOrCreateRemoteMotion(
RuntimeEntityRecord record,
Func<bool>? 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<bool>? 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;
}
/// <summary>
/// Acquires the one retail <c>CPhysicsObj</c> body for an exact accepted
/// object incarnation. Factories may cross a host boundary, so ownership
@ -499,6 +559,7 @@ public sealed class RuntimePhysicsState : IDisposable
$"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);
@ -905,6 +966,7 @@ public sealed class RuntimePhysicsState : IDisposable
{
if (_disposed)
return;
Engine.Clear();
_spatialRemotes.Clear();
_spatialProjectiles.Clear();
_spatialRoots.Clear();
@ -1039,4 +1101,22 @@ public sealed class RuntimePhysicsState : IDisposable
else
body.TransientState &= ~TransientStateFlags.Active;
}
/// <summary>
/// Retail <c>CPhysicsObj::set_description</c> (0x00514F40) installs the
/// accepted CreateObject velocity and omega once, when the incarnation's
/// CPhysicsObj is first acquired. Later SetState never replays them.
/// </summary>
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;
}
}

View file

@ -0,0 +1,44 @@
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Physics;
namespace AcDream.Runtime;
/// <summary>
/// One allocation-free terminal ownership ledger for the complete J5
/// presentation-independent simulation graph. It owns no mutable state.
/// Graphical and no-window hosts inspect the same canonical owners.
/// </summary>
public readonly record struct RuntimeSimulationOwnershipSnapshot(
RuntimeEntityObjectOwnershipSnapshot EntityObjects,
RuntimePhysicsOwnershipSnapshot Physics,
RuntimeGameplayOwnershipSnapshot Gameplay)
{
public bool IsConverged =>
EntityObjects.IsConverged
&& Physics.IsConverged
&& Gameplay.IsConverged;
}
public static class RuntimeSimulationOwnership
{
public static RuntimeSimulationOwnershipSnapshot Capture(
RuntimeEntityObjectLifetime entityObjects,
RuntimeInventoryState inventory,
RuntimeCharacterState character,
RuntimeCommunicationState communication,
RuntimeActionState actions,
RuntimeLocalPlayerMovementState movement)
{
ArgumentNullException.ThrowIfNull(entityObjects);
return new RuntimeSimulationOwnershipSnapshot(
entityObjects.CaptureOwnership(),
entityObjects.Physics.CaptureOwnership(),
RuntimeGameplayOwnership.Capture(
inventory,
character,
communication,
actions,
movement));
}
}