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

@ -6,19 +6,20 @@ namespace AcDream.App.World;
/// <summary>
/// Update-thread state machine for parent relations that may arrive before
/// either CreateObject. Unaccepted wire events remain in arrival order;
/// rollback history contains only relations that passed the canonical retail
/// timestamp gate.
/// committed rollback history contains only relations that passed both the
/// canonical retail timestamp gate and retail <c>PartArray::add_child</c>
/// validation.
/// </summary>
public sealed class ParentAttachmentState
{
private readonly Dictionary<uint, Queue<ParentAttachmentRelation>> _unresolvedByChild = new();
private readonly Dictionary<uint, ParentAttachmentRelation> _projectionByChild = new();
private readonly Dictionary<uint, ParentAttachmentRelation> _stagedByChild = new();
private readonly Dictionary<uint, ParentAttachmentRelation> _recoveryByChild = new();
private readonly Dictionary<uint, ParentAttachmentRelation> _lastAcceptedByChild = new();
public void AcceptCreateObjectRelation(ParentAttachmentRelation relation)
{
_projectionByChild[relation.ChildGuid] = relation;
_lastAcceptedByChild[relation.ChildGuid] = relation;
_stagedByChild[relation.ChildGuid] = relation;
}
public void Enqueue(ParentEvent.Parsed update)
@ -50,6 +51,8 @@ public sealed class ParentAttachmentState
Func<uint, ushort?> resolveInstance,
Func<ParentEvent.Parsed, bool> accept)
{
if (_stagedByChild.ContainsKey(childGuid))
return;
if (!_unresolvedByChild.TryGetValue(childGuid, out Queue<ParentAttachmentRelation>? queue))
return;
int candidateCount = queue.Count;
@ -95,8 +98,11 @@ public sealed class ParentAttachmentState
if (!accept(update))
continue;
_projectionByChild[childGuid] = relation;
_lastAcceptedByChild[childGuid] = relation;
_stagedByChild[childGuid] = relation with
{
WaitOwner = ParentAttachmentWaitOwner.Unknown,
};
break;
}
if (queue.Count == 0)
@ -104,22 +110,109 @@ public sealed class ParentAttachmentState
}
public bool TryGetProjection(uint childGuid, out ParentAttachmentRelation relation) =>
_projectionByChild.TryGetValue(childGuid, out relation);
_stagedByChild.TryGetValue(childGuid, out relation)
|| _recoveryByChild.TryGetValue(childGuid, out relation);
public void MarkProjected(uint childGuid) => _projectionByChild.Remove(childGuid);
public bool TryGetStagedProjection(
uint childGuid,
out ParentAttachmentRelation relation) =>
_stagedByChild.TryGetValue(childGuid, out relation);
public bool TryGetRecoveryProjection(
uint childGuid,
out ParentAttachmentRelation relation) =>
_recoveryByChild.TryGetValue(childGuid, out relation);
public void CopyPendingProjectionChildrenTo(List<uint> destination)
{
ArgumentNullException.ThrowIfNull(destination);
destination.Clear();
foreach (uint childGuid in _stagedByChild.Keys)
destination.Add(childGuid);
foreach (uint childGuid in _recoveryByChild.Keys)
{
if (!_stagedByChild.ContainsKey(childGuid))
destination.Add(childGuid);
}
}
public bool IsCommitted(ParentAttachmentRelation relation) =>
_lastAcceptedByChild.TryGetValue(
relation.ChildGuid,
out ParentAttachmentRelation committed)
&& committed == relation;
public bool IsPending(
ParentAttachmentRelation relation,
ParentProjectionCandidateKind kind) =>
(kind is ParentProjectionCandidateKind.Staged
? _stagedByChild
: _recoveryByChild).TryGetValue(
relation.ChildGuid,
out ParentAttachmentRelation pending)
&& pending == relation;
public void MarkProjected(
ParentAttachmentRelation relation,
ParentProjectionCandidateKind kind)
{
Dictionary<uint, ParentAttachmentRelation> source =
kind is ParentProjectionCandidateKind.Staged
? _stagedByChild
: _recoveryByChild;
if (source.TryGetValue(
relation.ChildGuid,
out ParentAttachmentRelation pending)
&& pending == relation)
{
source.Remove(relation.ChildGuid);
}
}
public bool CommitProjection(ParentAttachmentRelation relation)
{
if (!_stagedByChild.TryGetValue(
relation.ChildGuid,
out ParentAttachmentRelation staged)
|| staged != relation)
{
return false;
}
_lastAcceptedByChild[relation.ChildGuid] = relation;
_stagedByChild.Remove(relation.ChildGuid);
_recoveryByChild[relation.ChildGuid] = relation;
return true;
}
public void RejectProjection(ParentAttachmentRelation relation)
{
if (_stagedByChild.TryGetValue(
relation.ChildGuid,
out ParentAttachmentRelation staged)
&& staged == relation)
{
_stagedByChild.Remove(relation.ChildGuid);
}
}
public bool RestoreLastAccepted(uint childGuid)
{
if (!_lastAcceptedByChild.TryGetValue(childGuid, out ParentAttachmentRelation relation))
return false;
_projectionByChild[childGuid] = relation;
_recoveryByChild[childGuid] = relation;
return true;
}
public IReadOnlyList<uint> ChildrenWaitingForParent(uint parentGuid)
{
var result = new HashSet<uint>();
foreach ((uint childGuid, ParentAttachmentRelation relation) in _projectionByChild)
foreach ((uint childGuid, ParentAttachmentRelation relation) in _stagedByChild)
{
if (relation.ParentGuid == parentGuid)
result.Add(childGuid);
}
foreach ((uint childGuid, ParentAttachmentRelation relation) in _recoveryByChild)
{
if (relation.ParentGuid == parentGuid)
result.Add(childGuid);
@ -136,11 +229,13 @@ public sealed class ParentAttachmentState
public void RemoveObject(uint guid)
{
_projectionByChild.Remove(guid);
_stagedByChild.Remove(guid);
_recoveryByChild.Remove(guid);
_lastAcceptedByChild.Remove(guid);
_unresolvedByChild.Remove(guid);
RemoveParentReferences(_projectionByChild, guid);
RemoveParentReferences(_stagedByChild, guid);
RemoveParentReferences(_recoveryByChild, guid);
RemoveParentReferences(_lastAcceptedByChild, guid);
uint[] children = _unresolvedByChild.Keys.ToArray();
@ -165,9 +260,11 @@ public sealed class ParentAttachmentState
FilterChildCandidates(
guid,
relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent);
_projectionByChild.Remove(guid);
_stagedByChild.Remove(guid);
_recoveryByChild.Remove(guid);
_lastAcceptedByChild.Remove(guid);
RemoveParentReferences(_projectionByChild, guid);
RemoveParentReferences(_stagedByChild, guid);
RemoveParentReferences(_recoveryByChild, guid);
RemoveParentReferences(_lastAcceptedByChild, guid);
FilterParentCandidates(
guid,
@ -187,9 +284,11 @@ public sealed class ParentAttachmentState
FilterChildCandidates(
guid,
relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent);
_projectionByChild.Remove(guid);
_stagedByChild.Remove(guid);
_recoveryByChild.Remove(guid);
_lastAcceptedByChild.Remove(guid);
RemoveParentReferences(_projectionByChild, guid);
RemoveParentReferences(_stagedByChild, guid);
RemoveParentReferences(_recoveryByChild, guid);
RemoveParentReferences(_lastAcceptedByChild, guid);
FilterParentCandidates(
guid,
@ -204,13 +303,15 @@ public sealed class ParentAttachmentState
/// </summary>
public void EndChildProjection(uint childGuid)
{
_projectionByChild.Remove(childGuid);
_stagedByChild.Remove(childGuid);
_recoveryByChild.Remove(childGuid);
_lastAcceptedByChild.Remove(childGuid);
}
public void RemoveChild(uint childGuid)
{
_projectionByChild.Remove(childGuid);
_stagedByChild.Remove(childGuid);
_recoveryByChild.Remove(childGuid);
_lastAcceptedByChild.Remove(childGuid);
_unresolvedByChild.Remove(childGuid);
}
@ -218,7 +319,8 @@ public sealed class ParentAttachmentState
public void Clear()
{
_unresolvedByChild.Clear();
_projectionByChild.Clear();
_stagedByChild.Clear();
_recoveryByChild.Clear();
_lastAcceptedByChild.Clear();
}
@ -286,3 +388,9 @@ public enum ParentAttachmentWaitOwner
Parent,
Child,
}
public enum ParentProjectionCandidateKind
{
Staged,
Recovery,
}