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
|
|
@ -9,6 +9,7 @@ using AcDream.Core.Net.Messages;
|
|||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Vfx;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime.Entities;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Types;
|
||||
|
|
@ -129,6 +130,31 @@ public sealed class EntityEffectControllerTests
|
|||
return entity;
|
||||
}
|
||||
|
||||
public LiveEntityRecord AwaitInitialPresentation(
|
||||
uint guid = Guid,
|
||||
ushort generation = 1,
|
||||
EntityEffectProfile? profile = null)
|
||||
{
|
||||
WorldSession.EntitySpawn spawn = Spawn(guid, generation);
|
||||
RuntimeEntityRecord canonical = Assert.IsType<RuntimeEntityRecord>(
|
||||
Runtime.RegisterLiveEntity(spawn).Canonical);
|
||||
WorldEntity entity = Assert.IsType<WorldEntity>(
|
||||
Runtime.MaterializeLiveEntity(
|
||||
canonical,
|
||||
spawn.Position!.Value.LandblockId,
|
||||
id => Entity(id, guid),
|
||||
LiveEntityProjectionKind.World,
|
||||
initializeProjection: exact =>
|
||||
exact.EffectProfile = profile ?? LiveProfile(),
|
||||
out LiveEntityRecord? record,
|
||||
LiveEntityMaterializationResidence.AwaitRuntimePlacement));
|
||||
Assert.Same(entity, record!.WorldEntity);
|
||||
Assert.False(record.IsSpatiallyProjected);
|
||||
Assert.False(record.IsSpatiallyVisible);
|
||||
Assert.True(Controller.PrepareLiveEntityOwner(guid));
|
||||
return record;
|
||||
}
|
||||
|
||||
public static EntityEffectProfile LiveProfile(
|
||||
uint tableDid = TableDid,
|
||||
uint rawDefaultType = RawType,
|
||||
|
|
@ -187,6 +213,32 @@ public sealed class EntityEffectControllerTests
|
|||
Assert.Equal(1, fixture.Runner.ActiveScriptCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeInitialPlacementWindow_DefersPacketsUntilPresentationBinds()
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
LiveEntityRecord record = fixture.AwaitInitialPresentation();
|
||||
|
||||
fixture.Controller.HandleDirect(new PlayPhysicsScript(Guid, DirectDid));
|
||||
fixture.Controller.HandleTyped(
|
||||
new PlayPhysicsScriptType(Guid, RawType, 0.5f));
|
||||
|
||||
Assert.Equal(2, fixture.Controller.PendingPacketCount);
|
||||
Assert.Equal(0, fixture.Runner.ActiveScriptCount);
|
||||
|
||||
// Runtime has committed the first frame and the graphical placement
|
||||
// receipt is now publishing its pose/resources.
|
||||
record.FullCellId = 0x01010001u;
|
||||
record.IsSpatiallyProjected = true;
|
||||
record.IsSpatiallyVisible = true;
|
||||
Assert.True(fixture.Controller.OnPresentationBound(record));
|
||||
Assert.Equal(0, fixture.Controller.PendingPacketCount);
|
||||
Assert.Equal(2, fixture.Runner.ActiveScriptCount);
|
||||
|
||||
fixture.Runner.Tick(0.0);
|
||||
Assert.Equal([1u, 2u], EmitterIds(fixture.Sink));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadyOwnerReceivesDirectAndTypedPacketsImmediately()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue