feat(vfx): port retail effect scheduling and delivery

This commit is contained in:
Erik 2026-07-14 09:25:44 +02:00
parent 363e046112
commit 96ddfdf175
28 changed files with 2473 additions and 691 deletions

View file

@ -460,6 +460,46 @@ public sealed class LiveEntityRuntimeTests
Assert.Equal(0, resources.UnregisterCount);
}
[Fact]
public void RootRuntimePausesWhilePendingOrFrozenAndResumesInVisibleCell()
{
const uint guid = 0x70000027u;
var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources());
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn);
runtime.MaterializeLiveEntity(
guid,
spawn.Position!.Value.LandblockId,
id => Entity(id, guid));
Assert.True(runtime.ShouldAdvanceRootRuntime(guid));
Assert.True(runtime.RebucketLiveEntity(guid, 0x02020001u));
Assert.False(runtime.ShouldAdvanceRootRuntime(guid));
spatial.AddLandblock(EmptyLandblock(0x0202FFFFu));
Assert.True(runtime.ShouldAdvanceRootRuntime(guid));
Assert.True(runtime.TryApplyState(
new SetState.Parsed(
guid,
(uint)(PhysicsStateFlags.ReportCollisions | PhysicsStateFlags.Frozen),
1,
2),
out _));
Assert.False(runtime.ShouldAdvanceRootRuntime(guid));
Assert.True(runtime.TryApplyState(
new SetState.Parsed(
guid,
(uint)PhysicsStateFlags.ReportCollisions,
1,
3),
out _));
Assert.True(runtime.ShouldAdvanceRootRuntime(guid));
}
[Fact]
public void LandblockUnload_HidesTopLevelViewAndReloadRestoresSameIdentity()
{
@ -701,109 +741,6 @@ public sealed class LiveEntityRuntimeTests
firstLocalEntityId: uint.MaxValue));
}
[Fact]
public void Delete_StopsScriptsOwnedByCanonicalLocalId_NotServerGuid()
{
const uint serverGuid = 0x70000013u;
const uint scriptId = 0x33000001u;
var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var script = new DatPhysicsScript();
script.ScriptData.Add(new PhysicsScriptData
{
StartTime = 60d,
Hook = new CreateParticleHook { EmitterInfoId = 0x32000001u },
});
var particleSystem = new ParticleSystem(new EmitterDescRegistry());
var particleSink = new ParticleHookSink(particleSystem);
var runner = new PhysicsScriptRunner(
id => id == scriptId ? script : null,
particleSink);
var activator = new EntityScriptActivator(
runner,
particleSink,
_ => new ScriptActivationInfo(scriptId, Array.Empty<System.Numerics.Matrix4x4>()));
var runtime = new LiveEntityRuntime(
spatial,
new DelegateLiveEntityResourceLifecycle(
activator.OnCreate,
entity => activator.OnRemove(entity.Id)),
record => activator.OnRemoveLegacyOwner(
record.ServerGuid,
record.LocalEntityId ?? 0u));
WorldSession.EntitySpawn spawn = Spawn(serverGuid, 7, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn);
// Current AD-32 behavior before Step 4's pending FIFO: an F754 that
// precedes materialization temporarily starts under server GUID.
Assert.True(activator.PlayLegacyPending(
serverGuid,
scriptId,
System.Numerics.Vector3.Zero));
Assert.Equal(1, runner.ActiveScriptCount);
WorldEntity entity = runtime.MaterializeLiveEntity(
serverGuid,
spawn.Position!.Value.LandblockId,
id => Entity(id, serverGuid))!;
Assert.NotEqual(serverGuid, entity.Id);
Assert.Equal(2, runner.ActiveScriptCount);
Assert.True(runtime.UnregisterLiveEntity(
new DeleteObject.Parsed(serverGuid, spawn.InstanceSequence),
isLocalPlayer: false));
Assert.Equal(0, runner.ActiveScriptCount);
}
[Fact]
public void StaleDelete_DoesNotStopCurrentGenerationPreMaterializationF754Alias()
{
const uint serverGuid = 0x70000027u;
const uint scriptId = 0x33000001u;
var script = new DatPhysicsScript();
script.ScriptData.Add(new PhysicsScriptData
{
StartTime = 60d,
Hook = new CreateParticleHook { EmitterInfoId = 0x32000001u },
});
var particleSink = new ParticleHookSink(
new ParticleSystem(new EmitterDescRegistry()));
var runner = new PhysicsScriptRunner(
id => id == scriptId ? script : null,
particleSink);
var activator = new EntityScriptActivator(
runner,
particleSink,
_ => null);
var runtime = new LiveEntityRuntime(
new GpuWorldState(),
new RecordingResources(),
record => activator.OnRemoveLegacyOwner(
record.ServerGuid,
record.LocalEntityId ?? 0u));
WorldSession.EntitySpawn spawn = Spawn(serverGuid, 10, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn);
Assert.True(activator.PlayLegacyPending(
serverGuid,
scriptId,
System.Numerics.Vector3.Zero));
Assert.False(runtime.UnregisterLiveEntity(
new DeleteObject.Parsed(serverGuid, 9),
isLocalPlayer: false));
Assert.Equal(1, runner.ActiveScriptCount);
Assert.Equal(1, activator.LegacyPendingOwnerCount);
Assert.True(runtime.UnregisterLiveEntity(
new DeleteObject.Parsed(serverGuid, 10),
isLocalPlayer: false));
Assert.Equal(0, runner.ActiveScriptCount);
Assert.Equal(0, activator.LegacyPendingOwnerCount);
}
[Fact]
public void Delete_CleansLogicalRecordEvenWhenPreTeardownCallbackFails()
{