fix(vfx): bind effects after canonical placement
C3c created graphical effect, projectile, and static-animation sidecars before Runtime finished the entity's first SetPosition. One-shot F754/F755 packets could be discarded, projectiles could adopt a cell-less body, and animated statics could compete for body ownership. Keep effects behind an exact-incarnation presentation barrier, retry projectile/static binding on the committed visibility edge, and keep effect cells synchronized with canonical rebuckets. User verified spell, recall, arrow, projectile, portal, and static presentation; 90 focused App tests and the Release build pass.
This commit is contained in:
parent
1fc529cdcb
commit
f24532adf3
11 changed files with 417 additions and 50 deletions
|
|
@ -53,6 +53,9 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
private readonly Func<WorldEntity, bool> _isResident;
|
||||
private readonly Action<WorldEntity, PhysicsBody> _commitLiveRoot;
|
||||
private readonly Func<WorldEntity, ulong> _residencyVersion;
|
||||
private readonly Func<WorldEntity,
|
||||
(LiveEntityAnimationState Animation, PhysicsBody Body)?>?
|
||||
_resolveLiveOwner;
|
||||
private readonly Dictionary<uint, Owner> _owners = new();
|
||||
private readonly List<Owner> _snapshot = new();
|
||||
private readonly List<Owner> _hookSnapshot = new();
|
||||
|
|
@ -63,7 +66,10 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
Action<WorldEntity, IReadOnlyList<Matrix4x4>, IReadOnlyList<bool>> publishPartPoses,
|
||||
Func<WorldEntity, bool>? isResident = null,
|
||||
Action<WorldEntity, PhysicsBody>? commitLiveRoot = null,
|
||||
Func<WorldEntity, ulong>? residencyVersion = null)
|
||||
Func<WorldEntity, ulong>? residencyVersion = null,
|
||||
Func<WorldEntity,
|
||||
(LiveEntityAnimationState Animation, PhysicsBody Body)?>?
|
||||
resolveLiveOwner = null)
|
||||
{
|
||||
_animationLoader = animationLoader
|
||||
?? throw new ArgumentNullException(nameof(animationLoader));
|
||||
|
|
@ -74,6 +80,7 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
_isResident = isResident ?? (_ => true);
|
||||
_commitLiveRoot = commitLiveRoot ?? ((_, _) => { });
|
||||
_residencyVersion = residencyVersion ?? (_ => 0UL);
|
||||
_resolveLiveOwner = resolveLiveOwner;
|
||||
}
|
||||
|
||||
internal int Count => _owners.Count;
|
||||
|
|
@ -363,12 +370,30 @@ internal sealed class RetailStaticAnimatingObjectScheduler : ILiveStaticPartFram
|
|||
foreach (Owner owner in _snapshot)
|
||||
{
|
||||
if (!_owners.TryGetValue(owner.Entity.Id, out Owner? current)
|
||||
|| !ReferenceEquals(current, owner)
|
||||
|| owner.Sequencer is not { } sequencer)
|
||||
|| !ReferenceEquals(current, owner))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// C3c: live Static objects are registered while their visual
|
||||
// sidecar is hydrated, before Runtime has completed the initial
|
||||
// CreateObject SetPosition transaction. The first-entry
|
||||
// conductor owns construction of the canonical CPhysicsObj.
|
||||
// Resolve and bind that already-placed owner lazily; constructing
|
||||
// a second/eager body here makes the conductor reject authority
|
||||
// and leaves portals, doors, and other animated statics cell-less.
|
||||
if (owner.Sequencer is null
|
||||
&& owner.Entity.ServerGuid != 0
|
||||
&& _resolveLiveOwner?.Invoke(owner.Entity) is { } binding)
|
||||
{
|
||||
_ = BindLiveOwner(
|
||||
owner.Entity,
|
||||
binding.Animation,
|
||||
binding.Body);
|
||||
}
|
||||
if (owner.Sequencer is not { } sequencer)
|
||||
continue;
|
||||
|
||||
owner.ElapsedSinceUpdate += elapsedSeconds;
|
||||
if (!_isResident(owner.Entity))
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue