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:
Erik 2026-08-03 12:10:21 +02:00
parent 1fc529cdcb
commit f24532adf3
11 changed files with 417 additions and 50 deletions

View file

@ -107,7 +107,7 @@ public sealed class ProjectileControllerTests
Assert.Null(record.AnimationRuntime);
var remote =
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
remote.Body.Position = entity.Position;
remote.Body.SnapToCell(CellA, entity.Position, entity.Position);
remote.Body.Orientation = entity.Rotation;
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0, 1, 1));
Assert.Same(remote.Body, record.ProjectileRuntime!.Body);
@ -1006,6 +1006,8 @@ public sealed class ProjectileControllerTests
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
var remote =
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
WorldEntity entity = record.WorldEntity!;
remote.Body.SnapToCell(CellA, entity.Position, entity.Position);
record.FinalPhysicsState = MissileState;
Assert.True(fixture.Controller.ApplyAuthoritativeState(
@ -1081,7 +1083,10 @@ public sealed class ProjectileControllerTests
Assert.Equal(new Vector3(0f, 0f, 2f), remote.Body.Omega);
remote.Body.set_velocity(new Vector3(8f, 0f, 0f));
remote.Body.Omega = new Vector3(0f, 0f, 3f);
remote.Body.Position = entity.Position;
remote.Body.SnapToCell(
startCell,
entity.Position,
new Vector3(191f, 10f, 50f));
remote.Body.State = record.FinalPhysicsState;
PhysicsBody body = remote.Body;
@ -1142,7 +1147,7 @@ public sealed class ProjectileControllerTests
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
var remote =
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
remote.Body.Position = entity.Position;
remote.Body.SnapToCell(CellA, entity.Position, entity.Position);
remote.Body.Orientation = entity.Rotation;
Assert.True(float.IsNaN(record.Snapshot.Physics!.Value.Velocity!.Value.X));
@ -1181,6 +1186,55 @@ public sealed class ProjectileControllerTests
Assert.Equal(MissileState, remote.Body.State);
}
[Fact]
public void ResidencePlacementVisibility_RetriesProjectileBindingAfterBodyFrameCommits()
{
var fixture = new Fixture();
LiveEntityRecord record = fixture.Spawn(instance: 1);
WorldEntity entity = record.WorldEntity!;
var remote = fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
Assert.Null(record.ProjectileRuntime);
Assert.True(fixture.Live.WithdrawLiveEntityProjection(Guid));
remote.Body.Orientation = Quaternion.Identity;
remote.Body.set_velocity(new Vector3(10f, 0f, 0f));
remote.Body.SnapToCell(CellA, entity.Position, entity.Position);
Assert.True(fixture.Live.RebucketLiveEntity(Guid, CellA));
Assert.NotNull(record.ProjectileRuntime);
Assert.Same(remote.Body, record.ProjectileRuntime!.Body);
Assert.True(record.ProjectileRuntime.Body.InWorld);
}
[Fact]
public void SharedBodyCell_IsCanonicalBeforePresentationCellIsProjected()
{
var fixture = new Fixture();
LiveEntityRecord record = fixture.Spawn(instance: 1);
WorldEntity entity = record.WorldEntity!;
var remote = fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
Assert.True(fixture.Live.WithdrawLiveEntityProjection(Guid));
record.CanonicalLandblockId = 0u;
record.FullCellId = 0u;
Assert.Equal(0u, record.FullCellId);
remote.Body.Orientation = Quaternion.Identity;
remote.Body.set_velocity(new Vector3(10f, 0f, 0f));
remote.Body.SnapToCell(CellA, entity.Position, entity.Position);
Assert.True(fixture.Controller.TryBind(
record,
ProjectileSetup(),
currentTime: 1.0,
liveCenterX: 1,
liveCenterY: 1));
Assert.Equal(CellA, record.FullCellId);
Assert.Same(remote.Body, record.ProjectileRuntime!.Body);
}
[Fact]
public void SharedRemoteMotionCell_DelegatesToCanonicalLiveRecord()
{