refactor(world): extract live projection mechanics

Move appearance rebinding, collision construction, default-pose resolution, local shadow ownership, and exact leave-world presentation into focused owners. Preserve retail parent ordering with staged validation, committed recovery, recursive attached-subtree withdrawal, and retryable exact teardown across parent, pickup, position, unwield, and pose-loss edges.

Co-Authored-By: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-21 16:17:03 +02:00
parent c87bac31a0
commit 69a2ca0c6d
26 changed files with 4172 additions and 430 deletions

View file

@ -0,0 +1,175 @@
using AcDream.App.Physics;
using AcDream.App.Rendering.Vfx;
using AcDream.Core.Physics;
using AcDream.Core.Plugins;
namespace AcDream.App.World;
/// <summary>
/// Owns retail <c>CPhysicsObj::leave_world</c>'s App projection tail for a
/// still-live exact record. Component projection is removed before canonical
/// runtime withdrawal, matching the shipped failure/retry boundary.
/// </summary>
internal sealed class LiveEntityProjectionWithdrawalController
{
private readonly LiveEntityRuntime _runtime;
private readonly ProjectileController _projectiles;
private readonly WorldGameState _worldState;
private readonly WorldEvents _worldEvents;
private readonly ShadowObjectRegistry _shadows;
private readonly EntityEffectPoseRegistry _effectPoses;
private readonly LocalPlayerShadowState _localPlayerShadow;
public LiveEntityProjectionWithdrawalController(
LiveEntityRuntime runtime,
ProjectileController projectiles,
WorldGameState worldState,
WorldEvents worldEvents,
ShadowObjectRegistry shadows,
EntityEffectPoseRegistry effectPoses,
LocalPlayerShadowState localPlayerShadow)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_projectiles = projectiles ?? throw new ArgumentNullException(nameof(projectiles));
_worldState = worldState ?? throw new ArgumentNullException(nameof(worldState));
_worldEvents = worldEvents ?? throw new ArgumentNullException(nameof(worldEvents));
_shadows = shadows ?? throw new ArgumentNullException(nameof(shadows));
_effectPoses = effectPoses ?? throw new ArgumentNullException(nameof(effectPoses));
_localPlayerShadow = localPlayerShadow
?? throw new ArgumentNullException(nameof(localPlayerShadow));
}
public bool Withdraw(uint serverGuid, uint localPlayerGuid)
{
if (!_runtime.TryGetRecord(serverGuid, out LiveEntityRecord record)
|| record.WorldEntity is null)
{
return false;
}
return Withdraw(record, localPlayerGuid);
}
/// <summary>
/// Withdraws only the captured incarnation and projection operation. A
/// leave-world observer may accept a fresh same-generation Position and
/// reproject this record; that newer projection must survive the displaced
/// outer operation just as a newer INSTANCE_TS must.
/// </summary>
public bool Withdraw(LiveEntityRecord record, uint localPlayerGuid)
{
ArgumentNullException.ThrowIfNull(record);
if (!_runtime.IsCurrentRecord(record) || record.WorldEntity is null)
return false;
return Withdraw(
record,
record.PositionAuthorityVersion,
record.ProjectionMutationVersion,
localPlayerGuid);
}
public bool Withdraw(
LiveEntityRecord record,
ulong positionAuthorityVersion,
ulong projectionMutationVersion,
uint localPlayerGuid)
{
ExactProjectionWithdrawalOutcome outcome = WithdrawExact(
record,
positionAuthorityVersion,
projectionMutationVersion,
localPlayerGuid);
if (outcome.Failure is not null)
throw outcome.Failure;
return outcome.Disposition is ExactProjectionWithdrawalDisposition.Completed;
}
public ExactProjectionWithdrawalOutcome WithdrawExact(
LiveEntityRecord record,
ulong positionAuthorityVersion,
ulong projectionMutationVersion,
uint localPlayerGuid)
{
ArgumentNullException.ThrowIfNull(record);
if (!_runtime.IsCurrentRecord(record)
|| record.WorldEntity is null
|| record.PositionAuthorityVersion != positionAuthorityVersion
|| record.ProjectionMutationVersion != projectionMutationVersion)
{
return new(
ExactProjectionWithdrawalDisposition.Superseded,
Failure: null);
}
try
{
LeaveWorld(record, localPlayerGuid);
bool completed = _runtime.WithdrawLiveEntityProjection(
record,
positionAuthorityVersion,
projectionMutationVersion);
return new(
completed
? ExactProjectionWithdrawalDisposition.Completed
: ExactProjectionWithdrawalDisposition.Superseded,
Failure: null);
}
catch (Exception error)
{
bool current = _runtime.IsCurrentRecord(record);
ExactProjectionWithdrawalDisposition disposition =
current
&& record.PositionAuthorityVersion == positionAuthorityVersion
&& record.ProjectionMutationVersion == projectionMutationVersion
&& record.IsSpatiallyProjected
? ExactProjectionWithdrawalDisposition.Pending
: current
&& record.PositionAuthorityVersion == positionAuthorityVersion
&& !record.IsSpatiallyProjected
? ExactProjectionWithdrawalDisposition.Completed
: ExactProjectionWithdrawalDisposition.Superseded;
return new(disposition, error);
}
}
/// <summary>
/// Removes only the shorter world-facing component projection. This exact
/// overload is also used by logical teardown after the active GUID table
/// may already expose a replacement incarnation.
/// </summary>
public void LeaveWorld(LiveEntityRecord record, uint localPlayerGuid)
{
ArgumentNullException.ThrowIfNull(record);
if (record.WorldEntity is not { } entity)
return;
bool retainedProjectileShadow = _projectiles.LeaveWorld(record);
if (!retainedProjectileShadow)
_shadows.Suspend(entity.Id);
_worldState.RemoveById(entity.Id);
_worldEvents.ForgetEntity(entity.Id);
if (record.ServerGuid == localPlayerGuid
&& (!_runtime.TryGetRecord(record.ServerGuid, out LiveEntityRecord current)
|| ReferenceEquals(current, record)))
{
_localPlayerShadow.Clear();
}
// Pose-attached effects keep logical ownership while cell-less. A
// missing pose suppresses stale anchors; re-entry republishes the same
// WorldEntity frames without replaying create-time resources.
_effectPoses.Remove(entity.Id);
}
}
public enum ExactProjectionWithdrawalDisposition
{
Completed,
Pending,
Superseded,
}
public readonly record struct ExactProjectionWithdrawalOutcome(
ExactProjectionWithdrawalDisposition Disposition,
Exception? Failure);